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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.