search by tags

for the user

adventures into the land of the command line

message alert popups

my implementation of a custom message alert popup. it includes a network reachability monitor which will trigger the popup if there is no internet connection, but more on that in a future post. this one just just contains the message dialog class (which has a UIImage commented out because I dont use it) and an example of its use in a ViewController when pressing a button to manually trigger it based on some arbitrary condition.

the message dialog:

import Foundation
import UIKit
import ReachabilitySwift

public class MessageDialog {

    public init(){}

    public static func showNotification(type: String, title: String, message: String, dismissDelay: TimeInterval, completion: @escaping () -> () = {}) {

        let view = MessageDialogUIView()

        if type == "good" {
            view.setBackgroundColor(color: UIColor(netHex:0x3EB890))
            view.setTextColor(color: UIColor(netHex:0xFFFFFF))
        }
        else if type == "bad" {
            view.setBackgroundColor(color: UIColor(netHex:0xFA5563))
            view.setTextColor(color: UIColor(netHex:0xFFFFFF))
        }

        view.setTitle(title: title)
        view.setMessage(message: message)
        view.setDismisTimer(delay: dismissDelay)
        view.setCompletionBlock(completion)

        guard let window = UIApplication.shared.keyWindow else {
            print("Failed to show MessageDialog. No keywindow available.")
            return
        }

        window.addSubview(view)
        view.showNotification()
    }

}



// specifically for the internet connection, dismiss the permanent notification dialog when the internet connection is restored and stop listening
extension MessageDialogUIView: NetworkStatusListener {
    func networkStatusDidChange(status: Reachability.NetworkStatus) {
        switch status {
        case .notReachable:
            print("Internet Unavailable")
        case .reachableViaWiFi:
            self.dismissNotification()
            ReachabilityManager.shared.removeListener(listener: self as NetworkStatusListener)
        case .reachableViaWWAN:
            self.dismissNotification()
            ReachabilityManager.shared.removeListener(listener: self as NetworkStatusListener)
        }
    }
}

internal class MessageDialogUIView: UIView {

//    private let imageView: UIImageView = {
//        let view = UIImageView()
//        view.translatesAutoresizingMaskIntoConstraints = false
//        view.tintColor = .white
//        return view
//    }()

    private let titleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont(name: "Dosis-ExtraBold", size: 18)!
        label.textColor = .white
        return label
    }()

    private let messageLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont(name: "Dosis-SemiBold", size: 14)!
        label.textColor = .white
        label.numberOfLines = 2
        return label
    }()

    private var completion: () -> () = {}


    // Init

    required internal init?(coder aDecoder:NSCoder) { fatalError("Not implemented.") }

    internal init() {
        let deviceWidth = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
        let width = deviceWidth - 30
        let height = 75
        super.init(frame: CGRect(x: 0, y: -height, width: Int(width), height: height))
        center.x = UIScreen.main.bounds.width / 2

        setupLayer()
        setupSubviews()
        setupConstraints()
    }


    // Setup

    private func setupLayer() {
        layer.cornerRadius = 3
        layer.shadowRadius = 3
        layer.shadowOpacity = 0.25
        layer.shadowColor = UIColor.lightGray.cgColor
    }

    private func setupSubviews() {
//        addSubview(imageView)
        addSubview(titleLabel)
        addSubview(messageLabel)
    }

    private func setupConstraints() {
//        NSLayoutConstraint.activate([
//            imageView.topAnchor.constraint(equalTo: imageView.superview!.topAnchor, constant: 12),
//            imageView.leadingAnchor.constraint(equalTo: imageView.superview!.leadingAnchor, constant: 12),
//            imageView.bottomAnchor.constraint(equalTo: imageView.superview!.bottomAnchor, constant: -12),
//            imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
//            ])

        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(greaterThanOrEqualTo: titleLabel.superview!.topAnchor, constant: 8),
            titleLabel.leadingAnchor.constraint(equalTo: titleLabel.superview!.leadingAnchor, constant: 12),
            titleLabel.trailingAnchor.constraint(equalTo: titleLabel.superview!.trailingAnchor, constant: -12)
            ])

        NSLayoutConstraint.activate([
            messageLabel.topAnchor.constraint(lessThanOrEqualTo: titleLabel.bottomAnchor, constant: 2),
            messageLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
            messageLabel.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor)
            ])
    }

    // Sets the background color of the notification
    internal func setBackgroundColor(color: UIColor) {
        backgroundColor = color
    }

    // Sets the background color of the notification
    internal func setTextColor(color: UIColor) {
        titleLabel.textColor = color
        messageLabel.textColor = color
    }

    // Sets the title of the notification
    internal func setTitle(title: String) {
        titleLabel.text = title
    }

    // Sets the message of the notification
    internal func setMessage(message: String) {
        messageLabel.text = message
    }

    // Sets the image of the notification
//    internal func setImage(image: UIImage?) {
//        imageView.image = image
//    }

    // Sets the completion block of the notification for when it is dismissed
    internal func setCompletionBlock(_ completion: @escaping () -> ()) {
        self.completion = completion
    }

    // Dismisses the notification with a delay > 0
    internal func setDismisTimer(delay: TimeInterval) {
        if delay > 0 {
            Timer.scheduledTimer(timeInterval: Double(delay), target: self, selector: #selector(dismissNotification), userInfo: nil, repeats: false)
        }
        else if delay == 0 {
            // dont dismiss the notification, but listen for the internet connection to be restored
            ReachabilityManager.shared.addListener(listener: self as NetworkStatusListener)
        }
    }

    // Animates in the notification
    internal func showNotification() {
        DispatchQueue.main.async {
            UIView.animate(withDuration: 0.3, delay: 0.0, usingSpringWithDamping: 0.68, initialSpringVelocity: 0.1, options: UIViewAnimationOptions(), animations: {
                self.frame.origin.y = 30
            })
        }
    }

    // Animates out the notification
    @objc internal func dismissNotification() {
        DispatchQueue.main.async {
            UIView.animate(withDuration: 0.1, animations: {
                self.frame.origin.y = self.frame.origin.y + 5
            }, completion: {
                (complete: Bool) in
                UIView.animate(withDuration: 0.25, animations: {
                    self.center.y = -self.frame.height
                }, completion: { [weak self] (complete) in
                    self?.completion()
                    self?.removeFromSuperview()
                })
            })
        }
    }


}

its use in a ViewController:

import UIKit

class SomeViewController: UIViewController {

    var someValue: Int! = 0

    @IBAction func someRandomButton(_ sender: UIButton) {

        if someValue == 1 {

            MessageDialog.showNotification(type: "bad", title: "Oh-oh", message: "Some error message.", dismissDelay: 3)

        }
        else if someValue == 0 {

            MessageDialog.showNotification(type: "good", title: "Woo-hoo", message: "Some notification.", dismissDelay: 3)

        }

    }
    .
    .
    .

}

and an example of what it looks like: