ProHUD/Source/Alert/AlertController.swift

356 lines
11 KiB
Swift
Raw Permalink Normal View History

2019-07-31 17:40:39 +08:00
//
// Alert.swift
// ProHUD
//
// Created by xaoxuu on 2019/7/23.
// Copyright © 2019 Titan Studio. All rights reserved.
//
import UIKit
import SnapKit
2019-08-08 11:09:22 +08:00
public typealias Alert = ProHUD.Alert
2019-07-31 17:40:39 +08:00
public extension ProHUD {
class Alert: HUDController {
2021-07-20 16:04:39 +08:00
static var alerts = [Alert]()
2019-08-08 11:09:22 +08:00
2021-07-20 16:04:39 +08:00
static var alertWindow: UIWindow?
2019-08-08 11:09:22 +08:00
2019-08-05 11:18:25 +08:00
///
2019-08-12 15:02:36 +08:00
public var contentView = createBlurView()
2019-07-31 17:40:39 +08:00
2019-08-05 11:18:25 +08:00
/// icontextStackactionStack)
public var contentStack: StackContainer = {
2019-07-31 17:40:39 +08:00
let stack = StackContainer()
2019-08-03 17:48:37 +08:00
stack.spacing = cfg.alert.margin
2019-07-31 17:40:39 +08:00
return stack
}()
2019-08-05 11:18:25 +08:00
///
public var textStack: StackContainer = {
2019-07-31 17:40:39 +08:00
let stack = StackContainer()
2019-08-03 17:48:37 +08:00
stack.spacing = cfg.alert.margin
2019-07-31 17:40:39 +08:00
return stack
}()
2019-08-05 11:18:25 +08:00
///
public var imageView = UIImageView()
2019-08-05 11:18:25 +08:00
///
public var titleLabel: UILabel?
///
public var bodyLabel: UILabel?
2019-07-31 17:40:39 +08:00
///
2019-08-05 11:18:25 +08:00
public var actionStack: StackContainer = {
2019-07-31 17:40:39 +08:00
let stack = StackContainer()
stack.alignment = .fill
2019-08-03 17:48:37 +08:00
stack.spacing = cfg.alert.margin
2019-07-31 17:40:39 +08:00
return stack
}()
///
2019-08-09 18:02:41 +08:00
public var vm = ViewModel()
2019-07-31 21:08:25 +08:00
2019-07-31 17:40:39 +08:00
// MARK:
2019-08-13 09:14:30 +08:00
private var isLoadFinished = false
2019-07-31 17:40:39 +08:00
///
/// - Parameter scene:
/// - Parameter title:
/// - Parameter message:
/// - Parameter icon:
2020-06-23 20:16:12 +08:00
public convenience init(scene: Scene?, title: String? = nil, message: String? = nil, actions: ((Alert) -> Void)? = nil) {
2019-07-31 17:40:39 +08:00
self.init()
2019-08-09 18:02:41 +08:00
vm.vc = self
2020-06-23 20:16:12 +08:00
vm.scene = scene ?? .default
2019-08-09 18:02:41 +08:00
vm.title = title
vm.message = message
2019-08-12 20:20:07 +08:00
actions?(self)
2019-07-31 17:40:39 +08:00
}
2019-08-09 18:02:41 +08:00
public override func viewDidLoad() {
super.viewDidLoad()
view.tintColor = cfg.alert.tintColor
cfg.alert.reloadData(self)
isLoadFinished = true
}
2019-07-31 17:40:39 +08:00
2019-08-05 11:18:25 +08:00
}
}
// MARK: -
2019-08-08 11:09:22 +08:00
public extension Alert {
2019-08-05 11:18:25 +08:00
///
2019-08-08 11:09:22 +08:00
@discardableResult func push() -> Alert {
if Alert.alerts.contains(self) == false {
2020-06-22 13:27:54 +08:00
willAppearCallback?()
2019-08-12 15:02:36 +08:00
let window = Alert.privGetAlertWindow(self)
2019-08-08 11:09:22 +08:00
window.makeKeyAndVisible()
window.resignKey()
window.addSubview(view)
2020-06-15 15:11:44 +08:00
if #available(iOS 13.0, *) {
2021-07-20 16:04:39 +08:00
window.windowScene = cfg.windowScene ?? .currentWindowScene
2020-06-15 15:11:44 +08:00
}
2019-08-08 11:09:22 +08:00
view.transform = .init(scaleX: 1.2, y: 1.2)
view.alpha = 0
UIView.animateForAlertBuildIn {
self.view.transform = .identity
self.view.alpha = 1
window.backgroundColor = window.backgroundColor?.withAlphaComponent(0.6)
}
Alert.alerts.append(self)
2020-06-22 13:27:54 +08:00
didAppearCallback?()
2019-08-08 11:09:22 +08:00
}
2019-08-12 15:02:36 +08:00
Alert.privUpdateAlertsLayout()
2019-08-08 11:09:22 +08:00
return self
2019-08-05 11:18:25 +08:00
}
///
func pop() {
2019-08-12 19:02:33 +08:00
willDisappearCallback?()
2019-08-12 15:02:36 +08:00
let window = Alert.privGetAlertWindow(self)
Alert.privRemoveItemFromArray(alert: self)
2019-08-05 11:18:25 +08:00
UIView.animateForAlertBuildOut(animations: {
self.view.alpha = 0
self.view.transform = .init(scaleX: 1.08, y: 1.08)
}) { (done) in
self.view.removeFromSuperview()
self.removeFromParent()
2020-06-22 13:27:54 +08:00
self.didDisappearCallback?()
2019-08-05 11:18:25 +08:00
}
// hide window
2019-08-08 11:09:22 +08:00
let count = Alert.alerts.count
if count == 0 && Alert.alertWindow != nil {
2019-08-02 13:55:23 +08:00
UIView.animateForAlertBuildOut(animations: {
2019-08-05 11:18:25 +08:00
window.backgroundColor = window.backgroundColor?.withAlphaComponent(0)
2019-08-02 13:55:23 +08:00
}) { (done) in
2019-08-12 17:59:40 +08:00
if Alert.alerts.count == 0 {
Alert.alertWindow = nil
}
2019-08-03 16:22:50 +08:00
}
2019-07-31 17:40:39 +08:00
}
2019-08-05 11:18:25 +08:00
}
2019-08-12 15:02:36 +08:00
///
/// - Parameter callback:
func update(_ callback: ((inout ViewModel) -> Void)? = nil) {
callback?(&vm)
cfg.alert.reloadData(self)
}
2019-08-05 11:18:25 +08:00
2020-06-23 20:16:12 +08:00
///
2019-08-05 11:18:25 +08:00
/// - Parameter callback:
2019-08-12 15:02:36 +08:00
func didForceQuit(_ callback: (() -> Void)?) {
2019-08-09 18:02:41 +08:00
vm.forceQuitCallback = callback
2019-08-05 11:18:25 +08:00
}
2019-07-31 17:40:39 +08:00
}
2020-06-22 13:27:54 +08:00
// MARK:
extension Alert: LoadingRotateAnimation {}
2019-07-31 17:40:39 +08:00
2019-08-12 15:02:36 +08:00
// MARK: -
2019-08-08 11:09:22 +08:00
public extension Alert {
2019-07-31 17:40:39 +08:00
2019-08-05 11:18:25 +08:00
///
/// - Parameter alert:
/// - Parameter title:
/// - Parameter message:
2019-08-08 11:09:22 +08:00
/// - Parameter actions:
2019-08-13 11:32:27 +08:00
@discardableResult class func push(scene: ProHUD.Scene = .default, title: String? = nil, message: String? = nil, _ actions: ((Alert) -> Void)? = nil) -> Alert {
2019-08-08 11:09:22 +08:00
return Alert(scene: scene, title: title, message: message, actions: actions).push()
2019-07-31 17:40:39 +08:00
}
///
/// - Parameters:
/// - identifier:
/// - toast:
/// - Returns:
2020-06-23 20:16:12 +08:00
@discardableResult class func push(_ identifier: String, scene: ProHUD.Scene? = nil, instance: ((Alert) -> Void)? = nil) -> Alert {
if let a = find(identifier).last {
2020-06-23 20:16:12 +08:00
if let s = scene, s != a.vm.scene {
a.update { (vm) in
vm.scene = s
}
}
instance?(a)
return a
} else {
2020-06-23 20:16:12 +08:00
return Alert(scene: scene) { (aa) in
aa.identifier = identifier
2020-06-23 20:16:12 +08:00
instance?(aa)
}.push()
}
}
2019-08-13 09:14:30 +08:00
///
2019-08-05 11:18:25 +08:00
/// - Parameter identifier:
class func find(_ identifier: String) -> [Alert] {
2019-07-31 17:40:39 +08:00
var aa = [Alert]()
2019-08-08 11:09:22 +08:00
for a in Alert.alerts {
2019-08-12 20:20:07 +08:00
if a.identifier == identifier {
2019-07-31 17:40:39 +08:00
aa.append(a)
}
}
2019-08-08 11:09:22 +08:00
return aa
2019-07-31 17:40:39 +08:00
}
2019-08-12 20:20:07 +08:00
///
/// - Parameter identifier:
/// - Parameter last:
/// - Parameter none:
class func find(_ identifier: String, last: @escaping (Alert) -> Void) {
2019-08-12 20:20:07 +08:00
if let t = find(identifier).last {
last(t)
2019-08-12 20:20:07 +08:00
}
}
2019-08-05 11:18:25 +08:00
///
/// - Parameter alert:
2019-08-05 19:14:25 +08:00
class func pop(_ alert: Alert) {
2019-08-08 11:09:22 +08:00
alert.pop()
2019-07-31 17:40:39 +08:00
}
2019-08-08 11:09:22 +08:00
///
2019-08-05 11:18:25 +08:00
/// - Parameter identifier:
class func pop(_ identifier: String) {
2019-08-12 20:20:07 +08:00
for a in find(identifier) {
2019-08-08 11:09:22 +08:00
a.pop()
}
2019-07-31 17:40:39 +08:00
}
}
2019-08-05 11:18:25 +08:00
2019-08-12 15:02:36 +08:00
// MARK: -
2021-07-20 16:04:39 +08:00
extension Alert {
2019-08-05 11:18:25 +08:00
2019-08-12 20:20:07 +08:00
///
2019-08-12 15:02:36 +08:00
/// - Parameter style:
/// - Parameter title:
/// - Parameter action:
@discardableResult func insert(action index: Int?, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
2020-06-22 13:27:54 +08:00
let btn = Button.createActionButton(title: title)
2019-08-12 15:02:36 +08:00
if let idx = index, idx < actionStack.arrangedSubviews.count {
actionStack.insertArrangedSubview(btn, at: idx)
} else {
actionStack.addArrangedSubview(btn)
}
btn.update(style: style)
2019-08-05 11:18:25 +08:00
if actionStack.superview == nil {
contentStack.addArrangedSubview(actionStack)
2019-08-09 18:02:41 +08:00
contentStack.layoutIfNeeded()
2019-08-05 11:18:25 +08:00
}
2019-08-12 15:02:36 +08:00
addTouchUpAction(for: btn) { [weak self] in
2019-08-09 18:02:41 +08:00
handler?()
if btn.tag == UIAlertAction.Style.cancel.rawValue || handler == nil {
2019-08-05 11:18:25 +08:00
self?.pop()
}
}
2019-08-09 18:02:41 +08:00
if isLoadFinished {
actionStack.layoutIfNeeded()
UIView.animateForAlert {
self.view.layoutIfNeeded()
}
}
2019-08-12 15:02:36 +08:00
return btn
2019-08-05 11:18:25 +08:00
}
2019-08-12 20:20:07 +08:00
///
/// - Parameter index:
/// - Parameter style:
/// - Parameter title:
/// - Parameter handler:
2019-08-12 15:02:36 +08:00
func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) {
2019-08-09 18:02:41 +08:00
if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton {
btn.setTitle(title, for: .normal)
if let b = btn as? Button {
b.update(style: style)
}
if let _ = buttonEvents[btn] {
buttonEvents.removeValue(forKey: btn)
}
addTouchUpAction(for: btn) { [weak self] in
handler?()
if btn.tag == UIAlertAction.Style.cancel.rawValue {
self?.pop()
}
}
}
}
2019-08-12 15:02:36 +08:00
///
/// - Parameter index:
@discardableResult func remove(action index: Int) -> Alert {
if index < 0 {
for view in self.actionStack.arrangedSubviews {
if let btn = view as? UIButton {
btn.removeFromSuperview()
2019-08-12 17:59:40 +08:00
if let _ = buttonEvents[btn] {
buttonEvents.removeValue(forKey: btn)
}
2019-08-12 15:02:36 +08:00
}
}
} else if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton {
btn.removeFromSuperview()
2019-08-12 17:59:40 +08:00
if let _ = buttonEvents[btn] {
buttonEvents.removeValue(forKey: btn)
}
2019-08-12 15:02:36 +08:00
}
if self.actionStack.arrangedSubviews.count == 0 {
self.actionStack.removeFromSuperview()
}
UIView.animateForAlert {
self.view.layoutIfNeeded()
}
return self
}
2019-08-05 11:18:25 +08:00
}
2019-08-08 11:09:22 +08:00
fileprivate extension Alert {
2019-08-12 15:02:36 +08:00
class func privUpdateAlertsLayout() {
2019-07-31 17:40:39 +08:00
for (i, a) in alerts.reversed().enumerated() {
let scale = CGFloat(pow(0.7, CGFloat(i)))
2019-08-02 13:55:23 +08:00
UIView.animate(withDuration: 1.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseInOut], animations: {
2019-07-31 17:40:39 +08:00
let y = -50 * CGFloat(i) * CGFloat(pow(0.8, CGFloat(i)))
a.view.transform = CGAffineTransform.init(translationX: 0, y: y).scaledBy(x: scale, y: scale)
}) { (done) in
}
}
}
2019-08-12 15:02:36 +08:00
class func privGetAlertWindow(_ vc: UIViewController) -> UIWindow {
2019-07-31 17:40:39 +08:00
if let w = alertWindow {
return w
}
let w = UIWindow()
alertWindow = w
w.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0)
// alert
2021-07-20 16:04:39 +08:00
w.windowLevel = .alertForProHUD
2019-07-31 17:40:39 +08:00
return w
}
2019-08-12 15:02:36 +08:00
class func privRemoveItemFromArray(alert: Alert) {
2019-07-31 17:40:39 +08:00
if alerts.count > 1 {
2021-07-20 16:04:39 +08:00
alerts.removeAll { $0 == alert }
2019-08-12 15:02:36 +08:00
privUpdateAlertsLayout()
2019-07-31 17:40:39 +08:00
} else if alerts.count == 1 {
alerts.removeAll()
} else {
debug("代码漏洞已经没有alert了")
2019-07-31 17:40:39 +08:00
}
}
}