ProHUD/Source/Alert/AlertController.swift

356 lines
11 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
static var alerts = [Alert]()
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?, title: String? = nil, message: String? = nil, actions: ((Alert) -> Void)? = nil) {
self.init()
vm.vc = self
vm.scene = scene ?? .default
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 {
willAppearCallback?()
let window = Alert.privGetAlertWindow(self)
window.makeKeyAndVisible()
window.resignKey()
window.addSubview(view)
if #available(iOS 13.0, *) {
window.windowScene = cfg.windowScene ?? .currentWindowScene
}
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)
didAppearCallback?()
}
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()
self.didDisappearCallback?()
}
// 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
}
}
// MARK:
extension Alert: LoadingRotateAnimation {}
// 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()
}
///
/// - Parameters:
/// - identifier:
/// - toast:
/// - Returns:
@discardableResult class func push(_ identifier: String, scene: ProHUD.Scene? = nil, instance: ((Alert) -> Void)? = nil) -> Alert {
if let a = find(identifier).last {
if let s = scene, s != a.vm.scene {
a.update { (vm) in
vm.scene = s
}
}
instance?(a)
return a
} else {
return Alert(scene: scene) { (aa) in
aa.identifier = identifier
instance?(aa)
}.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: @escaping (Alert) -> Void) {
if let t = find(identifier).last {
last(t)
}
}
///
/// - Parameter alert:
class func pop(_ alert: Alert) {
alert.pop()
}
///
/// - Parameter identifier:
class func pop(_ identifier: String) {
for a in find(identifier) {
a.pop()
}
}
}
// MARK: -
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.createActionButton(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 || handler == nil {
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 = .alertForProHUD
return w
}
class func privRemoveItemFromArray(alert: Alert) {
if alerts.count > 1 {
alerts.removeAll { $0 == alert }
privUpdateAlertsLayout()
} else if alerts.count == 1 {
alerts.removeAll()
} else {
debug("代码漏洞已经没有alert了")
}
}
}