Created
November 14, 2016 11:49
-
-
Save brocoo/a1a699a703b6d1c63d5854bb3b3edad7 to your computer and use it in GitHub Desktop.
A customisable alert view class to replace the default UIAlertController (Swift3)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public typealias AlertViewClosure = (AlertView) -> Void | |
// MARK: - | |
public final class AlertView: UIView { | |
// MARK: - AlertViewButton | |
public struct Button { | |
// MARK: - Type enum | |
public enum `Type` { | |
case normal | |
case confirmation | |
case destructive | |
} | |
// MARK: - Properties | |
let title: String | |
let action: AlertViewClosure | |
// MARK: - Initializer | |
public init(withTitle title: String, ofType type: Type = .normal, onAction action: @escaping AlertViewClosure = { (alertView) in alertView.dismiss() } ) { | |
self.title = title | |
self.action = action | |
} | |
// MARK: - | |
fileprivate static let `default` = Button(withTitle: "Ok") { (alertView) in alertView.dismiss() } | |
} | |
// MARK: - Type enum | |
public enum `Type` { | |
case error | |
case info | |
} | |
// MARK: - Properties | |
public let type: Type | |
public let title: String? | |
public let bodyText: String? | |
fileprivate let buttons: [Button] | |
// MARK: - UI Properties | |
fileprivate let windowOverlay: UIView | |
fileprivate let contentView: UIView | |
fileprivate let titleLabel: UILabel | |
fileprivate let bodyTextLabel: UILabel | |
fileprivate var buttonViews: [UIButton] = [] | |
fileprivate var contentViewYCenter: NSLayoutConstraint? = nil | |
// MARK: - Computed properties | |
fileprivate class var mainWindow: UIWindow { | |
return (UIApplication.shared.delegate?.window!)! | |
} | |
// MARK: - Initializer | |
public init(ofType type: Type, title: String?, bodyText: String?, buttons: [Button] = [Button.default]) { | |
guard buttons.count > 0 else { fatalError("No buttons provided for the AlertView") } | |
guard buttons.count < 3 else { fatalError("No more than 2 buttons per AlertView") } | |
self.type = type | |
self.title = title | |
self.bodyText = bodyText | |
self.buttons = buttons | |
self.windowOverlay = UIView() | |
self.contentView = UIView() | |
self.titleLabel = UILabel() | |
self.bodyTextLabel = UILabel() | |
super.init(frame: CGRect.zero) } | |
public required init?(coder aDecoder: NSCoder) { | |
fatalError("'AlertView' Can be instanciated from a Nib") | |
} | |
// MARK: - UI Setup | |
fileprivate func setupUI(inView view: UIView) { | |
self.backgroundColor = UIColor.clear | |
view.addSubview(self) | |
self.windowOverlay.backgroundColor = UIColor.clear | |
self.addSubview(self.windowOverlay) | |
self.contentView.backgroundColor = UIColor.white | |
self.contentView.layer.cornerRadius = 15.0 | |
self.contentView.layer.masksToBounds = true | |
self.addSubview(self.contentView) | |
self.titleLabel.text = self.title | |
self.titleLabel.textAlignment = .center | |
self.titleLabel.numberOfLines = 2 | |
self.contentView.addSubview(self.titleLabel) | |
self.bodyTextLabel.text = self.bodyText | |
self.bodyTextLabel.textAlignment = .left | |
self.bodyTextLabel.numberOfLines = 0 | |
self.contentView.addSubview(self.bodyTextLabel) | |
var tag: Int = 0 | |
for button in self.buttons { | |
let buttonView: UIButton = UIButton(type: .system) | |
buttonView.addTarget(self, action: #selector(AlertView.buttonTapped(sender:)), for: .touchUpInside) | |
buttonView.setTitle(button.title, for: .normal) | |
buttonView.tag = tag | |
self.contentView.addSubview(buttonView) | |
self.buttonViews.append(buttonView) | |
tag += 1 | |
} | |
} | |
fileprivate func setupUIConstraints(inView view: UIView) { | |
// Self | |
view.addConstraint(NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 0.0)) | |
view.addConstraint(NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0.0)) | |
view.addConstraint(NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0.0)) | |
view.addConstraint(NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0)) | |
self.translatesAutoresizingMaskIntoConstraints = false | |
// Window overlay | |
self.addConstraint(NSLayoutConstraint(item: self.windowOverlay, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0)) | |
self.addConstraint(NSLayoutConstraint(item: self.windowOverlay, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 0.0)) | |
self.addConstraint(NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: self.windowOverlay, attribute: .trailing, multiplier: 1.0, constant: 0.0)) | |
self.addConstraint(NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: self.windowOverlay, attribute: .bottom, multiplier: 1.0, constant: 0.0)) | |
self.windowOverlay.translatesAutoresizingMaskIntoConstraints = false | |
// Content | |
let contentViewYCenter = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: self.contentView, attribute: .centerY, multiplier: 1.0, constant: 0.0) | |
self.contentViewYCenter = contentViewYCenter | |
self.addConstraint(contentViewYCenter) | |
self.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 45.0)) | |
self.addConstraint(NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: self.contentView, attribute: .trailing, multiplier: 1.0, constant: 45.0)) | |
self.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: .height, relatedBy: .lessThanOrEqual, toItem: self, attribute: .height, multiplier: 1.0, constant: -90.0)) | |
self.contentView.translatesAutoresizingMaskIntoConstraints = false | |
// Title | |
self.contentView.addConstraint(NSLayoutConstraint(item: self.titleLabel, attribute: .top, relatedBy: .equal, toItem: self.contentView, attribute: .top, multiplier: 1.0, constant: 15.0)) | |
self.contentView.addConstraint(NSLayoutConstraint(item: self.titleLabel, attribute: .leading, relatedBy: .equal, toItem: self.contentView, attribute: .leading, multiplier: 1.0, constant: 15.0)) | |
self.contentView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: .trailing, relatedBy: .equal, toItem: self.titleLabel, attribute: .trailing, multiplier: 1.0, constant: 15.0)) | |
self.titleLabel.translatesAutoresizingMaskIntoConstraints = false | |
// Body | |
self.contentView.addConstraint(NSLayoutConstraint(item: self.bodyTextLabel, attribute: .top, relatedBy: .equal, toItem: self.titleLabel, attribute: .bottom, multiplier: 1.0, constant: 15.0)) | |
self.contentView.addConstraint(NSLayoutConstraint(item: self.bodyTextLabel, attribute: .leading, relatedBy: .equal, toItem: self.contentView, attribute: .leading, multiplier: 1.0, constant: 15.0)) | |
self.contentView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: .trailing, relatedBy: .equal, toItem: self.bodyTextLabel, attribute: .trailing, multiplier: 1.0, constant: 15.0)) | |
self.bodyTextLabel.translatesAutoresizingMaskIntoConstraints = false | |
// Buttons | |
var leftButton: UIButton? = nil | |
for button in self.buttonViews { | |
if let leftButton = leftButton { | |
self.contentView.addConstraint(NSLayoutConstraint(item: button, attribute: .leading, relatedBy: .equal, toItem: leftButton, attribute: .trailing, multiplier: 1.0, constant: 0.0)) | |
self.contentView.addConstraint(NSLayoutConstraint(item: leftButton, attribute: .width, relatedBy: .equal, toItem: button, attribute: .width, multiplier: 1.0, constant: 0.0)) | |
} else { | |
self.contentView.addConstraint(NSLayoutConstraint(item: button, attribute: .leading, relatedBy: .equal, toItem: self.contentView, attribute: .leading, multiplier: 1.0, constant: 0.0)) | |
} | |
self.contentView.addConstraint(NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal, toItem: self.bodyTextLabel, attribute: .bottom, multiplier: 1.0, constant: 15.0)) | |
self.contentView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: .bottom, relatedBy: .equal, toItem: button, attribute: .bottom, multiplier: 1.0, constant: 15.0)) | |
button.addConstraint(NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 50.0)) | |
button.translatesAutoresizingMaskIntoConstraints = false | |
leftButton = button | |
} | |
if let leftButton = leftButton { | |
self.contentView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: .trailing, relatedBy: .equal, toItem: leftButton, attribute: .trailing, multiplier: 1.0, constant: 0.0)) | |
} | |
} | |
// MARK: - Actions | |
@objc fileprivate func buttonTapped(sender: UIButton) { | |
let tag = sender.tag | |
self.buttons[tag].action(self) | |
} | |
// MARK: - Presentation | |
public func present(animated: Bool = true, inView view: UIView = AlertView.mainWindow) { | |
self.setupUI(inView: view) | |
self.setupUIConstraints(inView: view) | |
self.contentViewYCenter?.constant = -view.bounds.height | |
self.layoutIfNeeded() | |
self.contentViewYCenter?.constant = 0.0 | |
UIView.animate(withDuration: 0.3, delay: 0.0, usingSpringWithDamping: 0.75, initialSpringVelocity: 1.0, options: .curveLinear, animations: { | |
self.windowOverlay.layer.backgroundColor = UIColor(white: 0.0, alpha: 0.5).cgColor | |
self.layoutIfNeeded() | |
}, completion: nil) | |
} | |
public func dismiss(animated: Bool = true, onCompletion completion: @escaping Closure = { _ in }) { | |
guard let superview = self.superview else { | |
completion() | |
return | |
} | |
self.contentViewYCenter?.constant = -superview.bounds.height | |
UIView.animate(withDuration: 0.3, delay: 0.0, usingSpringWithDamping: 0.75, initialSpringVelocity: 1.0, options: .curveLinear, animations: { | |
self.windowOverlay.layer.backgroundColor = UIColor.clear.cgColor | |
self.layoutIfNeeded() | |
}, completion: { _ in | |
self.removeFromSuperview() | |
completion() | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment