// // AlertView.swift // PaiaiUIKit // // Created by FFIB on 2017/11/14. // Copyright © 2017年 FFIB. All rights reserved. // import UIKit public final class AlertView: UIView { public static var `default`: AlertView { return AlertView() } private typealias ItemAction = ((AlertItem) -> Void) private var confirmAction: AlertAction? private var cancelAction: AlertAction? public var cancelItem: AlertItem = { let item = AlertItem(type: .custom) item.backgroundColor = UIColor(r: 214, g: 214, b: 214) item.setTitleColor(UIColor(r: 51, g: 51, b: 51), for: .normal) return item }() public var confirmItem: AlertItem = { let item = AlertItem(type: .custom) item.backgroundColor = UIColor(r: 129, g: 209, b: 53) item.setTitleColor(UIColor.white, for: .normal) return item }() public var titleLabel: UILabel = { var label = UILabel() label.numberOfLines = 0 label.textAlignment = .left label.backgroundColor = UIColor.white label.font = UIFont.systemFont(ofSize: 17) label.textColor = UIColor(r: 53, g: 53, b: 53) return label }() public var messageLabel: UILabel = { var label = UILabel() label.numberOfLines = 0 label.textAlignment = .left label.backgroundColor = UIColor.white label.font = UIFont.systemFont(ofSize: 15) label.textColor = UIColor(r: 153, g: 153, b: 153) return label }() public var contentView: UIView? private var viewNotReady = true private var bottomView: UIView? private var topView: UIView? var title: String = "" { didSet { titleLabel.text = title } } var message: String = "" { didSet { messageLabel.text = message } } override public func didMoveToWindow() { super.didMoveToWindow() guard viewNotReady else { return } constructViewHierarchy() activateConstraints() installTarget() backgroundColor = UIColor.white viewNotReady = false } private func constructViewHierarchy() { if !title.isEmpty { addSubview(titleLabel) } if !message.isEmpty { addSubview(messageLabel) } if cancelAction != nil { addSubview(cancelItem) } if confirmAction != nil { addSubview(confirmItem) } if let contentView = contentView { addSubview(contentView) } } private func activateConstraints() { activateConstraintsItems() activateConstraintsLabels() activateConstraintsContentView() activateConstraintsRootView() } func addAlertAction(_ action: AlertAction) { switch action.style { case .default: confirmAction = action case .cancel: cancelAction = action case let .custom(item): confirmAction = action confirmItem = item } } private func installTarget() { if cancelAction != nil { cancelItem.addTarget(self, action: #selector(cancelAction(btn:)), for: .touchUpInside) } if confirmAction != nil { confirmItem.addTarget(self, action: #selector(confirmAction(btn:)), for: .touchUpInside) } } @objc private func confirmAction(btn: UIButton) { confirmAction?.handler?(confirmItem) dismissSuperViewController() } @objc private func cancelAction(btn: UIButton) { cancelAction?.handler?(cancelItem) dismissSuperViewController() } private func dismissSuperViewController() { guard let vc = getSuperViewController() else { return } vc.dismissController() } } /// MARK: layout fileprivate extension AlertView { func activateConstraintsRootView() { guard let v = superview else { return } translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ centerYAnchor.constraint(equalTo: v.centerYAnchor), centerXAnchor.constraint(equalTo: v.centerXAnchor), leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 40), trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -40), ]) if #available(iOS 11, *) { directionalLayoutMargins = NSDirectionalEdgeInsets(top: 10, leading: 16, bottom: 0, trailing: 16) } else { layoutMargins = UIEdgeInsets(top: 10, left: 16, bottom: 0, right: 16) } guard let topView = topView else { return } NSLayoutConstraint.activate([ topView.bottomAnchor.constraint(equalTo: bottomView?.topAnchor ?? bottomAnchor, constant: -12) ]) } func activateConstraintsContentView() { guard let contentView = contentView else { return } contentView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ contentView.leadingAnchor.constraint(equalTo: leadingAnchor), contentView.trailingAnchor.constraint(equalTo: trailingAnchor), contentView.topAnchor.constraint(equalTo: topView?.bottomAnchor ?? topAnchor), contentView.bottomAnchor.constraint(equalTo: bottomView?.bottomAnchor ?? bottomAnchor) ]) bottomView = contentView } func activateConstraintsItems() { let (alertActions, items) = getActionsAndItems() guard !alertActions.isEmpty, let width = superview?.bounds.width else { return } /// center view spacing is 80 /// margins 32 let spacing: CGFloat = 80 + 32 let itemSpacing: CGFloat = CGFloat((items.count - 1) * 6) let w = (width - spacing - itemSpacing) / CGFloat(items.count) var last: AlertItem? = nil var leading: CGFloat = 0 for (action, item) in zip(alertActions, items) { item.translatesAutoresizingMaskIntoConstraints = false item.setTitle(action.title, for: .normal) NSLayoutConstraint.activate([ item.widthAnchor.constraint(equalToConstant: w), item.heightAnchor.constraint(equalToConstant: 36), item.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -14), item.leadingAnchor.constraint(equalTo: last?.trailingAnchor ?? layoutMarginsGuide.leadingAnchor, constant: leading) ]) leading = 6 last = item } bottomView = last } func activateConstraintsLabels() { var labels: [UILabel] = [] if !title.isEmpty { labels.append(titleLabel) } if !message.isEmpty { labels.append(messageLabel) } if labels.isEmpty { return } var last: UILabel? = nil for label in labels { label.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), label.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), label.topAnchor.constraint(equalTo: last?.bottomAnchor ?? layoutMarginsGuide.topAnchor, constant: 12), ]) last = label } topView = last } } fileprivate extension AlertView { func getActionsAndItems() -> ([AlertAction], [AlertItem]) { let alertActions = [cancelAction, confirmAction].compactMap { $0 } let items = alertActions.map { (action) -> AlertItem in switch action.style { case .cancel: return cancelItem case .default, .custom: return confirmItem } } return (alertActions, items) } }