Skip to content

Instantly share code, notes, and snippets.

@brocoo
Created November 14, 2016 11:49
Show Gist options
  • Save brocoo/a1a699a703b6d1c63d5854bb3b3edad7 to your computer and use it in GitHub Desktop.
Save brocoo/a1a699a703b6d1c63d5854bb3b3edad7 to your computer and use it in GitHub Desktop.
A customisable alert view class to replace the default UIAlertController (Swift3)
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