2
\$\begingroup\$

I was trying out some projects like a status bar alert. It made most sense for this status bar to be a singleton: there is only one alert visible at a time.

final class UIStatusBarAlert {
    static var shared = UIStatusBarAlert()

    private var window: UIWindow!
    private var alertViewController: AlertViewController!

    private init() {
        let size = CGSize(width: UIScreen.main.bounds.width, height: UIApplication.shared.statusBarFrame.height)
        window = UIWindow(frame: CGRect(origin: CGPoint.zero, size: size))
        window.windowLevel = UIWindowLevelStatusBar + 1
        alertViewController = AlertViewController()
        window.rootViewController = alertViewController
    }

    var size: CGSize { return window.frame.size }

    var isHidden: Bool {
        get {
            return window.isHidden
        }
        set(bool) {
            window.isHidden = bool
        }
    }

    func setConfiguration(_ configuration: Configuration) {
        alertViewController.titleLabel.text = configuration.title
        alertViewController.titleLabel.textColor = configuration.tintColor
        alertViewController.titleLabel.font = configuration.font
        alertViewController.view.backgroundColor = configuration.backgroundColor
    }

    func setHidden(_ hidden: Bool, with animation: UIStatusBarAnimation) {
        let duration = TimeInterval(UINavigationControllerHideShowBarDuration)
        if hidden {
            hideStatusBar(animation: animation, animationDuration: duration)
        } else {
            showStatusBar(animation: animation, animationDuration: duration)
        }
    }

    func show(for duration: TimeInterval, with animation: UIStatusBarAnimation) {
        setHidden(false, with: animation)
        Timer.scheduledTimer(withTimeInterval: duration, repeats: false) { (timer) in
            self.setHidden(true, with: animation)
        }
    }

    private func hideStatusBar(animation: UIStatusBarAnimation, animationDuration: TimeInterval) {
        switch animation {
        case .fade:
            UIView.animate(withDuration: animationDuration, animations: {
                self.window.alpha = 0
            }, completion: { (Bool) in
                self.isHidden = true
            })
        case .slide:
            UIView.animate(withDuration: animationDuration, animations: {
                self.window.frame.origin.y = -self.window.frame.height
            }, completion: { (Bool) in
                self.isHidden = true
            })
        case .none:
            self.isHidden = true
        }
    }

    private func showStatusBar(animation: UIStatusBarAnimation, animationDuration: TimeInterval) {
        switch animation {
        case .fade:
            if isHidden {
                window.alpha = 0
                window.isHidden = false
                window.frame.origin.y = 0
            }
            UIView.animate(withDuration: animationDuration, animations: {
                self.window.alpha = 1
            })
        case .slide:
            if isHidden {
                window.alpha = 1
                window.isHidden = false
                window.frame.origin.y = -window.frame.height
            }
            UIView.animate(withDuration: animationDuration, animations: {
                self.window.frame.origin.y = 0
            })
        case .none:
            window.alpha = 1
            isHidden = false
            window.frame.origin.y = 0
        }
    }

    private class AlertViewController: UIViewController {
        var titleLabel = UILabel()
        override func viewDidLoad() {
            titleLabel.frame = CGRect(origin: CGPoint.zero, size: UIStatusBarAlert.shared.size)
            titleLabel.textAlignment = .center
            view.addSubview(titleLabel)
        }
    }

    public class Configuration {
        var title = ""
        var tintColor = UIColor.black
        var backgroundColor = UIColor.white
        var font = UIFont.systemFont(ofSize: 15)
    }
}

I think that my organization is ok and that the access control is appropriate. Do you agree? Also, is my implementation of a singleton correct?

The main issue with the singleton setup was that some functions change the singleton before it had even been initialized; I am having trouble figuring out exactly why that is, but I found a work around. For example, setting the alertViewController.titleLabel properties would cause a crash (force unwrapped an optional value that is nil), but I fixed this by setting var titleLabel = UILabel() instead of var titleLabel: UILabel and initializing it in viewDidLoad. Is there a better way to get around this problem than that?

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.