diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index ef186db..ea9a03b 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -31,7 +31,7 @@ class ViewController: UIViewController { let t = ProHUD.Toast(scene: .loading, title: "正在加载", message: "请稍候片刻") let a = ProHUD.push(alert : .loading, title: "正在加载", message: "请稍候片刻") - a.didMinimize { + a.didForceQuit { hud.push(t) } t.didTapped { [weak t] in @@ -58,15 +58,23 @@ class ViewController: UIViewController { a?.removeAction(index: 0).removeAction(index: 0) a?.updateContent(scene: .loading, title: "正在删除", message: "请稍后片刻") DispatchQueue.main.asyncAfter(deadline: .now()+1) { - a?.updateContent(scene: .success, title: "删除成功", message: "啊哈哈哈哈").timeout(2) - ProHUD.show(toast: .success, title: "删除成功", message: "aaa") + a?.updateContent(scene: .success, title: "删除成功", message: "啊哈哈哈哈").duration(2) + ProHUD.push(toast: .success, title: "删除成功", message: "aaa") } }).addAction(style: .cancel, title: "取消", action: nil) + } @IBAction func test(_ sender: UIButton) { + + + textUpdateAction() + + } + + func testGuard() { let g = ProHUD.Guard(title: "请求权限", message: "请打开相机权限开关,否则无法进行测量。") - + g.loadTitle("呵呵") g.loadBody("请打开相机权限开关,否则无法进行测量。请打开相机权限开关,否则无法进行测量。") g.loadButton(style: .default, title: "测试弹窗", action: { [weak self] in @@ -78,8 +86,18 @@ class ViewController: UIViewController { g.loadButton(style: .cancel, title: "我知道了") g.push(to: self) debugPrint("test: ", g) + } + + func textUpdateAction() { + let a = ProHUD.push(alert: .confirm, title: "确认删除", message: "此操作无法撤销") + a.addAction(style: .destructive, title: "删除") { + a.removeAction(index: 0, 1).updateContent(scene: .loading, title: "正在删除", message: "请稍后片刻") + }.addAction(style: .cancel, title: "取消", action: nil) + + } + override func touchesBegan(_ touches: Set, with event: UIEvent?) { diff --git a/ProHUD/Alert/AlertConfig.swift b/ProHUD/Alert/AlertConfig.swift index ca116ec..80dd8f5 100644 --- a/ProHUD/Alert/AlertConfig.swift +++ b/ProHUD/Alert/AlertConfig.swift @@ -132,7 +132,7 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { isFirstLayout = false } let imgStr: String - switch vc.vm.scene { + switch vc.model.scene { case .success: imgStr = "ProHUDSuccess" case .warning: @@ -148,7 +148,7 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { default: imgStr = "ProHUDMessage" } - let img = vc.vm.icon ?? ProHUD.image(named: imgStr) + let img = vc.model.icon ?? ProHUD.image(named: imgStr) if let imgv = vc.imageView { imgv.image = img } else { @@ -167,7 +167,7 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { } // text - if vc.vm.title?.count ?? 0 > 0 || vc.vm.message?.count ?? 0 > 0 { + if vc.model.title?.count ?? 0 > 0 || vc.model.message?.count ?? 0 > 0 { vc.contentStack.addArrangedSubview(vc.textStack) vc.textStack.snp.makeConstraints { (mk) in mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding*1.75) @@ -175,19 +175,19 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { 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 vc.model.title?.count ?? 0 > 0 { if let lb = vc.titleLabel { - lb.text = vc.vm.title + lb.text = vc.model.title } else { let title = UILabel() title.textAlignment = .center title.numberOfLines = config.titleMaxLines title.textColor = cfg.primaryLabelColor - title.text = vc.vm.title + title.text = vc.model.title vc.textStack.addArrangedSubview(title) vc.titleLabel = title } - if vc.vm.message?.count ?? 0 > 0 { + if vc.model.message?.count ?? 0 > 0 { // 有message vc.titleLabel?.font = config.titleFont } else { @@ -197,28 +197,28 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { } else { vc.titleLabel?.removeFromSuperview() } - if vc.vm.message?.count ?? 0 > 0 { - if let lb = vc.messageLabel { - lb.text = vc.vm.message + if vc.model.message?.count ?? 0 > 0 { + if let lb = vc.bodyLabel { + lb.text = vc.model.message } else { let body = UILabel() body.textAlignment = .center body.font = config.bodyFont body.numberOfLines = config.bodyMaxLines body.textColor = cfg.secondaryLabelColor - body.text = vc.vm.message + body.text = vc.model.message vc.textStack.addArrangedSubview(body) - vc.messageLabel = body + vc.bodyLabel = body } - if vc.vm.title?.count ?? 0 > 0 { + if vc.model.title?.count ?? 0 > 0 { // 有title - vc.messageLabel?.font = config.bodyFont + vc.bodyLabel?.font = config.bodyFont } else { // 没有title - vc.messageLabel?.font = config.boldTextFont + vc.bodyLabel?.font = config.boldTextFont } } else { - vc.messageLabel?.removeFromSuperview() + vc.bodyLabel?.removeFromSuperview() } } else { vc.textStack.removeFromSuperview() @@ -297,7 +297,7 @@ fileprivate var privLoadForceQuitButton: (ProHUD.Alert) -> Void = { } vc.addTouchUpAction(for: btn) { [weak vc] in debug("点击了隐藏") - vc?.minimizeCallback?() + vc?.model.forceQuitCallback?() vc?.pop() } } diff --git a/ProHUD/Alert/AlertController.swift b/ProHUD/Alert/AlertController.swift index 6868392..7993b96 100644 --- a/ProHUD/Alert/AlertController.swift +++ b/ProHUD/Alert/AlertController.swift @@ -12,27 +12,34 @@ import SnapKit public extension ProHUD { class Alert: HUDController { - /// 内容容器 + /// 内容视图 public var contentView = BlurView() - /// 正文(包括icon、textStack、actionStack) - internal var contentStack: StackContainer = { + /// 内容容器(包括icon、textStack、actionStack) + public var contentStack: StackContainer = { let stack = StackContainer() stack.spacing = cfg.alert.margin return stack }() - /// 文本区域 - internal var textStack: StackContainer = { + /// 文本区容器 + public var textStack: StackContainer = { let stack = StackContainer() stack.spacing = cfg.alert.margin return stack }() - internal var imageView: UIImageView? - internal var titleLabel: UILabel? - internal var messageLabel: UILabel? + + /// 图片 + public var imageView: UIImageView? + + /// 标题 + public var titleLabel: UILabel? + + /// 正文 + public var bodyLabel: UILabel? + /// 操作区域 - internal var actionStack: StackContainer = { + public var actionStack: StackContainer = { let stack = StackContainer() stack.alignment = .fill stack.spacing = cfg.alert.margin @@ -40,12 +47,8 @@ public extension ProHUD { }() /// 视图模型 - public var vm = ViewModel() + public var model = ViewModel() - /// 显示顶部按钮(最小化) - internal var showNavButtonsBlock: DispatchWorkItem? - - internal var minimizeCallback: (() -> Void)? // MARK: 生命周期 @@ -57,15 +60,15 @@ public extension ProHUD { public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil) { self.init() view.tintColor = cfg.alert.tintColor - vm.scene = scene - vm.title = title - vm.message = message - vm.icon = icon + model.scene = scene + model.title = title + model.message = message + model.icon = icon switch scene { case .loading: - timeout = nil + model.duration = nil default: - timeout = 2 + model.duration = 2 } willLayoutSubviews() @@ -78,172 +81,312 @@ public extension ProHUD { } - /// 移除 - public func pop() { - let window = hud.getAlertWindow(self) - hud.removeItemFromArray(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 = hud.alerts.count - if count == 0 && hud.alertWindow != nil { - UIView.animateForAlertBuildOut(animations: { - window.backgroundColor = window.backgroundColor?.withAlphaComponent(0) - }) { (done) in - hud.alertWindow = nil - } - } - } - - - // MARK: 设置函数 - - /// 设置超时时间 - /// - Parameter timeout: 超时时间 - @discardableResult public func timeout(_ timeout: TimeInterval?) -> Alert { - self.timeout = timeout - willLayoutSubviews() - return self - } - - /// 添加按钮 - /// - Parameter style: 样式 - /// - Parameter text: 标题 - /// - Parameter action: 事件 - @discardableResult public func addAction(style: UIAlertAction.Style, title: String?, action: (() -> Void)?) -> Alert { - if let btn = privAddButton(custom: Button.actionButton(title: title), action: action) as? Button { - btn.update(style: style) - } - return self - } - - /// 添加按钮 - /// - Parameter button: 按钮 - /// - Parameter action: 事件 - @discardableResult public func addAction(custom button: UIButton, action: (() -> Void)?) -> Alert { - privAddButton(custom: button, action: action) - return self - } - - /// 最小化事件 - /// - Parameter callback: 事件回调 - @discardableResult public func didMinimize(_ callback: (() -> Void)?) -> Alert { - minimizeCallback = callback - return self - } - - /// 消失事件 - /// - Parameter callback: 事件回调 - @discardableResult public func didDisappear(_ callback: (() -> Void)?) -> Alert { - disappearCallback = callback - return self - } - - /// 更新标题 - /// - Parameter title: 标题 - @discardableResult public func updateContent(scene: Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Alert { - vm.title = title - vm.message = message - vm.scene = scene - vm.icon = icon - cfg.alert.reloadData(self) - return self - } - - - /// 更新按钮 - /// - Parameter index: 索引 - /// - Parameter style: 样式 - /// - Parameter title: 标题 - /// - Parameter action: 事件 - @discardableResult public func updateAction(index: Int, style: UIAlertAction.Style, title: String?, action: (() -> Void)?) -> Alert { - return updateAction(index: index, button: { (btn) in - btn.setTitle(title, for: .normal) - if let b = btn as? Button { - b.update(style: style) - } - btn.layoutIfNeeded() - }, action: action) - } - - - @discardableResult public func updateAction(index: Int, button: (UIButton) -> Void, action: (() -> Void)? = nil) -> Alert { - if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton { - button(btn) - if let ac = action { - addTouchUpAction(for: btn, action: ac) - } - } - UIView.animateForAlert { - self.view.layoutIfNeeded() - } - return self - } - - @discardableResult public func removeAction(index: Int) -> Alert { - if index < 0 { - for view in self.actionStack.arrangedSubviews { - if let btn = view as? UIButton { - btn.removeFromSuperview() - } - } - } else if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton { - btn.removeFromSuperview() - } - if self.actionStack.arrangedSubviews.count == 0 { - self.actionStack.removeFromSuperview() - } - willLayoutSubviews() - UIView.animateForAlert { - self.view.layoutIfNeeded() - } - return self - } - } } +// MARK: - 实例函数 + +public extension ProHUD.Alert { + + // MARK: 生命周期函数 + + /// 推入屏幕 + @discardableResult func push() -> ProHUD.Alert { + return ProHUD.push(self) + } + + /// 弹出屏幕 + func pop() { + let window = hud.getAlertWindow(self) + hud.removeItemFromArray(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 = hud.alerts.count + if count == 0 && hud.alertWindow != nil { + UIView.animateForAlertBuildOut(animations: { + window.backgroundColor = window.backgroundColor?.withAlphaComponent(0) + }) { (done) in + hud.alertWindow = nil + } + } + } + + + // MARK: 设置函数 + + /// 设置超时时间 + /// - Parameter duration: 超时时间 + @discardableResult func duration(_ duration: TimeInterval?) -> ProHUD.Alert { + model.duration = duration + willLayoutSubviews() + return self + } + + /// 添加按钮 + /// - Parameter style: 样式 + /// - Parameter text: 标题 + /// - Parameter action: 事件 + @discardableResult func addAction(style: UIAlertAction.Style, title: String?, action: (() -> Void)?) -> ProHUD.Alert { + if let btn = privAddButton(custom: Button.actionButton(title: title), action: action) as? Button { + btn.update(style: style) + } + return self + } + + /// 添加按钮 + /// - Parameter button: 按钮 + /// - Parameter action: 事件 + @discardableResult func addAction(custom button: UIButton, action: (() -> Void)?) -> ProHUD.Alert { + privAddButton(custom: button, action: action) + return self + } + + /// 最小化事件 + /// - Parameter callback: 事件回调 + @discardableResult func didForceQuit(_ callback: (() -> Void)?) -> ProHUD.Alert { + model.forceQuitCallback = callback + return self + } + + /// 消失事件 + /// - Parameter callback: 事件回调 + @discardableResult func didDisappear(_ callback: (() -> Void)?) -> ProHUD.Alert { + disappearCallback = callback + return self + } + + /// 更新标题 + /// - Parameter title: 标题 + @discardableResult func updateContent(scene: Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> ProHUD.Alert { + model.title = title + model.message = message + model.scene = scene + model.icon = icon + cfg.alert.reloadData(self) + return self + } + + /// 更新按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter action: 事件 + @discardableResult func updateAction(index: Int, style: UIAlertAction.Style, title: String?, action: (() -> Void)?) -> ProHUD.Alert { + return updateAction(index: index, button: { (btn) in + btn.setTitle(title, for: .normal) + if let b = btn as? Button { + b.update(style: style) + } + btn.layoutIfNeeded() + }, action: action) + } + + /// 更新按钮 + /// - Parameter index: 索引 + /// - Parameter button: 按钮 + /// - Parameter action: 事件 + @discardableResult func updateAction(index: Int, button: (UIButton) -> Void, action: (() -> Void)? = nil) -> ProHUD.Alert { + if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton { + button(btn) + if let ac = action { + addTouchUpAction(for: btn, action: ac) + } + } + UIView.animateForAlert { + self.view.layoutIfNeeded() + } + return self + } + + /// 移除按钮 + /// - Parameter index: 索引 + @discardableResult func removeAction(index: Int...) -> ProHUD.Alert { + for (i, idx) in index.enumerated() { + privRemoveAction(index: idx-i) + } + return self + } + +} + + + +// MARK: - 实例函数 + +public extension ProHUD { + + /// 推入屏幕 + /// - Parameter alert: 实例 + @discardableResult func push(_ alert: Alert) -> Alert { + let window = getAlertWindow(alert) + window.makeKeyAndVisible() + window.resignKey() + window.addSubview(alert.view) + alert.view.transform = .init(scaleX: 1.2, y: 1.2) + alert.view.alpha = 0 + UIView.animateForAlertBuildIn { + alert.view.transform = .identity + alert.view.alpha = 1 + window.backgroundColor = window.backgroundColor?.withAlphaComponent(0.6) + } + alerts.append(alert) + updateAlertsLayout() + // setup duration + if let _ = alert.model.duration, alert.model.durationBlock == nil { + alert.duration(alert.model.duration) + } + return alert + } + + /// 推入屏幕 + /// - Parameter alert: 场景 + /// - Parameter title: 标题 + /// - Parameter message: 正文 + /// - Parameter icon: 图标 + @discardableResult func push(alert scene: Alert.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Alert { + return push(Alert(scene: scene, title: title, message: message, icon: icon)) + } + + /// 获取指定的实例 + /// - Parameter identifier: 指定实例的标识 + func alerts(identifier: String?) -> [Alert] { + var aa = [Alert]() + for a in alerts { + if a.identifier == identifier { + aa.append(a) + } + } + return aa + } + + /// 弹出屏幕 + /// - Parameter alert: 实例 + func pop(alert: Alert) { + for a in alerts { + if a == alert { + a.pop() + } + } + } + + /// 弹出实例 + /// - Parameter identifier: 指定实例的标识 + func pop(alert identifier: String?) { + for a in alerts { + if a.identifier == identifier { + a.pop() + } + } + } +} + + +// MARK: 类函数 + +public extension ProHUD { + + /// 推入屏幕 + /// - Parameter alert: 实例 + @discardableResult class func push(_ alert: Alert) -> Alert { + return shared.push(alert) + } + + /// 推入屏幕 + /// - Parameter alert: 场景 + /// - Parameter title: 标题 + /// - Parameter message: 正文 + /// - Parameter icon: 图标 + @discardableResult class func push(alert: Alert.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Alert { + return shared.push(alert: alert, title: title, message: message, icon: icon) + } + + /// 获取指定的实例 + /// - Parameter identifier: 指定实例的标识 + class func alert(identifier: String?) -> [Alert] { + return shared.alerts(identifier: identifier) + } + + /// 弹出屏幕 + /// - Parameter alert: 实例 + class func pop(alert: Alert) { + shared.pop(alert: alert) + } + + /// 弹出实例 + /// - Parameter identifier: 指定实例的标识 + class func pop(alert identifier: String?) { + shared.pop(alert: identifier) + } + +} + +// MARK: - 私有 + fileprivate extension ProHUD.Alert { + + /// 移除按钮 + /// - Parameter index: 索引 + @discardableResult func privRemoveAction(index: Int) -> ProHUD.Alert { + if index < 0 { + for view in self.actionStack.arrangedSubviews { + if let btn = view as? UIButton { + btn.removeFromSuperview() + } + } + } else if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton { + btn.removeFromSuperview() + } + if self.actionStack.arrangedSubviews.count == 0 { + self.actionStack.removeFromSuperview() + } + willLayoutSubviews() + UIView.animateForAlert { + self.view.layoutIfNeeded() + } + return self + } + func willLayoutSubviews() { willLayout?.cancel() - timeoutBlock?.cancel() - showNavButtonsBlock?.cancel() + model.durationBlock?.cancel() + model.forceQuitTimerBlock?.cancel() willLayout = DispatchWorkItem(block: { [weak self] in if let a = self { // 布局 cfg.alert.loadSubviews(a) cfg.alert.reloadData(a) // 超时 - if let t = a.timeout, t > 0 { - a.timeoutBlock = DispatchWorkItem(block: { [weak self] in + if let t = a.model.duration, t > 0 { + a.model.durationBlock = DispatchWorkItem(block: { [weak self] in self?.pop() }) - DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: a.timeoutBlock!) + DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: a.model.durationBlock!) } else { - a.timeoutBlock = nil + a.model.durationBlock = nil } - // 顶部按钮 + // 强制退出按钮 if cfg.alert.forceQuitTimer > 0 && self?.actionStack.superview == nil { - a.showNavButtonsBlock = DispatchWorkItem(block: { [weak self] in + a.model.forceQuitTimerBlock = DispatchWorkItem(block: { [weak self] in if let s = self { cfg.alert.loadForceQuitButton(s) } }) - DispatchQueue.main.asyncAfter(deadline: .now()+cfg.alert.forceQuitTimer, execute: a.showNavButtonsBlock!) + DispatchQueue.main.asyncAfter(deadline: .now()+cfg.alert.forceQuitTimer, execute: a.model.forceQuitTimerBlock!) } } }) DispatchQueue.main.asyncAfter(deadline: .now()+0.001, execute: willLayout!) } - @discardableResult private func privAddButton(custom button: UIButton, action: (() -> Void)?) -> UIButton { - timeout = nil + @discardableResult func privAddButton(custom button: UIButton, action: (() -> Void)?) -> UIButton { + model.duration = nil if actionStack.superview == nil { contentStack.addArrangedSubview(actionStack) } @@ -266,95 +409,6 @@ fileprivate extension ProHUD.Alert { } - -// MARK: - AlertHUD public func - -public extension ProHUD { - - @discardableResult - func push(_ alert: Alert) -> Alert { - let window = getAlertWindow(alert) - window.makeKeyAndVisible() - window.resignKey() - window.addSubview(alert.view) - alert.view.transform = .init(scaleX: 1.2, y: 1.2) - alert.view.alpha = 0 - UIView.animateForAlertBuildIn { - alert.view.transform = .identity - alert.view.alpha = 1 - window.backgroundColor = window.backgroundColor?.withAlphaComponent(0.6) - } - alerts.append(alert) - updateAlertsLayout() - // setup timeout - if let _ = alert.timeout, alert.timeoutBlock == nil { - alert.timeout(alert.timeout) - } - return alert - } - - @discardableResult - func push(alert: Alert.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Alert { - return push(Alert(scene: alert, title: title, message: message, icon: icon)) - } - - func alerts(identifier: String?) -> [Alert] { - var aa = [Alert]() - for a in alerts { - if a.identifier == identifier { - aa.append(a) - } - } - return aa - } - - func pop(alert: Alert) { - for a in alerts { - if a == alert { - a.pop() - } - } - } - - func pop(alert identifier: String?) { - for a in alerts { - if a.identifier == identifier { - a.pop() - } - } - } -} - - -// MARK: AlertHUD public class func - -public extension ProHUD { - - @discardableResult - class func push(_ alert: Alert) -> Alert { - return shared.push(alert) - } - - @discardableResult - class func push(alert: Alert.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Alert { - return shared.push(alert: alert, title: title, message: message, icon: icon) - } - - class func alert(identifier: String?) -> [Alert] { - return shared.alerts(identifier: identifier) - } - - class func pop(alert: Alert) { - shared.pop(alert: alert) - } - - class func pop(alert identifier: String?) { - shared.pop(alert: identifier) - } - -} - -// MARK: AlertHUD private func internal extension ProHUD { func updateAlertsLayout() { @@ -369,6 +423,7 @@ internal extension ProHUD { } } } + fileprivate extension ProHUD { func getAlertWindow(_ vc: UIViewController) -> UIWindow { diff --git a/ProHUD/Alert/AlertModel.swift b/ProHUD/Alert/AlertModel.swift index 01d64ef..dc321f2 100644 --- a/ProHUD/Alert/AlertModel.swift +++ b/ProHUD/Alert/AlertModel.swift @@ -66,6 +66,18 @@ public extension ProHUD.Alert { /// 图标 public var icon: UIImage? + /// 持续时间 + internal var duration: TimeInterval? + + /// 持续时间 + internal var durationBlock: DispatchWorkItem? + + /// 显示顶部按钮(最小化) + internal var forceQuitTimerBlock: DispatchWorkItem? + + /// 强制退出代码 + internal var forceQuitCallback: (() -> Void)? + public init(title: String? = nil, message: String? = nil, icon: UIImage? = nil) { self.title = title self.message = message diff --git a/ProHUD/Guard/GuardController.swift b/ProHUD/Guard/GuardController.swift index a420725..3b91a3c 100644 --- a/ProHUD/Guard/GuardController.swift +++ b/ProHUD/Guard/GuardController.swift @@ -9,12 +9,13 @@ import SnapKit public extension ProHUD { + class Guard: HUDController { - /// 内容容器 + /// 内容视图 public var contentView = BlurView() - /// 内容容器(包括textStack、actionStack) + /// 内容容器(包括textStack、actionStack,可以自己插入需要的控件) public var contentStack: StackContainer = { let stack = StackContainer() stack.spacing = cfg.guard.padding + cfg.guard.margin @@ -22,7 +23,7 @@ public extension ProHUD { return stack }() - /// 文本区域 + /// 文本区容器 public var textStack: StackContainer = { let stack = StackContainer() stack.spacing = cfg.guard.margin @@ -30,10 +31,7 @@ public extension ProHUD { return stack }() -// public var titleLabel: UILabel? -// public var bodyLabel: UILabel? - - /// 操作区域 + /// 操作区容器 public var actionStack: StackContainer = { let stack = StackContainer() stack.alignment = .fill @@ -47,6 +45,7 @@ public extension ProHUD { private let tag = Int(23905340) private var displaying = false + // MARK: 生命周期 /// 实例化 @@ -56,7 +55,7 @@ public extension ProHUD { /// - Parameter icon: 图标 public convenience init(title: String? = nil, message: String? = nil) { self.init() -// view = View() + view.tintColor = cfg.guard.tintColor if let _ = title { loadTitle(title) @@ -84,48 +83,58 @@ public extension ProHUD { debug(self, "deinit") } - public func push(to viewController: UIViewController? = nil) { - if let vc = viewController { - view.layoutIfNeeded() - vc.addChild(self) - vc.view.addSubview(view) - view.isUserInteractionEnabled = true - view.snp.makeConstraints { (mk) in - mk.edges.equalToSuperview() - } - if displaying == false { - translateOut() - } - displaying = true - UIView.animateForGuard { - self.translateIn() - } + + } +} + +// MARK: - 实例函数 + +public extension ProHUD.Guard { + + // MARK: 生命周期函数 + + /// 推入某个视图控制器 + /// - Parameter viewController: 视图控制器 + func push(to viewController: UIViewController? = nil) { + if let vc = viewController { + view.layoutIfNeeded() + vc.addChild(self) + vc.view.addSubview(view) + view.isUserInteractionEnabled = true + view.snp.makeConstraints { (mk) in + mk.edges.equalToSuperview() + } + if displaying == false { + translateOut() + } + displaying = true + UIView.animateForGuard { + self.translateIn() } - } - public func pop() { - if displaying { - debug("pop") - displaying = false - view.isUserInteractionEnabled = false - self.removeFromParent() - UIView.animateForGuard(animations: { - self.translateOut() - }) { (done) in - if self.displaying == false { - self.view.removeFromSuperview() - } + } + + /// 从父视图控制器弹出 + func pop() { + if displaying { + debug("pop") + displaying = false + view.isUserInteractionEnabled = false + self.removeFromParent() + UIView.animateForGuard(animations: { + self.translateOut() + }) { (done) in + if self.displaying == false { + self.view.removeFromSuperview() } } } } -} - -public extension ProHUD.Guard { - @discardableResult - func loadTitle(_ text: String?) -> UILabel { + /// 加载一个标题 + /// - Parameter text: 文本 + @discardableResult func loadTitle(_ text: String?) -> UILabel { let lb = UILabel() lb.font = cfg.guard.titleFont lb.textColor = cfg.primaryLabelColor @@ -144,8 +153,9 @@ public extension ProHUD.Guard { return lb } - @discardableResult - func loadBody(_ text: String?) -> UILabel { + /// 加载一段正文 + /// - Parameter text: 文本 + @discardableResult func loadBody(_ text: String?) -> UILabel { let lb = UILabel() lb.font = cfg.guard.bodyFont lb.textColor = cfg.secondaryLabelColor @@ -156,8 +166,11 @@ public extension ProHUD.Guard { return lb } - @discardableResult - func loadButton(style: UIAlertAction.Style, title: String?, action: (() -> Void)? = nil) -> UIButton { + /// 加载一个按钮 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter action: 事件 + @discardableResult func loadButton(style: UIAlertAction.Style, title: String?, action: (() -> Void)? = nil) -> UIButton { let btn = Button.actionButton(title: title) btn.titleLabel?.font = cfg.guard.buttonFont if actionStack.superview == nil { diff --git a/ProHUD/HUDController.swift b/ProHUD/HUDController.swift index 1ad3c99..a3c9650 100644 --- a/ProHUD/HUDController.swift +++ b/ProHUD/HUDController.swift @@ -14,11 +14,6 @@ public class HUDController: UIViewController { internal var willLayout: DispatchWorkItem? - /// 超时(自动消失)时间 - internal var timeout: TimeInterval? - - internal var timeoutBlock: DispatchWorkItem? - /// 消失回调 internal var disappearCallback: (() -> Void)? internal var buttonEvents = [UIButton:() -> Void]() diff --git a/ProHUD/Toast/ToastConfig.swift b/ProHUD/Toast/ToastConfig.swift index 32e2078..afb99dc 100644 --- a/ProHUD/Toast/ToastConfig.swift +++ b/ProHUD/Toast/ToastConfig.swift @@ -77,10 +77,10 @@ fileprivate var privReloadData: (ProHUD.Toast) -> Void = { return { (vc) in debug(vc, "reloadData") let config = cfg.toast - let scene = vc.vm.scene + let scene = vc.model.scene // 设置数据 let imgStr: String - switch vc.vm.scene { + switch vc.model.scene { case .success: imgStr = "ProHUDSuccess" case .warning: @@ -96,12 +96,12 @@ fileprivate var privReloadData: (ProHUD.Toast) -> Void = { default: imgStr = "ProHUDMessage" } - let img = vc.vm.icon ?? ProHUD.image(named: imgStr) + let img = vc.model.icon ?? ProHUD.image(named: imgStr) vc.imageView.image = img vc.titleLabel.textColor = cfg.primaryLabelColor - vc.titleLabel.text = vc.vm.title + vc.titleLabel.text = vc.model.title vc.bodyLabel.textColor = cfg.secondaryLabelColor - vc.bodyLabel.text = vc.vm.message + vc.bodyLabel.text = vc.model.message // 更新布局 vc.imageView.snp.makeConstraints { (mk) in diff --git a/ProHUD/Toast/ToastController.swift b/ProHUD/Toast/ToastController.swift index 3ec184f..b23e0a4 100644 --- a/ProHUD/Toast/ToastController.swift +++ b/ProHUD/Toast/ToastController.swift @@ -15,14 +15,14 @@ public extension ProHUD { public var window: UIWindow? /// 图标 - internal lazy var imageView: UIImageView = { + public lazy var imageView: UIImageView = { let imgv = UIImageView() imgv.contentMode = .scaleAspectFit return imgv }() /// 标题 - internal lazy var titleLabel: UILabel = { + public lazy var titleLabel: UILabel = { let lb = UILabel() lb.textColor = cfg.primaryLabelColor lb.font = cfg.toast.titleFont @@ -32,7 +32,7 @@ public extension ProHUD { }() /// 正文 - internal lazy var bodyLabel: UILabel = { + public lazy var bodyLabel: UILabel = { let lb = UILabel() lb.textColor = cfg.secondaryLabelColor lb.font = cfg.toast.bodyFont @@ -42,7 +42,7 @@ public extension ProHUD { }() /// 背景层 - var backgroundView: UIVisualEffectView = { + public var backgroundView: UIVisualEffectView = { let vev = UIVisualEffectView() if #available(iOS 13.0, *) { vev.effect = UIBlurEffect.init(style: .systemMaterial) @@ -55,12 +55,7 @@ public extension ProHUD { }() /// 视图模型 - public var vm = ViewModel() - - - public var removable = true - - internal var tapCallback: (() -> Void)? + public var model = ViewModel() // MARK: 生命周期 @@ -72,15 +67,15 @@ public extension ProHUD { 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 + model.scene = scene + model.title = title + model.message = message + model.icon = icon switch scene { case .loading: - timeout = nil + model.duration = nil default: - timeout = 2 + model.duration = 2 } // 布局 @@ -101,88 +96,103 @@ public extension ProHUD { disappearCallback?() } - - /// 移除 - public func pop() { - 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 - } + } + +} + +// MARK: - 实例函数 + +public extension ProHUD.Toast { + + // MARK: 生命周期函数 + + /// 推入屏幕 + func push() { + ProHUD.push(self) + } + + /// 弹出屏幕 + func pop() { + 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 } - - @discardableResult - func update(title: String?) -> Toast { - vm.title = title - titleLabel.text = title - return self + } + + // MARK: 设置函数 + + /// 设置持续时间 + /// - Parameter duration: 持续时间 + @discardableResult func duration(_ duration: TimeInterval?) -> ProHUD.Toast { + model.duration = duration + // 持续时间 + model.durationBlock?.cancel() + if let t = duration, t > 0 { + model.durationBlock = DispatchWorkItem(block: { [weak self] in + self?.pop() + }) + DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: model.durationBlock!) + } else { + model.durationBlock = nil } - - @discardableResult - func update(message: String?) -> Toast { - vm.message = message - bodyLabel.text = message - return self - } - - @discardableResult - func update(icon: UIImage?) -> Toast { - vm.icon = icon - imageView.image = icon - return self - } - - - // MARK: 设置函数 - - /// 设置超时时间 - /// - Parameter timeout: 超时时间 - @discardableResult public func timeout(_ timeout: TimeInterval?) -> Toast { - self.timeout = timeout - // 超时 - timeoutBlock?.cancel() - if let t = timeout, t > 0 { - timeoutBlock = DispatchWorkItem(block: { [weak self] in - self?.pop() - }) - DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: timeoutBlock!) - } else { - timeoutBlock = nil - } - 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 - cfg.toast.reloadData(self) - return self - } - - - + return self + } + + /// 点击事件 + /// - Parameter callback: 事件回调 + @discardableResult func didTapped(_ callback: (() -> Void)?) -> ProHUD.Toast { + model.tapCallback = callback + return self + } + + /// 消失事件 + /// - Parameter callback: 事件回调 + @discardableResult func didDisappear(_ callback: (() -> Void)?) -> ProHUD.Toast { + disappearCallback = callback + return self + } + + /// 更新 + /// - Parameter scene: 场景 + /// - Parameter title: 标题 + /// - Parameter message: 内容 + /// - Parameter icon: 图标 + @discardableResult func update(scene: Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> ProHUD.Toast { + model.scene = scene + model.title = title + model.message = message + model.icon = icon + cfg.toast.reloadData(self) + return self + } + + /// 更新标题 + /// - Parameter title: 标题 + @discardableResult func update(title: String?) -> ProHUD.Toast { + model.title = title + titleLabel.text = title + return self + } + + /// 更新文本 + /// - Parameter message: 消息 + @discardableResult func update(message: String?) -> ProHUD.Toast { + model.message = message + bodyLabel.text = message + return self + } + + /// 更新图标 + /// - Parameter icon: 图标 + @discardableResult func update(icon: UIImage?) -> ProHUD.Toast { + model.icon = icon + imageView.image = icon + return self } } @@ -192,18 +202,18 @@ fileprivate extension ProHUD.Toast { /// 点击事件 /// - Parameter sender: 手势 @objc func privDidTapped(_ sender: UITapGestureRecognizer) { - tapCallback?() + model.tapCallback?() } /// 拖拽事件 /// - Parameter sender: 手势 @objc func privDidPan(_ sender: UIPanGestureRecognizer) { - timeoutBlock?.cancel() + model.durationBlock?.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) { + if model.removable == true && (((window?.frame.origin.y ?? 0) < 0 && v.y < 0) || v.y < -1200) { // 移除 self.pop() } else { @@ -219,11 +229,13 @@ fileprivate extension ProHUD.Toast { } -// MARK: - AlertHUD public func +// MARK: - 实例函数 + public extension ProHUD { - @discardableResult - func push(_ toast: Toast) -> Toast { + /// Toast推入屏幕 + /// - Parameter toast: 实例 + @discardableResult func push(_ toast: Toast) -> Toast { let config = cfg.toast let isNew: Bool if toast.window == nil { @@ -275,11 +287,17 @@ public extension ProHUD { return toast } - @discardableResult - func push(toast: Toast.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast { - return push(Toast(scene: toast, title: title, message: message, icon: icon)) + /// Toast推入屏幕 + /// - Parameter toast: 场景 + /// - Parameter title: 标题 + /// - Parameter message: 内容 + /// - Parameter icon: 图标 + @discardableResult func push(toast scene: Toast.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast { + return push(Toast(scene: scene, title: title, message: message, icon: icon)) } + /// 获取指定的toast + /// - Parameter identifier: 标识 func toasts(identifier: String?) -> [Toast] { var tt = [Toast]() for t in toasts { @@ -290,6 +308,8 @@ public extension ProHUD { return tt } + /// Toast弹出屏幕 + /// - Parameter toast: 实例 func pop(toast: Toast) { for t in toasts { if t == toast { @@ -298,6 +318,8 @@ public extension ProHUD { } } + /// Toast弹出屏幕 + /// - Parameter identifier: 需要弹出的Toast的标识 func pop(toast identifier: String?) { for t in toasts { if t.identifier == identifier { @@ -305,38 +327,50 @@ public extension ProHUD { } } } + } -// MARK: AlertHUD public class func +// MARK: 类函数 public extension ProHUD { - @discardableResult - class func push(_ toast: Toast) -> Toast { + /// Toast推入屏幕 + /// - Parameter toast: 实例 + @discardableResult class func push(_ toast: Toast) -> Toast { return shared.push(toast) } - @discardableResult - class func show(toast: Toast.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast { + /// Toast推入屏幕 + /// - Parameter toast: 场景 + /// - Parameter title: 标题 + /// - Parameter message: 内容 + /// - Parameter icon: 图标 + @discardableResult class func push(toast: Toast.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast { return shared.push(toast: toast, title: title, message: message, icon: icon) } + /// 获取指定的toast + /// - Parameter identifier: 标识 class func toast(identifier: String?) -> [Toast] { return shared.toasts(identifier: identifier) } + /// Toast弹出屏幕 + /// - Parameter toast: 实例 class func pop(toast: Toast) { shared.pop(toast: toast) } + /// Toast弹出屏幕 + /// - Parameter identifier: 需要弹出的Toast的标识 class func pop(toast identifier: String?) { shared.pop(toast: identifier) } } -// MARK: AlertHUD private func +// MARK: 私有 fileprivate var willUpdateToastsLayout: DispatchWorkItem? @@ -375,7 +409,8 @@ internal extension ProHUD { internal extension ProHUD { - + /// 从数组中移除 + /// - Parameter toast: 实例 func removeItemFromArray(toast: Toast) { if toasts.count > 1 { for (i, t) in toasts.enumerated() { diff --git a/ProHUD/Toast/ToastModel.swift b/ProHUD/Toast/ToastModel.swift index 9264d5f..62498f1 100644 --- a/ProHUD/Toast/ToastModel.swift +++ b/ProHUD/Toast/ToastModel.swift @@ -45,6 +45,18 @@ public extension ProHUD.Toast { /// 图标 public var icon: UIImage? + /// 持续时间 + internal var duration: TimeInterval? + + /// 持续时间 + internal var durationBlock: DispatchWorkItem? + + /// 是否可以通过手势移除(向上划) + public var removable = true + + /// 点击事件回调 + internal var tapCallback: (() -> Void)? + public init(title: String? = nil, message: String? = nil, icon: UIImage? = nil) { self.title = title self.message = message