ProHUD/Source/Alert/AlertController.swift

346 lines
11 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Alert.swift
// ProHUD
//
// Created by xaoxuu on 2019/7/23.
// Copyright © 2019 Titan Studio. All rights reserved.
//
import UIKit
import SnapKit
public typealias Alert = ProHUD.Alert
public extension ProHUD {
class Alert: HUDController {
internal static var alerts = [Alert]()
internal static var alertWindow: UIWindow?
///
public var contentView = createBlurView()
/// icontextStackactionStack)
public var contentStack: StackContainer = {
let stack = StackContainer()
stack.spacing = cfg.alert.margin
return stack
}()
///
public var textStack: StackContainer = {
let stack = StackContainer()
stack.spacing = cfg.alert.margin
return stack
}()
///
public var imageView: UIImageView?
///
public var titleLabel: UILabel?
///
public var bodyLabel: UILabel?
///
public var actionStack: StackContainer = {
let stack = StackContainer()
stack.alignment = .fill
stack.spacing = cfg.alert.margin
return stack
}()
///
public var vm = ViewModel()
// MARK:
private var isLoadFinished = false
///
/// - Parameter scene:
/// - Parameter title:
/// - Parameter message:
/// - Parameter icon:
public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, actions: ((Alert) -> Void)? = nil) {
self.init()
vm.vc = self
vm.scene = scene
vm.title = title
vm.message = message
actions?(self)
}
public override func viewDidLoad() {
super.viewDidLoad()
view.tintColor = cfg.alert.tintColor
cfg.alert.reloadData(self)
isLoadFinished = true
}
}
}
// MARK: -
public extension Alert {
///
@discardableResult func push() -> Alert {
if Alert.alerts.contains(self) == false {
let window = Alert.privGetAlertWindow(self)
window.makeKeyAndVisible()
window.resignKey()
window.addSubview(view)
if #available(iOS 13.0, *) {
window.windowScene = cfg.windowScene
} else {
// Fallback on earlier versions
}
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)
}
Alert.privUpdateAlertsLayout()
return self
}
///
func pop() {
willDisappearCallback?()
let window = Alert.privGetAlertWindow(self)
Alert.privRemoveItemFromArray(alert: self)
UIView.animateForAlertBuildOut(animations: {
self.view.alpha = 0
self.view.transform = .init(scaleX: 1.08, y: 1.08)
}) { (done) in
self.view.removeFromSuperview()
self.removeFromParent()
}
// hide window
let count = Alert.alerts.count
if count == 0 && Alert.alertWindow != nil {
UIView.animateForAlertBuildOut(animations: {
window.backgroundColor = window.backgroundColor?.withAlphaComponent(0)
}) { (done) in
if Alert.alerts.count == 0 {
Alert.alertWindow = nil
}
}
}
}
///
/// - Parameter callback:
func update(_ callback: ((inout ViewModel) -> Void)? = nil) {
callback?(&vm)
cfg.alert.reloadData(self)
}
///
/// - Parameter callback:
func didForceQuit(_ callback: (() -> Void)?) {
vm.forceQuitCallback = callback
}
///
/// - Parameter flag:
func rotate(_ flag: Bool = true, direction: ProHUD.RotateDirection = .clockwise, speed: CFTimeInterval = 1) {
DispatchQueue.main.async {
self.imageView?.rotate(flag: flag, direction: direction, speed: speed)
}
}
}
// MARK: -
public extension Alert {
///
/// - Parameter alert:
/// - Parameter title:
/// - Parameter message:
/// - Parameter actions:
@discardableResult class func push(scene: ProHUD.Scene = .default, title: String? = nil, message: String? = nil, _ actions: ((Alert) -> Void)? = nil) -> Alert {
return Alert(scene: scene, title: title, message: message, actions: actions).push()
}
///
/// - Parameter identifier:
class func find(_ identifier: String?) -> [Alert] {
var aa = [Alert]()
for a in Alert.alerts {
if a.identifier == identifier {
aa.append(a)
}
}
return aa
}
///
/// - Parameter identifier:
/// - Parameter last:
/// - Parameter none:
class func find(_ identifier: String?, last: ((Alert) -> Void)? = nil, none: (() -> Void)? = nil) {
if let t = find(identifier).last {
last?(t)
} else {
none?()
}
}
///
/// - Parameter alert:
class func pop(_ alert: Alert) {
alert.pop()
}
///
/// - Parameter identifier:
class func pop(_ identifier: String?) {
for a in find(identifier) {
a.pop()
}
}
}
// MARK: -
internal extension Alert {
///
/// - Parameter style:
/// - Parameter title:
/// - Parameter action:
@discardableResult func insert(action index: Int?, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
let btn = Button.actionButton(title: title)
if let idx = index, idx < actionStack.arrangedSubviews.count {
actionStack.insertArrangedSubview(btn, at: idx)
} else {
actionStack.addArrangedSubview(btn)
}
btn.update(style: style)
if actionStack.superview == nil {
contentStack.addArrangedSubview(actionStack)
contentStack.layoutIfNeeded()
}
addTouchUpAction(for: btn) { [weak self] in
handler?()
if btn.tag == UIAlertAction.Style.cancel.rawValue {
self?.pop()
}
}
if isLoadFinished {
actionStack.layoutIfNeeded()
UIView.animateForAlert {
self.view.layoutIfNeeded()
}
}
return btn
}
///
/// - Parameter index:
/// - Parameter style:
/// - Parameter title:
/// - Parameter handler:
func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) {
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()
}
}
}
}
///
/// - 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()
if let _ = buttonEvents[btn] {
buttonEvents.removeValue(forKey: btn)
}
}
}
} else if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton {
btn.removeFromSuperview()
if let _ = buttonEvents[btn] {
buttonEvents.removeValue(forKey: btn)
}
}
if self.actionStack.arrangedSubviews.count == 0 {
self.actionStack.removeFromSuperview()
}
UIView.animateForAlert {
self.view.layoutIfNeeded()
}
return self
}
}
fileprivate extension Alert {
class func privUpdateAlertsLayout() {
for (i, a) in alerts.reversed().enumerated() {
let scale = CGFloat(pow(0.7, CGFloat(i)))
UIView.animate(withDuration: 1.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseInOut], animations: {
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
}
}
}
class func privGetAlertWindow(_ vc: UIViewController) -> UIWindow {
if let w = alertWindow {
return w
}
let w = UIWindow()
alertWindow = w
w.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0)
// alert
w.windowLevel = UIWindow.Level.alert - 1
return w
}
class func privRemoveItemFromArray(alert: Alert) {
if alerts.count > 1 {
for (i, a) in alerts.enumerated() {
if a == alert {
if i < alerts.count {
alerts.remove(at: i)
}
}
}
privUpdateAlertsLayout()
} else if alerts.count == 1 {
alerts.removeAll()
} else {
debug("漏洞已经没有alert了")
}
}
}