diff --git a/Example/Example/AppDelegate.swift b/Example/Example/AppDelegate.swift index ad6aca4..dd25882 100644 --- a/Example/Example/AppDelegate.swift +++ b/Example/Example/AppDelegate.swift @@ -22,20 +22,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { - hud.config(alert: { (a) in -// a.tintColor = .green -// a.loadSubviews { (aa) in -// -// } -// a.padding = 12 -// a.margin = 4 -// a.loadSubviews { (aa) in -// -// } - }) - hud.config(toast: { (a) in - - }) + + return true } diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 03bd703..54dcf0f 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -16,29 +16,53 @@ class ViewController: UIViewController { // Do any additional setup after loading the view. + ProHUD.configAlert { (alert) in + alert.minimizeTimeout = 1 + } + } override func touchesBegan(_ touches: Set, with event: UIEvent?) { - let a = ProHUD.show(alert: .delete, title: "确认删除", message: "此操作不可撤销") - - a.addAction(style: .destructive, title: "删除") { [weak a] in - a?.updateContent(scene: .success, title: "操作成功", message: "恭喜,您已经成功删除了xxx") - a?.updateAction(index: 0, style: .default, title: "好的", action: { - a?.remove() - }).removeAction(index: 1) -// a?.updateContent(scene: .success, title: "操作成功").removeAction(index: -1).timeout(2) - }.addAction(style: .cancel, title: "取消", action: nil).didDisappear { - debugPrint("didDisappear") - } - +// let a = ProHUD.show(alert: .delete, title: "确认删除", message: "此操作不可撤销") +// +// a.addAction(style: .destructive, title: "删除") { [weak a] in +// a?.updateContent(scene: .success, title: "操作成功", message: "恭喜,您已经成功删除了xxx") +// a?.updateAction(index: 0, style: .default, title: "好的", action: { +// a?.remove() +// }).removeAction(index: 1) +//// a?.updateContent(scene: .success, title: "操作成功").removeAction(index: -1).timeout(2) +// }.addAction(style: .cancel, title: "取消", action: nil).didDisappear { +// debugPrint("didDisappear") +// } +// // ProHUD.show(alert: .delete, title: "克里斯蒂娜删除", message: "克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞").addAction(style: .destructive, title: "删除") { // // }.addAction(style: .cancel, title: "取消", action: nil).didDisappear { // debugPrint("didDisappear") // } + let t = ProHUD.Toast(scene: .loading, title: "正在加载", message: "哈哈") + let a = ProHUD.show(alert: .loading, title: "正在加载", message: "请坐和放宽") + a.didMinimize { + hud.show(t) + } + t.didTapped { [weak t] in + hud.show(a) + t?.remove() + } + +// a.didMinimize { +// ProHUD.show(toast: .loading, title: "正在加载", message: "哈哈").didTapped { +// ProHUD.show(toast: .loading, title: "正在加载", message: "哈克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞哈克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞") +// } +// } + + + + + DispatchQueue.main.asyncAfter(deadline: .now()+1) { diff --git a/ProHUD/Alert/AlertConfig.swift b/ProHUD/Alert/AlertConfig.swift index 1ad306c..a2ad097 100644 --- a/ProHUD/Alert/AlertConfig.swift +++ b/ProHUD/Alert/AlertConfig.swift @@ -35,8 +35,9 @@ public extension ProHUD.Configuration { public var iconSize = CGSize(width: 48, height: 48) public var tintColor = UIColor.init(red: 3/255, green: 169/255, blue: 244/255, alpha: 1) - /// 多少秒后显示强制退出的按钮【终止/隐藏】 - public var forceQuitTimeout = TimeInterval(2) + + /// 多少秒后显示最小化的按钮 + public var minimizeTimeout = TimeInterval(10) /// 加载视图 lazy var loadSubviews: (ProHUD.Alert) -> Void = { @@ -45,22 +46,21 @@ public extension ProHUD.Configuration { vc.view.addSubview(vc.contentView) vc.contentView.contentView.addSubview(vc.contentStack) - let config = hud.config.alert - vc.contentStack.spacing = config.margin + config.padding + vc.contentStack.spacing = alertConfig.margin + alertConfig.padding vc.contentView.layer.masksToBounds = true - vc.contentView.layer.cornerRadius = config.cornerRadius + vc.contentView.layer.cornerRadius = alertConfig.cornerRadius vc.contentView.snp.makeConstraints { (mk) in mk.center.equalToSuperview() - mk.width.lessThanOrEqualTo(CGFloat.minimum(UIScreen.main.bounds.width * 0.68, config.maxWidth)) + mk.width.lessThanOrEqualTo(CGFloat.minimum(UIScreen.main.bounds.width * 0.68, alertConfig.maxWidth)) } vc.contentStack.snp.makeConstraints { (mk) in mk.centerX.equalToSuperview() - mk.top.equalToSuperview().offset(config.padding) - mk.bottom.equalToSuperview().offset(-config.padding) - mk.leading.equalToSuperview().offset(config.padding) - mk.trailing.equalToSuperview().offset(-config.padding) + mk.top.equalToSuperview().offset(alertConfig.padding) + mk.bottom.equalToSuperview().offset(-alertConfig.padding) + mk.leading.equalToSuperview().offset(alertConfig.padding) + mk.trailing.equalToSuperview().offset(-alertConfig.padding) } } @@ -70,7 +70,6 @@ public extension ProHUD.Configuration { lazy var updateFrame: (ProHUD.Alert) -> Void = { return { (vc) in debugPrint(vc, "updateFrame") - let config = hud.config.alert let isFirstLayout: Bool // 图标和文字至少有一个,如果都没有添加到视图中,说明是第一次layout if vc.textStack.superview == nil && vc.imageView?.superview == nil { @@ -102,10 +101,10 @@ public extension ProHUD.Configuration { let icon = UIImageView(image: img) vc.contentStack.addArrangedSubview(icon) icon.snp.makeConstraints { (mk) in - mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding*2.5) - mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2.5) - mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding*4) - mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*4) + mk.top.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*2.5) + mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*2.5) + mk.leading.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*4) + mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*4) } vc.imageView = icon } @@ -114,10 +113,10 @@ public extension ProHUD.Configuration { if vc.vm.title?.count ?? 0 > 0 || vc.vm.message?.count ?? 0 > 0 { vc.contentStack.addArrangedSubview(vc.textStack) vc.textStack.snp.makeConstraints { (mk) in - mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding*1.5) - mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding*1.5) - mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding*2) - mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2) + mk.top.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*1.5) + mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*1.5) + mk.leading.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*2) + mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*2) } if vc.vm.title?.count ?? 0 > 0 { if let lb = vc.titleLabel { @@ -125,7 +124,7 @@ public extension ProHUD.Configuration { } else { let title = UILabel() title.textAlignment = .center - title.numberOfLines = config.titleMaxLines + title.numberOfLines = alertConfig.titleMaxLines title.textColor = UIColor.init(white: 0.2, alpha: 1) title.text = vc.vm.title vc.textStack.addArrangedSubview(title) @@ -133,10 +132,10 @@ public extension ProHUD.Configuration { } if vc.vm.message?.count ?? 0 > 0 { // 有message - vc.titleLabel?.font = config.largeTitleFont + vc.titleLabel?.font = alertConfig.largeTitleFont } else { // 没有message - vc.titleLabel?.font = config.titleFont + vc.titleLabel?.font = alertConfig.titleFont } } else { vc.titleLabel?.removeFromSuperview() @@ -147,8 +146,8 @@ public extension ProHUD.Configuration { } else { let body = UILabel() body.textAlignment = .center - body.font = config.bodyFont - body.numberOfLines = config.bodyMaxLines + body.font = alertConfig.bodyFont + body.numberOfLines = alertConfig.bodyMaxLines body.textColor = UIColor.darkGray body.text = vc.vm.message vc.textStack.addArrangedSubview(body) @@ -156,10 +155,10 @@ public extension ProHUD.Configuration { } if vc.vm.title?.count ?? 0 > 0 { // 有title - vc.messageLabel?.font = config.bodyFont + vc.messageLabel?.font = alertConfig.bodyFont } else { // 没有title - vc.messageLabel?.font = config.titleFont + vc.messageLabel?.font = alertConfig.titleFont } } else { vc.messageLabel?.removeFromSuperview() @@ -202,10 +201,11 @@ public extension ProHUD.Configuration { let btn = UIButton.hideButton() vc.view.addSubview(btn) btn.snp.makeConstraints { (mk) in - mk.leading.top.equalTo(vc.contentView).offset(hud.config.alert.margin/2) + mk.leading.top.equalTo(vc.contentView).offset(alertConfig.margin/2) } vc.addTouchUpAction(for: btn) { [weak vc] in debugPrint("点击了隐藏") + vc?.minimizeCallback?() vc?.remove() } } @@ -225,3 +225,11 @@ public extension ProHUD.Configuration { } } + +internal var alertConfig = ProHUD.Configuration.Alert() + +public extension ProHUD { + static func configAlert(_ config: (inout Configuration.Alert) -> Void) { + config(&alertConfig) + } +} diff --git a/ProHUD/Alert/AlertController.swift b/ProHUD/Alert/AlertController.swift index 0f91509..c3047c0 100644 --- a/ProHUD/Alert/AlertController.swift +++ b/ProHUD/Alert/AlertController.swift @@ -18,12 +18,14 @@ public extension ProHUD { /// 正文(包括icon、textStack、actionStack) internal var contentStack: StackContainer = { let stack = StackContainer() + stack.spacing = alertConfig.margin return stack }() /// 文本区域 internal var textStack: StackContainer = { let stack = StackContainer() + stack.spacing = alertConfig.margin return stack }() internal var imageView: UIImageView? @@ -33,6 +35,7 @@ public extension ProHUD { internal var actionStack: StackContainer = { let stack = StackContainer() stack.alignment = .fill + stack.spacing = alertConfig.margin return stack }() @@ -42,6 +45,8 @@ public extension ProHUD { /// 显示顶部按钮(最小化) internal var showNavButtonsBlock: DispatchWorkItem? + internal var minimizeCallback: (() -> Void)? + // MARK: 生命周期 /// 实例化 @@ -51,7 +56,7 @@ public extension ProHUD { /// - Parameter icon: 图标 public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil) { self.init() - view.tintColor = hud.config.alert.tintColor + view.tintColor = alertConfig.tintColor vm.scene = scene vm.title = title vm.message = message @@ -85,7 +90,7 @@ public extension ProHUD { let count = hud.alerts.count if count == 0 && hud.alertWindow != nil { UIView.animateFastEaseOut(delay: 0, animations: { - self.view.transform = .init(scaleX: 1.02, y: 1.02) + self.view.transform = .init(scaleX: 1.05, y: 1.05) window.backgroundColor = window.backgroundColor?.withAlphaComponent(0) }) { (done) in hud.alertWindow = nil @@ -130,6 +135,13 @@ public extension ProHUD { return self } + /// 最小化事件 + /// - Parameter callback: 事件回调 + @discardableResult public func didMinimize(_ callback: (() -> Void)?) -> Alert { + minimizeCallback = callback + return self + } + /// 消失事件 /// - Parameter callback: 事件回调 @discardableResult public func didDisappear(_ callback: (() -> Void)?) -> Alert { @@ -144,7 +156,7 @@ public extension ProHUD { vm.message = message vm.scene = scene vm.icon = icon - hud.config.alert.updateFrame(self) + alertConfig.updateFrame(self) return self } @@ -206,8 +218,8 @@ fileprivate extension ProHUD.Alert { willLayout = DispatchWorkItem(block: { [weak self] in if let a = self { // 布局 - hud.config.alert.loadSubviews(a) - hud.config.alert.updateFrame(a) + alertConfig.loadSubviews(a) + alertConfig.updateFrame(a) // 超时 a.timeoutBlock?.cancel() if let t = a.timeout, t > 0 { @@ -219,14 +231,14 @@ fileprivate extension ProHUD.Alert { a.timeoutBlock = nil } // 顶部按钮 - if hud.config.alert.forceQuitTimeout > 0 && self?.actionStack.superview == nil { + if alertConfig.minimizeTimeout > 0 && self?.actionStack.superview == nil { a.showNavButtonsBlock?.cancel() a.showNavButtonsBlock = DispatchWorkItem(block: { [weak self] in if let s = self { - hud.config.alert.showNavButtons(s) + alertConfig.showNavButtons(s) } }) - DispatchQueue.main.asyncAfter(deadline: .now()+hud.config.alert.forceQuitTimeout, execute: a.showNavButtonsBlock!) + DispatchQueue.main.asyncAfter(deadline: .now()+alertConfig.minimizeTimeout, execute: a.showNavButtonsBlock!) } } }) @@ -257,7 +269,7 @@ public extension ProHUD { alert.view.alpha = 1 }) alerts.append(alert) - updateAlertsScale() + updateAlertsLayout() // setup timeout if let _ = alert.timeout, alert.timeoutBlock == nil { alert.timeout(alert.timeout) @@ -302,6 +314,11 @@ public extension ProHUD { public extension ProHUD { + @discardableResult + class func show(_ alert: Alert) -> Alert { + return shared.show(alert) + } + @discardableResult class func show(alert: Alert.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Alert { return shared.show(alert: alert, title: title, message: message, icon: icon) @@ -325,7 +342,7 @@ public extension ProHUD { fileprivate extension ProHUD { - func updateAlertsScale() { + func updateAlertsLayout() { for (i, a) in alerts.reversed().enumerated() { let scale = CGFloat(pow(0.7, CGFloat(i))) UIView.animate(withDuration: 2, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseInOut], animations: { @@ -356,7 +373,7 @@ fileprivate extension ProHUD { alerts.remove(at: i) } } - updateAlertsScale() + updateAlertsLayout() } else if alerts.count == 1 { alerts.removeAll() } else { diff --git a/ProHUD/Alert/AlertModel.swift b/ProHUD/Alert/AlertModel.swift index 496ce99..01d64ef 100644 --- a/ProHUD/Alert/AlertModel.swift +++ b/ProHUD/Alert/AlertModel.swift @@ -6,8 +6,6 @@ // Copyright © 2019 Titan Studio. All rights reserved. // -import UIKit - public extension ProHUD.Alert { enum Scene { /// 默认场景 diff --git a/ProHUD/Alert/AlertView.swift b/ProHUD/Alert/AlertView.swift index 0051364..70eace7 100644 --- a/ProHUD/Alert/AlertView.swift +++ b/ProHUD/Alert/AlertView.swift @@ -33,14 +33,14 @@ extension UIButton { class func actionButton(style: UIAlertAction.Style, title: String?) -> UIButton { let btn = UIButton(type: .system) btn.setTitle(title, for: .normal) - btn.layer.cornerRadius = hud.config.alert.cornerRadius / 2 - btn.titleLabel?.font = hud.config.alert.buttonFont + btn.layer.cornerRadius = alertConfig.cornerRadius / 2 + btn.titleLabel?.font = alertConfig.buttonFont btn.update(style: style) return btn } func update(style: UIAlertAction.Style) { - let pd = hud.config.alert.padding/2 + let pd = alertConfig.padding/2 if style != .cancel { backgroundColor = .init(white: 0, alpha: 0.04) contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5) diff --git a/ProHUD/HUDView.swift b/ProHUD/HUDView.swift index 963fd75..caae496 100644 --- a/ProHUD/HUDView.swift +++ b/ProHUD/HUDView.swift @@ -15,7 +15,7 @@ public extension ProHUD { public override init(frame: CGRect) { super.init(frame: frame) - spacing = hud.config.alert.margin + spacing = alertConfig.margin distribution = .fill alignment = .center axis = .vertical @@ -59,4 +59,12 @@ internal extension UIView { animateEaseOut(duration: 2, delay: delay, animations: animations, completion: completion) } + class func animateForToast(animations: @escaping () -> Void) { + animateEaseOut(duration: 1, delay: 0, animations: animations, completion: nil) + } + + class func animateForToast(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { + animateEaseOut(duration: 1, delay: 0, animations: animations, completion: completion) + } + } diff --git a/ProHUD/ProHUD.swift b/ProHUD/ProHUD.swift index 50320fc..d06797d 100644 --- a/ProHUD/ProHUD.swift +++ b/ProHUD/ProHUD.swift @@ -14,35 +14,16 @@ public class ProHUD: NSObject { public static let shared = ProHUD() - internal var config = Configuration() - - public var elements = [String]() - -// public var toasts = [Toast]() - public var alerts = [Alert]() + internal var toasts = [Toast]() + internal var alerts = [Alert]() internal var alertWindow: UIWindow? deinit { debugPrint(self, "deinit") } - -} - -public extension ProHUD { - - func config(toast: (inout Configuration.Toast) -> Void) { - toast(&config.toast) - } - func config(alert: (inout Configuration.Alert) -> Void) { - alert(&config.alert) - } - func config(guard: (inout Configuration.Guard) -> Void) { - `guard`(&config.guard) - } - } diff --git a/ProHUD/Toast/ToastConfig.swift b/ProHUD/Toast/ToastConfig.swift index b128343..bd81696 100644 --- a/ProHUD/Toast/ToastConfig.swift +++ b/ProHUD/Toast/ToastConfig.swift @@ -30,32 +30,157 @@ public extension ProHUD.Configuration { public var iconSize = CGSize(width: 48, height: 48) /// 加载视图 - lazy var loadSubviews: (ProHUD.Alert) -> Void = { + lazy var loadSubviews: (ProHUD.Toast) -> Void = { return { (vc) in debugPrint(vc, "loadSubviews") + let config = toastConfig + vc.view.addSubview(vc.contentView) + vc.contentView.contentView.addSubview(vc.contentStack) + vc.contentStack.spacing = config.margin + + vc.contentView.layer.masksToBounds = true + vc.contentView.layer.cornerRadius = config.cornerRadius + + vc.contentView.snp.makeConstraints { (mk) in + mk.leading.trailing.top.equalToSuperview() + } + vc.contentStack.snp.makeConstraints { (mk) in + mk.top.equalToSuperview().offset(config.padding) + mk.bottom.equalToSuperview().offset(-config.padding) + mk.leading.equalToSuperview().offset(config.padding) + mk.trailing.equalToSuperview().offset(-config.padding) + } } }() /// 更新视图 - lazy var updateFrame: (ProHUD.Alert) -> Void = { + lazy var updateFrame: (ProHUD.Toast) -> Void = { return { (vc) in debugPrint(vc, "updateFrame") + let config = toastConfig + let isFirstLayout: Bool + // 图标和文字至少有一个,如果都没有添加到视图中,说明是第一次layout + if vc.textStack.superview == nil && vc.imageView?.superview == nil { + isFirstLayout = true + } else { + isFirstLayout = false + } + let imgStr: String + switch vc.vm.scene { + case .success: + imgStr = "ProHUDSuccess" + case .warning: + imgStr = "ProHUDWarning" + case .error: + imgStr = "ProHUDError" + case .loading: + imgStr = "ProHUDLoading" + case .confirm: + imgStr = "ProHUDMessage" + case .delete: + imgStr = "ProHUDTrash" + default: + imgStr = "ProHUDMessage" + } + let img = vc.vm.icon ?? ProHUD.image(named: imgStr) + if let imgv = vc.imageView { + imgv.image = img + } else { + let icon = UIImageView(image: img) + vc.contentStack.addArrangedSubview(icon) +// icon.snp.makeConstraints { (mk) in +// mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding+config.margin) +// mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding-config.margin) +// mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding+config.margin) +// mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding-config.margin) +// } + vc.imageView = icon + } + + // text + if vc.vm.title?.count ?? 0 > 0 || vc.vm.message?.count ?? 0 > 0 { + vc.contentStack.addArrangedSubview(vc.textStack) +// vc.textStack.snp.makeConstraints { (mk) in +// mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding*1.5) +// mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding*1.5) +// mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding*2) +// mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2) +// } + if vc.vm.title?.count ?? 0 > 0 { + if let lb = vc.titleLabel { + lb.text = vc.vm.title + } else { + let title = UILabel() + title.textAlignment = .justified + title.numberOfLines = config.titleMaxLines + title.textColor = UIColor.init(white: 0.2, alpha: 1) + title.font = config.titleFont + title.text = vc.vm.title + vc.textStack.addArrangedSubview(title) + vc.titleLabel = title + } + } else { + vc.titleLabel?.removeFromSuperview() + } + if vc.vm.message?.count ?? 0 > 0 { + if let lb = vc.messageLabel { + lb.text = vc.vm.message + } else { + let body = UILabel() + body.textAlignment = .justified + body.font = config.bodyFont + body.numberOfLines = config.bodyMaxLines + body.textColor = UIColor.darkGray + body.font = config.bodyFont + body.text = vc.vm.message + vc.textStack.addArrangedSubview(body) + vc.messageLabel = body + } + } else { + vc.messageLabel?.removeFromSuperview() + } + } else { + vc.textStack.removeFromSuperview() + } + + if isFirstLayout { + vc.view.layoutIfNeeded() + vc.updateFrame() + vc.imageView?.transform = .init(scaleX: 0.75, y: 0.75) + } else { + + } + + UIView.animateFastEaseOut(delay: 0, animations: { + vc.imageView?.transform = .identity + vc.view.layoutIfNeeded() + vc.updateFrame() + + }) { (done) in + } } }() /// 加载视图 /// - Parameter callback: 回调代码 - public mutating func loadSubviews(_ callback: @escaping (ProHUD.Alert) -> Void) { + public mutating func loadSubviews(_ callback: @escaping (ProHUD.Toast) -> Void) { loadSubviews = callback } /// 更新视图 /// - Parameter callback: 回调代码 - public mutating func updateFrame(_ callback: @escaping (ProHUD.Alert) -> Void) { + public mutating func updateFrame(_ callback: @escaping (ProHUD.Toast) -> Void) { updateFrame = callback } } } +internal var toastConfig = ProHUD.Configuration.Toast() + +public extension ProHUD { + static func configToast(_ config: (inout Configuration.Toast) -> Void) { + config(&toastConfig) + } +} diff --git a/ProHUD/Toast/ToastController.swift b/ProHUD/Toast/ToastController.swift index 85ed164..ff3c4cb 100644 --- a/ProHUD/Toast/ToastController.swift +++ b/ProHUD/Toast/ToastController.swift @@ -6,4 +6,326 @@ // Copyright © 2019 Titan Studio. All rights reserved. // -import Foundation +import UIKit +import SnapKit + +public extension ProHUD { + class Toast: HUDController { + + public var window: UIWindow? + /// 内容容器 + public var contentView = BlurView() + internal var contentStack: StackContainer = { + let stack = StackContainer() + stack.axis = .horizontal + stack.alignment = .top + stack.spacing = toastConfig.margin + return stack + }() + + /// 图标 + internal var imageView: UIImageView? + /// 文本区域 + internal var textStack: StackContainer = { + let stack = StackContainer() + stack.spacing = toastConfig.margin + stack.alignment = .leading + return stack + }() + internal var titleLabel: UILabel? + internal var messageLabel: UILabel? + + /// 视图模型 + public var vm = ViewModel() + + public var removable = true + + internal var tapCallback: (() -> Void)? + + // MARK: 生命周期 + + /// 实例化 + /// - Parameter scene: 场景 + /// - Parameter title: 标题 + /// - Parameter message: 内容 + /// - Parameter icon: 图标 + public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil) { + self.init() + vm.scene = scene + vm.title = title + vm.message = message + vm.icon = icon + switch scene { + case .loading: + timeout = nil + default: + timeout = 2 + } + + willLayout() + + // 点击 + let tap = UITapGestureRecognizer(target: self, action: #selector(privDidTapped(_:))) + view.addGestureRecognizer(tap) + // 拖动 + let pan = UIPanGestureRecognizer(target: self, action: #selector(privDidPan(_:))) + view.addGestureRecognizer(pan) + + } + + public override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + disappearCallback?() + } + + + /// 移除 + public func remove() { + hud.removeItemFromArray(toast: self) + UIView.animateForToast(animations: { + let frame = self.window?.frame ?? .zero + self.window?.transform = .init(translationX: 0, y: -200-frame.maxY) + }) { (done) in + self.view.removeFromSuperview() + self.removeFromParent() + self.window = nil + } + } + + internal func updateFrame() { + let newSize = contentView.frame.size + view.frame.size = newSize + window?.frame.size = newSize + hud.updateToastsLayout() + } + + // MARK: 设置函数 + + /// 设置超时时间 + /// - Parameter timeout: 超时时间 + @discardableResult public func timeout(_ timeout: TimeInterval?) -> Toast { + self.timeout = timeout + willLayout() + return self + } + + /// 点击事件 + /// - Parameter callback: 事件回调 + @discardableResult public func didTapped(_ callback: (() -> Void)?) -> Toast { + tapCallback = callback + return self + } + + /// 消失事件 + /// - Parameter callback: 事件回调 + @discardableResult public func didDisappear(_ callback: (() -> Void)?) -> Toast { + disappearCallback = callback + return self + } + + /// 更新标题 + /// - Parameter title: 标题 + @discardableResult public func update(scene: Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast { + vm.scene = scene + vm.title = title + vm.message = message + vm.icon = icon + toastConfig.updateFrame(self) + return self + } + + + + } + +} + +fileprivate extension ProHUD.Toast { + func willLayout() { + willLayout?.cancel() + willLayout = DispatchWorkItem(block: { [weak self] in + if let a = self { + // 布局 + toastConfig.loadSubviews(a) + toastConfig.updateFrame(a) + // 超时 + a.timeoutBlock?.cancel() + if let t = a.timeout, t > 0 { + a.timeoutBlock = DispatchWorkItem(block: { [weak self] in + self?.remove() + }) + DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: a.timeoutBlock!) + } else { + a.timeoutBlock = nil + } + } + }) + DispatchQueue.main.asyncAfter(deadline: .now()+0.001, execute: willLayout!) + } + + func willUpdateToastsLayout() { + + } + + + /// 点击事件 + /// - Parameter sender: 手势 + @objc func privDidTapped(_ sender: UITapGestureRecognizer) { + tapCallback?() + } + + /// 拖拽事件 + /// - Parameter sender: 手势 + @objc func privDidPan(_ sender: UIPanGestureRecognizer) { + timeoutBlock?.cancel() + let point = sender.translation(in: sender.view) + window?.transform = .init(translationX: 0, y: point.y) + if sender.state == .recognized { + let v = sender.velocity(in: sender.view) + if removable == true && (((window?.frame.origin.y ?? 0) < 0 && v.y < 0) || v.y < -1200) { + // 移除 + self.remove() + } else { + UIView.animateForToast(animations: { + self.window?.transform = .identity + }) { (done) in + // 重置计时器 + + } + } + } + } +} + + +// MARK: - AlertHUD public func + +public extension ProHUD { + + @discardableResult + func show(_ toast: Toast) -> Toast { + let config = toastConfig + if toast.window == nil { + let w = UIWindow(frame: .init(x: config.margin, y: config.margin, width: UIScreen.main.bounds.width - 2*config.margin, height: 500)) + toast.window = w + w.rootViewController = toast + w.windowLevel = UIWindow.Level(5000) + w.backgroundColor = .clear + w.layer.shadowRadius = 8 + w.layer.shadowOffset = .init(width: 0, height: 5) + w.layer.shadowOpacity = 0.2 + } + + let window = toast.window! + window.makeKeyAndVisible() + window.transform = .init(translationX: 0, y: -window.frame.maxY) + + toasts.append(toast) + + UIView.animateForToast { + window.transform = .identity + } + return toast + } + + @discardableResult + func show(toast: Toast.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast { + return show(Toast(scene: toast, title: title, message: message, icon: icon)) + } + + func toasts(identifier: String?) -> [Toast] { + var tt = [Toast]() + for t in toasts { + if t.identifier == identifier { + tt.append(t) + } + } + return tt + } + + func remove(toast: Toast) { + for t in toasts { + if t == toast { + t.remove() + } + } + } + + func remove(toast identifier: String?) { + for t in toasts { + if t.identifier == identifier { + t.remove() + } + } + } +} + + +// MARK: AlertHUD public class func + +public extension ProHUD { + + @discardableResult + class func show(_ toast: Toast) -> Toast { + return shared.show(toast) + } + + @discardableResult + class func show(toast: Toast.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast { + return shared.show(toast: toast, title: title, message: message, icon: icon) + } + + class func alert(identifier: String?) -> [Toast] { + return shared.toasts(identifier: identifier) + } + + class func remove(toast: Toast) { + shared.remove(toast: toast) + } + + class func remove(toast identifier: String?) { + shared.remove(toast: identifier) + } + +} + +// MARK: AlertHUD private func + +fileprivate extension ProHUD { + + func updateToastsLayout() { + for (i, e) in toasts.enumerated() { + let config = toastConfig + var frame = e.window?.frame ?? .zero + if i == 0 { + frame.origin.y = 44 + } else { + let lastY = toasts[i-1].window?.frame.maxY ?? .zero + frame.origin.y = lastY + config.margin + } + UIView.animateForToast(animations: { + e.window?.frame = frame + }) { (done) in + + } + } + } + + func removeItemFromArray(toast: Toast) { + if toasts.count > 1 { + for (i, t) in toasts.enumerated() { + if t == toast { + toasts.remove(at: i) + } + } + updateToastsLayout() + } else if toasts.count == 1 { + toasts.removeAll() + } else { + debugPrint("漏洞:已经没有toast了") + } + } + +} + + diff --git a/ProHUD/Toast/ToastModel.swift b/ProHUD/Toast/ToastModel.swift index eab4d69..fd4b385 100644 --- a/ProHUD/Toast/ToastModel.swift +++ b/ProHUD/Toast/ToastModel.swift @@ -6,4 +6,75 @@ // Copyright © 2019 Titan Studio. All rights reserved. // -import Foundation +public extension ProHUD.Toast { + enum Scene { + /// 默认场景 + case `default` + + /// 加载中场景 + case loading + + /// 确认场景 + case confirm + + /// 删除场景 + case delete + + /// 成功场景 + case success + + /// 警告场景 + case warning + + /// 错误场景 + case error + + public var backgroundColor: UIColor { + switch self { + case .success: + return UIColor.green + case .warning: + return UIColor.yellow + case .error: + return UIColor.red + default: + return .clear + } + } + + public var tintColor: UIColor { + switch self { + case .success, .error: + return .white + default: + return UIColor.darkGray + } + } + } + + struct ViewModel { + + /// 使用场景 + public var scene = Scene.default + + /// 标题 + public var title: String? + + /// 正文 + public var message: String? + + /// 图标 + public var icon: UIImage? + + public init(title: String? = nil, message: String? = nil, icon: UIImage? = nil) { + self.title = title + self.message = message + self.icon = icon + } + + + } + +} + +