From 26d7aa0049ca7ed91021678e5c2b1129925b0e11 Mon Sep 17 00:00:00 2001 From: xaoxuu Date: Fri, 9 Aug 2019 18:02:41 +0800 Subject: [PATCH] tmp --- Example/Example/ViewController.swift | 215 +++++++++++++++------- ProHUD.xcodeproj/project.pbxproj | 4 + ProHUD/Alert/AlertConfig.swift | 258 ++++++++++++++------------- ProHUD/Alert/AlertController.swift | 173 ++++++------------ ProHUD/Alert/AlertModel.swift | 122 +++++++++---- ProHUD/Alert/AlertView.swift | 1 + ProHUD/Guard/GuardConfig.swift | 11 +- ProHUD/Guard/GuardController.swift | 20 ++- ProHUD/Guard/GuardModel.swift | 80 +++++++++ ProHUD/HUDController.swift | 3 - ProHUD/Toast/ToastController.swift | 4 +- ProHUD/Toast/ToastModel.swift | 3 + 12 files changed, 531 insertions(+), 363 deletions(-) create mode 100644 ProHUD/Guard/GuardModel.swift diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 9df51f8..62dd439 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -18,8 +18,15 @@ class ViewController: UIViewController { ProHUD.config { (cfg) in cfg.alert { (a) in - a.forceQuitTimer = 2 + a.duration = 1 + a.forceQuitTimer = 3 // a.iconSize = .init(width: 20, height: 80) +// a.reloadData +// a.iconSize = .init(width: 20, height: 80) + a.iconForScene { (s) -> UIImage? in + return UIImage(named: "icon_download") + } + } cfg.toast { (t) in // t.iconSize = .init(width: 300, height: 30) @@ -30,43 +37,134 @@ class ViewController: UIViewController { @IBAction func test(_ sender: UIButton) { - - +// testAlert() + testToast() // testUpdateAction() - testGuard() +// testGuard() // fastGuard() } + func testAlert() { + let a = Alert.push(scene: .loading) { (a) in + +// a.update() + } + a.update { (vm) in + vm.add(action: .default, title: "OK", handler: nil) + } +// a.update() +// Alert.push(scene: .loading, title: "Loading") { (a) in +// a.animate(rotate: true) +// DispatchQueue.main.asyncAfter(deadline: .now()+1) { +// a.update { (vm) in +// vm.message = "请稍后片刻" +// } +// a.animate(rotate: true) +// } +// DispatchQueue.main.asyncAfter(deadline: .now()+2) { +// a.update { (vm) in +// vm.message = "请稍后片刻请稍后片刻" +// } +// a.animate(rotate: true) +// } +// DispatchQueue.main.asyncAfter(deadline: .now()+3) { +// a.update { (vm) in +// vm.scene = .success +// vm.add(action: .default, title: "OK") { [weak a] in +// a?.pop() +// } +// } +// } +// DispatchQueue.main.asyncAfter(deadline: .now()+4) { +// a.update { (vm) in +// vm.update(action: 0, style: .cancel, title: "Cancel", handler: nil) +// } +// } +// } +// Alert.push(scene: .delete, title: "确认删除", message: "此操作不可撤销!此操作不可撤销!此操作不可撤销!") { (a) in +// a.identifier = "" +// DispatchQueue.main.asyncAfter(deadline: .now()+1) { +// a.update { (vm) in +// vm.add(action: .destructive, title: "确认") { [weak a] in +// a?.update({ (vm) in +// vm.message = "但是饭撒 打算放过" +// vm.remove(action: 1) +// vm.update(action: 0, style: .destructive, title: "确认", handler: { +// a?.pop() +// }) +// }) +// } +// vm.add(action: .cancel, title: "取消", handler: nil) +// } +// } +// +// } + } + func testDelete() { - let a = ProHUD.push(alert: .delete, title: "确认删除", message: "此操作不可撤销") - a.add(action: .destructive, title: "确认", action: { [weak a] in - a?.remove(action: 0, 1) - a?.update(scene: .loading, title: "正在删除", message: "请稍后片刻") - DispatchQueue.main.asyncAfter(deadline: .now()+1) { - a?.update(scene: .success, title: "删除成功", message: "啊哈哈哈哈").duration(2) - ProHUD.push(toast: .success, title: "删除成功", message: "aaa") - } - }).add(action: .cancel, title: "取消", action: nil) + let a = Alert.push(scene: .delete, title: "确认删除", message: "此操作不可撤销") + a.update { (vm) in + vm.add(action: .destructive, title: "确认", handler: { [weak a] in + a?.update { (vm) in + vm.scene = .loading + vm.title = "正在删除" + vm.message = "请稍后片刻" + vm.remove(action: 0, 1) + } + DispatchQueue.main.asyncAfter(deadline: .now()+1) { + a?.update { (vm) in + vm.scene = .success + vm.title = "删除成功" + vm.message = "啊哈哈哈哈" + vm.duration = 2 + } + Toast.push(scene: .success, title: "删除成功", message: "aaa") + } + }) + vm.add(action: .cancel, title: "取消", handler: nil) + } + } func testToast() { - let t = ProHUD.Toast(scene: .loading, title: "正在加载", message: "请稍候片刻") + let t = Toast(scene: .loading, title: "正在加载", message: "请稍候片刻") - let a = ProHUD.push(alert : .loading, title: "正在加载", message: "请稍候片刻") + let a = Alert.push(scene : .loading, title: "正在加载", message: "请稍候片刻") a.didForceQuit { - hud.push(t) + t.push() } + t.didTapped { [weak t] in t?.pop() - let a2 = ProHUD.push(alert: .loading, title: "正在加载", message: "马上就要成功了") + Alert.push(scene: .loading, title: "正在加载", message: "马上就要成功了") DispatchQueue.main.asyncAfter(deadline: .now()+1) { - let a3 = ProHUD.push(alert: .error, title: "加载失败", message: "点击充实") - a3.add(action: .default, title: "重新加载") { [weak a3] in - a3?.update(scene: .success, title: "加载成功", message: "马上就要成功了") - a3?.update(action: 0, style: .default, title: "OK", action: { [weak a3] in - a3?.pop() - }).remove(action: 1, 2) - }.add(action: .destructive, title: "终止", action: nil).add(action: .cancel, title: "取消", action: nil) + Alert.push(scene: .error, title: "加载失败", message: "点击充实") { (vm) in + vm.duration = 0 + vm.identifier = "hehe" + let a = vm.vc! + vm.add(action: .default, title: "重新加载") { + a.vm.scene = .success + a.vm.title = "加载成功" + a.vm.message = "马上就要成功了aaaa" + a.vm.remove(action: 1, 2) + a.vm.update(action: 0, style: .default, title: "OK") { [weak a] in + a?.pop() + } + + } + vm.add(action: .destructive, title: "终止", handler: nil) + vm.add(action: .cancel, title: "取消", handler: nil) + } + + DispatchQueue.main.asyncAfter(deadline: .now()+1) { + if let a = Alert.alerts("hehe").last { + a.update { (vm) in + vm.add(action: .cancel, title: "CANCEL", handler: nil) + } + + } + } + } @@ -80,37 +178,47 @@ class ViewController: UIViewController { g.add(title: "呵呵") g.add(message: "请打开相机权限开关,否则无法进行测量。请打开相机权限开关,否则无法进行测量。") - g.add(action: .default, title: "测试弹窗", action: { [weak self] in + g.add(action: .default, title: "测试弹窗", handler: { [weak self] in self?.testToast() }) - g.add(action: .destructive, title: "测试删除弹窗", action: { [weak self] in + g.add(action: .destructive, title: "测试删除弹窗", handler: { [weak self] in self?.testDelete() }) - g.add(action: .cancel, title: "我知道了", action: nil) + g.add(action: .cancel, title: "我知道了", handler: nil) g.push(to: self) debugPrint("test: ", g) } func testUpdateAction() { - let a = ProHUD.push(alert: .confirm, title: "确认删除", message: "此操作无法撤销") - a.add(action: .destructive, title: "删除") { - a.remove(action: 0, 1).update(scene: .loading, title: "正在删除", message: "请稍后片刻") - }.add(action: .cancel, title: "取消", action: nil) + let a = Alert.push(scene: .confirm, title: "确认删除", message: "此操作无法撤销") + a.update { (vm) in + vm.add(action: .destructive, title: "删除") { + a.update { (vm) in + vm.remove(action: 0, 1) + vm.scene = .loading + vm.title = "正在删除" + vm.message = "请稍后片刻" + } + } + vm.add(action: .cancel, title: "取消", handler: nil) + } } func fastGuard() { - let g = ProHUD.push(guard: self, title: "测试", message: "测试测试") - g.add(action: .default, title: "默认按钮", action: { - - }) - g.add(action: .cancel, title: "取消", action: nil) - g.view.backgroundColor = .clear + Guard.push(to: self) { (vm) in + vm.add(title: "测试") + vm.add(message: "测试测试") + vm.add(action: .default, title: "默认按钮", handler: { + + }) + vm.add(action: .cancel, title: "取消", handler: nil) + vm.vc?.view.backgroundColor = .clear + } -// g.contentView.backgroundColor = UIColor.white } @@ -120,37 +228,6 @@ class ViewController: UIViewController { - -// -// ProHUD.show(alert: .loading, title: "确认删除", message: "此操作不可撤销").timeout(nil) -// -// ProHUD.show(alert: .confirm, title: "确认删除", message: "此操作不可撤销").timeout(3) - -// -// 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") -// }.addAction(style: .cancel, title: nil) { -// -// } - - - - -// ProHUD.show(toast: .loading, title: "正在加载", message: "拉升的反馈老实交代分开就撒开了击快乐圣反馈老实交代分开就撒开了击快乐圣") -// ProHUD.show(toast: .loading, title: "正在加载", message: "哈克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞哈克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞") - } } diff --git a/ProHUD.xcodeproj/project.pbxproj b/ProHUD.xcodeproj/project.pbxproj index dde47e4..bd7f55f 100644 --- a/ProHUD.xcodeproj/project.pbxproj +++ b/ProHUD.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ CD95D26B22E72DB3007559A3 /* ProHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD95D26A22E72DB3007559A3 /* ProHUD.swift */; }; CDB6A07B22EEF06500AF6CF0 /* HUDController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB6A07A22EEF06500AF6CF0 /* HUDController.swift */; }; CDB6A07D22EEF19D00AF6CF0 /* HUDConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB6A07C22EEF19D00AF6CF0 /* HUDConfig.swift */; }; + CDC39CFD22FD6DDF0070E914 /* GuardModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -50,6 +51,7 @@ CD95D26A22E72DB3007559A3 /* ProHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProHUD.swift; sourceTree = ""; }; CDB6A07A22EEF06500AF6CF0 /* HUDController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDController.swift; sourceTree = ""; }; CDB6A07C22EEF19D00AF6CF0 /* HUDConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDConfig.swift; sourceTree = ""; }; + CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardModel.swift; sourceTree = ""; }; DC31EBFAC56868D6096A233A /* Pods-ProHUD.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ProHUD.release.xcconfig"; path = "Pods/Target Support Files/Pods-ProHUD/Pods-ProHUD.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -110,6 +112,7 @@ CD6CD87422F185C200F4FD4A /* GuardController.swift */, CD6CD87822F185D000F4FD4A /* GuardConfig.swift */, CD6CD87A22F185D600F4FD4A /* GuardView.swift */, + CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */, ); path = Guard; sourceTree = ""; @@ -292,6 +295,7 @@ CD16490B22EF09AB0077988C /* AlertModel.swift in Sources */, CD16491222EF0D900077988C /* HUDView.swift in Sources */, CDB6A07B22EEF06500AF6CF0 /* HUDController.swift in Sources */, + CDC39CFD22FD6DDF0070E914 /* GuardModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ProHUD/Alert/AlertConfig.swift b/ProHUD/Alert/AlertConfig.swift index c166c4d..b11711f 100644 --- a/ProHUD/Alert/AlertConfig.swift +++ b/ProHUD/Alert/AlertConfig.swift @@ -11,7 +11,7 @@ import SnapKit import Inspire public extension ProHUD.Configuration { - struct Alert { + class Alert { // MARK: 卡片样式 /// 最大宽度(用于优化横屏或者iPad显示) public var maxWidth = CGFloat(400) @@ -28,6 +28,10 @@ public extension ProHUD.Configuration { /// 图标尺寸 public var iconSize = CGSize(width: 48, height: 48) + public func iconForScene(_ callback: @escaping (ProHUD.Alert.Scene) -> UIImage?) { + privIconForScene = callback + } + // MARK: 文本样式 /// 标题字体 public var titleFont = UIFont.boldSystemFont(ofSize: 22) @@ -48,18 +52,14 @@ public extension ProHUD.Configuration { // MARK: 生命周期 - /// 加载视图 - /// - Parameter callback: 回调代码 - public mutating func loadSubviews(_ callback: @escaping (ProHUD.Alert) -> Void) { - privLoadSubviews = callback - } - - /// 更新视图 - /// - Parameter callback: 回调代码 - public mutating func reloadData(_ callback: @escaping (ProHUD.Alert) -> Void) { + /// 刷新数据和布局 + public func reloadData(_ callback: @escaping (ProHUD.Alert) -> Void) { privReloadData = callback } + /// 非Loading弹窗的默认持续时间 + public var duration = TimeInterval(2) + /// 多少秒后显示强制退出的按钮(只有无按钮的弹窗才会出现) public var forceQuitTimer = TimeInterval(30) @@ -68,77 +68,41 @@ public extension ProHUD.Configuration { /// 加载强制退出按钮 /// - Parameter callback: 回调代码 - public mutating func loadForceQuitButton(_ callback: @escaping (ProHUD.Alert) -> Void) { + public func loadForceQuitButton(_ callback: @escaping (ProHUD.Alert) -> Void) { privLoadForceQuitButton = callback } + + } +} + + +// MARK: - 内部调用 + +internal extension ProHUD.Configuration.Alert { + var reloadData: (ProHUD.Alert) -> Void { + return privReloadData } } // MARK: - 默认实现 -internal extension ProHUD.Configuration.Alert { - var loadSubviews: (ProHUD.Alert) -> Void { - return privLoadSubviews - } - - var reloadData: (ProHUD.Alert) -> Void { - return privReloadData - } - - var loadForceQuitButton: (ProHUD.Alert) -> Void { - return privLoadForceQuitButton - } - - var setupDefaultDuration: (ProHUD.Alert) -> Void { - return { (vc) in - // 如果设置了超时时间,但是增加了按钮 - if let t = vc.model.duration, t > 0 { - if vc.buttonEvents.count > 0 { - vc.duration(nil) - } - } else if vc.model.duration == nil && vc.model.scene != .loading { - // 如果没有设置超时时间 - vc.duration(2) - } - } - } - - var reloadStack: (ProHUD.Alert) -> Void { - return { (vc) in - if vc.textStack.arrangedSubviews.count > 0 { - vc.contentStack.addArrangedSubview(vc.textStack) - } else { - vc.textStack.removeFromSuperview() - } - if vc.actionStack.arrangedSubviews.count > 0 { - vc.contentStack.addArrangedSubview(vc.actionStack) - } else { - vc.actionStack.removeFromSuperview() - } - } - } -} - -fileprivate var privLoadSubviews: (ProHUD.Alert) -> Void = { +fileprivate var privLayoutContentView: (ProHUD.Alert) -> Void = { return { (vc) in - debug(vc, "loadSubviews") - let config = cfg.alert if vc.contentView.superview == nil { vc.view.addSubview(vc.contentView) - vc.contentView.contentView.addSubview(vc.contentStack) - - vc.contentStack.spacing = cfg.alert.margin + cfg.alert.padding - vc.contentView.layer.masksToBounds = true vc.contentView.layer.cornerRadius = cfg.alert.cornerRadius - let maxWidth = CGFloat.maximum(CGFloat.minimum(UIScreen.main.bounds.width * 0.68, cfg.alert.maxWidth), 268) vc.contentView.snp.makeConstraints { (mk) in mk.center.equalToSuperview() mk.width.lessThanOrEqualTo(maxWidth) } + } + if vc.contentStack.superview == nil { + vc.contentView.contentView.addSubview(vc.contentStack) + vc.contentStack.spacing = cfg.alert.margin + cfg.alert.padding vc.contentStack.snp.makeConstraints { (mk) in mk.centerX.equalToSuperview() mk.top.equalToSuperview().offset(cfg.alert.padding) @@ -147,23 +111,13 @@ fileprivate var privLoadSubviews: (ProHUD.Alert) -> Void = { mk.trailing.equalToSuperview().offset(-cfg.alert.padding) } } - } }() -fileprivate var privReloadData: (ProHUD.Alert) -> Void = { - return { (vc) in - debug(vc, "reloadData") - let config = cfg.alert - let isFirstLayout: Bool - // 图标和文字至少有一个,如果都没有添加到视图中,说明是第一次layout - if vc.textStack.superview == nil && vc.imageView?.superview == nil { - isFirstLayout = true - } else { - isFirstLayout = false - } +fileprivate var privIconForScene: (ProHUD.Alert.Scene) -> UIImage? = { + return { (scene) in let imgStr: String - switch vc.model.scene { + switch scene { case .success: imgStr = "ProHUDSuccess" case .warning: @@ -179,7 +133,14 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { default: imgStr = "ProHUDMessage" } - let img = vc.model.icon ?? ProHUD.image(named: imgStr) + return ProHUD.image(named: imgStr) + } +}() + +fileprivate var privUpdateImage: (ProHUD.Alert) -> Void = { + return { (vc) in + let config = cfg.alert + let img = vc.vm.icon ?? privIconForScene(vc.vm.scene) if let imgv = vc.imageView { imgv.image = img } else { @@ -197,9 +158,13 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { vc.imageView = icon } vc.imageView?.layer.removeAllAnimations() - - // text - if vc.model.title?.count ?? 0 > 0 || vc.model.message?.count ?? 0 > 0 { + } +}() + +fileprivate var privUpdateTextStack: (ProHUD.Alert) -> Void = { + return { (vc) in + let config = cfg.alert + 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.75) @@ -212,19 +177,19 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2) } } - if vc.model.title?.count ?? 0 > 0 { + if vc.vm.title?.count ?? 0 > 0 { if let lb = vc.titleLabel { - lb.text = vc.model.title + lb.text = vc.vm.title } else { let title = UILabel() title.textAlignment = .center title.numberOfLines = config.titleMaxLines title.textColor = cfg.primaryLabelColor - title.text = vc.model.title + title.text = vc.vm.title vc.textStack.addArrangedSubview(title) vc.titleLabel = title } - if vc.model.message?.count ?? 0 > 0 { + if vc.vm.message?.count ?? 0 > 0 { // 有message vc.titleLabel?.font = config.titleFont } else { @@ -234,20 +199,20 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { } else { vc.titleLabel?.removeFromSuperview() } - if vc.model.message?.count ?? 0 > 0 { + if vc.vm.message?.count ?? 0 > 0 { if let lb = vc.bodyLabel { - lb.text = vc.model.message + lb.text = vc.vm.message } else { let body = UILabel() body.textAlignment = .center body.font = config.bodyFont body.numberOfLines = config.bodyMaxLines body.textColor = cfg.secondaryLabelColor - body.text = vc.model.message + body.text = vc.vm.message vc.textStack.addArrangedSubview(body) vc.bodyLabel = body } - if vc.model.title?.count ?? 0 > 0 { + if vc.vm.title?.count ?? 0 > 0 { // 有title vc.bodyLabel?.font = config.bodyFont } else { @@ -260,48 +225,35 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { } else { vc.textStack.removeFromSuperview() } - if vc.actionStack.superview != nil { - if isFirstLayout { - vc.contentStack.addArrangedSubview(vc.actionStack) - } else { - vc.actionStack.transform = .init(scaleX: 1, y: 0.001) - UIView.animateForAlert { - vc.contentStack.addArrangedSubview(vc.actionStack) - vc.view.layoutIfNeeded() - } - } + vc.textStack.layoutIfNeeded() + } +}() + + +fileprivate var privUpdateActionStack: (ProHUD.Alert) -> Void = { + return { (vc) in + let config = cfg.alert + if vc.buttonEvents.count > 0 { + // 有按钮 + vc.contentStack.addArrangedSubview(vc.actionStack) // 适配横竖屏和iPad - if isPortrait == false { + if isPortrait == false && vc.buttonEvents.count < 4 { vc.actionStack.axis = .horizontal vc.actionStack.alignment = .fill vc.actionStack.distribution = .fillEqually } - vc.actionStack.snp.makeConstraints { (mk) in + vc.actionStack.snp.makeConstraints { (mk) in mk.width.greaterThanOrEqualTo(200) mk.leading.trailing.equalToSuperview() } - if isFirstLayout == false { - UIView.animateForAlert { - vc.actionStack.transform = .identity - } - } - - } - - if isFirstLayout { - vc.view.layoutIfNeeded() - vc.imageView?.transform = .init(scaleX: 0.75, y: 0.75) - UIView.animateForAlert { - vc.imageView?.transform = .identity - vc.view.layoutIfNeeded() - } } else { - UIView.animateForAlert { - vc.view.layoutIfNeeded() + // 无按钮 + for v in vc.actionStack.arrangedSubviews { + v.removeFromSuperview() } + vc.actionStack.removeFromSuperview() } - - + vc.actionStack.layoutIfNeeded() } }() @@ -335,12 +287,74 @@ fileprivate var privLoadForceQuitButton: (ProHUD.Alert) -> Void = { bg.transform = .identity } vc.addTouchUpAction(for: btn) { [weak vc] in - debug("点击了隐藏") - vc?.model.forceQuitCallback?() + debug("点击了【\(config.forceQuitTitle)】") + vc?.vm.forceQuitCallback?() vc?.pop() } } }() - - +/// 刷新数据和布局 +fileprivate var privReloadData: (ProHUD.Alert) -> Void = { + return { (vc) in + debug(vc, "reloadData") + let config = cfg.alert + let isFirstLayout: Bool + if vc.contentView.superview == nil { + isFirstLayout = true + // 布局主容器视图 + privLayoutContentView(vc) + } else { + isFirstLayout = false + } + // 更新图片 + privUpdateImage(vc) + + // 更新文本容器 + privUpdateTextStack(vc) + + // 更新操作容器 + privUpdateActionStack(vc) + vc.contentStack.layoutIfNeeded() + vc.contentView.layoutIfNeeded() + + // 动画 + if isFirstLayout { + vc.view.layoutIfNeeded() + vc.imageView?.transform = .init(scaleX: 0.75, y: 0.75) + UIView.animateForAlert { + vc.view.layoutIfNeeded() + vc.imageView?.transform = .identity + } + } else { + UIView.animateForAlert { + vc.view.layoutIfNeeded() + } + } + + // 设置默认持续时间 + if vc.vm.duration == nil { + if vc.vm.scene == .loading { + vc.vm.duration = 0 + } else { + vc.vm.duration = config.duration + } + } + + // 强制退出按钮 + vc.vm.forceQuitTimerBlock?.cancel() + if vc.buttonEvents.count == 0 { + vc.vm.forceQuitTimerBlock = DispatchWorkItem(block: { [weak vc] in + if let vc = vc { + if vc.buttonEvents.count == 0 { + privLoadForceQuitButton(vc) + } + } + }) + DispatchQueue.main.asyncAfter(deadline: .now() + config.forceQuitTimer, execute: vc.vm.forceQuitTimerBlock!) + } else { + vc.vm.forceQuitTimerBlock = nil + } + + } +}() diff --git a/ProHUD/Alert/AlertController.swift b/ProHUD/Alert/AlertController.swift index 41031ad..b19133b 100644 --- a/ProHUD/Alert/AlertController.swift +++ b/ProHUD/Alert/AlertController.swift @@ -53,28 +53,32 @@ public extension ProHUD { }() /// 视图模型 - public var model = ViewModel() - + public var vm = ViewModel() // MARK: 生命周期 + internal var isLoadFinished = false /// 实例化 /// - Parameter scene: 场景 /// - Parameter title: 标题 /// - Parameter message: 内容 /// - Parameter icon: 图标 - public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil, actions: ((Alert) -> Void)? = nil) { + public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil, actions: ((inout ViewModel) -> Void)? = nil) { self.init() - view.tintColor = cfg.alert.tintColor - model.scene = scene - model.title = title - model.message = message - model.icon = icon - actions?(self) - willLayoutSubviews() - + vm.vc = self + vm.scene = scene + vm.title = title + vm.message = message + vm.icon = icon + actions?(&vm) } + public override func viewDidLoad() { + super.viewDidLoad() + view.tintColor = cfg.alert.tintColor + cfg.alert.reloadData(self) + isLoadFinished = true + } } @@ -88,7 +92,6 @@ public extension Alert { /// 推入屏幕 @discardableResult func push() -> Alert { - let hud = ProHUD.shared if Alert.alerts.contains(self) == false { let window = Alert.getAlertWindow(self) window.makeKeyAndVisible() @@ -104,11 +107,6 @@ public extension Alert { Alert.alerts.append(self) } Alert.updateAlertsLayout() - - // setup duration - if let _ = model.duration, model.durationBlock == nil { - duration(model.duration) - } return self } @@ -137,22 +135,11 @@ public extension Alert { // MARK: 设置函数 - /// 添加按钮 - /// - Parameter style: 样式 - /// - Parameter text: 标题 - /// - Parameter handler: 事件处理 - @discardableResult func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { - let btn = privAddButton(custom: Button.actionButton(title: title), action: handler) - if let b = btn as? Button { - b.update(style: style) - } - return btn - } /// 最小化事件 /// - Parameter callback: 事件回调 @discardableResult func didForceQuit(_ callback: (() -> Void)?) -> Alert { - model.forceQuitCallback = callback + vm.forceQuitCallback = callback return self } @@ -163,77 +150,29 @@ public extension Alert { return self } - /// 设置持续时间 - /// - Parameter duration: 持续时间 - @discardableResult func duration(_ duration: TimeInterval?) -> Alert { - model.setupDuration(duration: duration) { [weak self] in - self?.pop() - } - return self - } - /// 更新 - /// - Parameter scene: 场景 - /// - Parameter title: 标题 - /// - Parameter message: 正文 - @discardableResult func update(scene: Scene, title: String?, message: String?) -> Alert { - model.scene = scene - model.title = title - model.message = message - willLayoutSubviews() - return self - } - - /// 更新图标 - /// - Parameter icon: 图标 - @discardableResult func update(icon: UIImage?) -> Alert { - model.icon = icon + /// - Parameter callback: 回调 + func update(_ callback: ((inout Alert.ViewModel) -> Void)? = nil) { + callback?(&vm) cfg.alert.reloadData(self) - return self } - @discardableResult func animate(rotate: Bool) -> Alert { + func animate(rotate: Bool) { if rotate { DispatchQueue.main.async { let ani = CABasicAnimation(keyPath: "transform.rotation.z") - ani.toValue = M_PI*2.0 - ani.duration = 2 + ani.toValue = Double.pi * 2.0 + ani.duration = 3 ani.repeatCount = 10000 self.imageView?.layer.add(ani, forKey: "rotationAnimation") } } else { imageView?.layer.removeAllAnimations() } - return self } - /// 更新按钮 - /// - Parameter index: 索引 - /// - Parameter style: 样式 - /// - Parameter title: 标题 - /// - Parameter action: 事件 - @discardableResult func update(action index: Int, style: UIAlertAction.Style, title: String?, action: (() -> Void)?) -> Alert { - 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) - } - btn.layoutIfNeeded() - if let ac = action { - addTouchUpAction(for: btn, action: ac) - } - } - return self - } - /// 移除按钮 - /// - Parameter index: 索引 - @discardableResult func remove(action index: Int...) -> Alert { - for (i, idx) in index.enumerated() { - privRemoveAction(index: idx-i) - } - return self - } + } @@ -247,7 +186,7 @@ public extension Alert { /// - Parameter title: 标题 /// - Parameter message: 正文 /// - Parameter actions: 更多操作 - @discardableResult class func push(alert scene: Alert.Scene, title: String? = nil, message: String? = nil, actions: ((Alert) -> Void)? = nil) -> Alert { + @discardableResult class func push(scene: Alert.Scene, title: String? = nil, message: String? = nil, actions: ((inout Alert.ViewModel) -> Void)? = nil) -> Alert { return Alert(scene: scene, title: title, message: message, actions: actions).push() } @@ -256,7 +195,7 @@ public extension Alert { class func alerts(_ identifier: String?) -> [Alert] { var aa = [Alert]() for a in Alert.alerts { - if a.identifier == identifier { + if a.vm.identifier == identifier { aa.append(a) } } @@ -281,7 +220,7 @@ public extension Alert { // MARK: - 私有 -fileprivate extension Alert { +internal extension Alert { /// 移除按钮 /// - Parameter index: 索引 @@ -298,52 +237,56 @@ fileprivate extension Alert { if self.actionStack.arrangedSubviews.count == 0 { self.actionStack.removeFromSuperview() } - willLayoutSubviews() UIView.animateForAlert { self.view.layoutIfNeeded() } return self } - func willLayoutSubviews() { - model.setupWillLayout(duration: 0.001) { [weak self] in - if let a = self { - // 布局 - cfg.alert.loadSubviews(a) - cfg.alert.reloadData(a) - cfg.alert.setupDefaultDuration(a) - // 强制退出按钮 - a.model.setupForceQuit(duration: cfg.alert.forceQuitTimer) { [weak self] in - if let aa = self, aa.actionStack.superview == nil { - cfg.alert.loadForceQuitButton(aa) - } - } - } - } - } - - @discardableResult func privAddButton(custom button: UIButton, action: (() -> Void)?) -> UIButton { - model.duration = nil + @discardableResult func privAddButton(custom button: UIButton, at index: Int? = nil, handler: (() -> Void)?) -> UIButton { if actionStack.superview == nil { contentStack.addArrangedSubview(actionStack) + contentStack.layoutIfNeeded() } - self.view.layoutIfNeeded() - button.transform = .init(scaleX: 1, y: 0.001) - actionStack.addArrangedSubview(button) - UIView.animateForAlert { - button.transform = .identity - self.view.layoutIfNeeded() + if let idx = index, idx < actionStack.arrangedSubviews.count { + actionStack.insertArrangedSubview(button, at: idx) + } else { + actionStack.addArrangedSubview(button) } + addTouchUpAction(for: button) { [weak self] in - action?() + handler?() if button.tag == UIAlertAction.Style.cancel.rawValue { self?.pop() } } - willLayoutSubviews() + if isLoadFinished { + actionStack.layoutIfNeeded() + UIView.animateForAlert { + self.view.layoutIfNeeded() + } + } return button } + func privUpdateButton(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() + } + } + } + } + } fileprivate extension Alert { diff --git a/ProHUD/Alert/AlertModel.swift b/ProHUD/Alert/AlertModel.swift index ca210b0..eaa8223 100644 --- a/ProHUD/Alert/AlertModel.swift +++ b/ProHUD/Alert/AlertModel.swift @@ -35,20 +35,50 @@ public extension Alert { struct ViewModel { + /// ID标识 + public var identifier = String(Date().timeIntervalSince1970) + /// 使用场景 public var scene = Scene.default /// 标题 - public var title: String? + public var title: String? { + didSet { + vc?.titleLabel?.text = title + } + } /// 正文 - public var message: String? + public var message: String? { + didSet { + vc?.bodyLabel?.text = message + } + } /// 图标 - public var icon: UIImage? + public var icon: UIImage? { + didSet { + vc?.imageView?.image = icon + } + } - /// 持续时间 - internal var duration: TimeInterval? + /// 持续时间(为空代表根据场景不同的默认配置,为0代表无穷大) + public var duration: TimeInterval? { + didSet { + durationBlock?.cancel() + if let t = duration, t > 0 { + let v = vc + durationBlock = DispatchWorkItem(block: { + v?.pop() + }) + DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: durationBlock!) + } else { + durationBlock = nil + } + } + } + + public weak var vc: Alert? /// 持续时间 internal var durationBlock: DispatchWorkItem? @@ -59,40 +89,56 @@ public extension Alert { /// 强制退出代码 internal var forceQuitCallback: (() -> Void)? - internal var willLayoutBlock: DispatchWorkItem? - - internal mutating func setupDuration(duration: TimeInterval?, callback: @escaping () -> Void) { - self.duration = duration - durationBlock?.cancel() - if let t = duration, t > 0 { - durationBlock = DispatchWorkItem(block: callback) - DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: durationBlock!) - } else { - durationBlock = nil - } - } - - internal mutating func setupForceQuit(duration: TimeInterval?, callback: @escaping () -> Void) { - forceQuitTimerBlock?.cancel() - if let t = duration, t > 0 { - forceQuitTimerBlock = DispatchWorkItem(block: callback) - DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: forceQuitTimerBlock!) - } else { - forceQuitTimerBlock = nil - } - } - - internal mutating func setupWillLayout(duration: TimeInterval?, callback: @escaping () -> Void) { - willLayoutBlock?.cancel() - if let t = duration, t > 0 { - willLayoutBlock = DispatchWorkItem(block: callback) - DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: willLayoutBlock!) - } else { - willLayoutBlock = nil - } - } - } } +public extension Alert.ViewModel { + + /// 添加按钮 + /// - Parameter style: 样式 + /// - Parameter text: 标题 + /// - Parameter handler: 事件处理 + @discardableResult mutating func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + duration = 0 + let btn = vc?.privAddButton(custom: Alert.Button.actionButton(title: title), handler: handler) + if let b = btn as? Alert.Button { + b.update(style: style) + } + return btn! + } + + /// 插入按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter handler: 事件处理 + @discardableResult mutating func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + duration = 0 + let btn = vc?.privAddButton(custom: Alert.Button.actionButton(title: title), at: index, handler: handler) + if let b = btn as? Alert.Button { + b.update(style: style) + } + return btn! + } + + /// 更新按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter handler: 事件处理 + mutating func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) { + vc?.privUpdateButton(action: index, style: style, title: title, handler) + } + + /// 移除按钮 + /// - Parameter index: 索引 + mutating func remove(action index: Int...) { + guard let alert = self.vc else { return } + for (i, idx) in index.enumerated() { + alert.privRemoveAction(index: idx-i) + } + } + + +} diff --git a/ProHUD/Alert/AlertView.swift b/ProHUD/Alert/AlertView.swift index a384214..607c668 100644 --- a/ProHUD/Alert/AlertView.swift +++ b/ProHUD/Alert/AlertView.swift @@ -24,6 +24,7 @@ internal extension Alert { backgroundColor = cfg.dynamicColor.withAlphaComponent(0.04) contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5) } else { + backgroundColor = .clear contentEdgeInsets = .init(top: pd*0.5, left: pd*1.5, bottom: pd*0.5, right: pd*1.5) } switch style { diff --git a/ProHUD/Guard/GuardConfig.swift b/ProHUD/Guard/GuardConfig.swift index 8e4b61e..bcb3ccd 100644 --- a/ProHUD/Guard/GuardConfig.swift +++ b/ProHUD/Guard/GuardConfig.swift @@ -86,11 +86,7 @@ fileprivate var privLoadSubviews: (ProHUD.Guard) -> Void = { return { (vc) in debug(vc, "loadSubviews") let config = cfg.guard - // background - vc.view.tintColor = config.tintColor - vc.view.backgroundColor = UIColor(white: 0, alpha: 0) - vc.view.addSubview(vc.contentView) - vc.contentView.contentView.addSubview(vc.contentStack) + } }() @@ -98,6 +94,11 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = { return { (vc) in debug(vc, "reloadData") let config = cfg.guard + // background + vc.view.tintColor = config.tintColor + vc.view.backgroundColor = UIColor(white: 0, alpha: 0) + vc.view.addSubview(vc.contentView) + vc.contentView.contentView.addSubview(vc.contentStack) // 更新布局 var width = UIScreen.main.bounds.width if width > config.cardMaxWidth { diff --git a/ProHUD/Guard/GuardController.swift b/ProHUD/Guard/GuardController.swift index 485fc96..04f2ba9 100644 --- a/ProHUD/Guard/GuardController.swift +++ b/ProHUD/Guard/GuardController.swift @@ -50,23 +50,26 @@ public extension ProHUD { /// 背景颜色 public var backgroundColor: UIColor? = UIColor(white: 0, alpha: 0.5) + public var vm = ViewModel() + // MARK: 生命周期 /// 实例化 /// - Parameter title: 标题 /// - Parameter message: 内容 /// - Parameter actions: 更多操作 - public convenience init(title: String? = nil, message: String? = nil, actions: ((Guard) -> Void)? = nil) { + public convenience init(title: String? = nil, message: String? = nil, actions: ((inout ViewModel) -> Void)? = nil) { self.init() - - view.tintColor = cfg.guard.tintColor + vm.vc = self if let _ = title { add(title: title) } if let _ = message { add(message: message) } - actions?(self) + actions?(&vm) + + view.tintColor = cfg.guard.tintColor cfg.guard.loadSubviews(self) cfg.guard.reloadData(self) cfg.guard.reloadStack(self) @@ -91,7 +94,7 @@ public extension Guard { /// 推入某个视图控制器 /// - Parameter viewController: 视图控制器 - func push(to viewController: UIViewController? = nil) -> Guard { + @discardableResult func push(to viewController: UIViewController? = nil) -> Guard { func f(_ vc: UIViewController) { view.layoutIfNeeded() vc.addChild(self) @@ -226,7 +229,7 @@ public extension Guard { /// - Parameter title: 标题 /// - Parameter message: 正文 /// - Parameter icon: 图标 - @discardableResult class func push(to viewController: UIViewController? = nil, actions: ((Guard) -> Void)? = nil) -> Guard { + @discardableResult class func push(to viewController: UIViewController? = nil, actions: ((inout ViewModel) -> Void)? = nil) -> Guard { return Guard(actions: actions).push(to: viewController) } @@ -239,7 +242,7 @@ public extension Guard { if child.isKind(of: Guard.self) { if let g = child as? Guard { if let id = identifier { - if g.identifier == id { + if g.vm.identifier == id { gg.append(g) } } else { @@ -272,7 +275,7 @@ public extension Guard { // MARK: - 私有 -fileprivate extension Guard { +internal extension Guard { /// 点击事件 /// - Parameter sender: 手势 @@ -317,6 +320,5 @@ fileprivate extension Guard { } - } diff --git a/ProHUD/Guard/GuardModel.swift b/ProHUD/Guard/GuardModel.swift new file mode 100644 index 0000000..e9e483a --- /dev/null +++ b/ProHUD/Guard/GuardModel.swift @@ -0,0 +1,80 @@ +// +// GuardModel.swift +// ProHUD +// +// Created by xaoxuu on 2019/8/9. +// Copyright © 2019 Titan Studio. All rights reserved. +// + +import UIKit + +public extension Guard { + + struct ViewModel { + + /// ID标识 + public var identifier = String(Date().timeIntervalSince1970) + + public weak var vc: Guard? + + } + +} + +public extension Guard.ViewModel { + + /// 加载一个标题 + /// - Parameter text: 文本 + @discardableResult func add(title: String?) -> UILabel { + return vc!.add(title: title) + } + + /// 加载一个副标题 + /// - Parameter text: 文本 + @discardableResult func add(subTitle: String?) -> UILabel { + return vc!.add(subTitle: subTitle) + } + + /// 加载一段正文 + /// - Parameter text: 文本 + @discardableResult func add(message: String?) -> UILabel { + return vc!.add(message: message) + } + + /// 加载一个按钮 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter action: 事件 + @discardableResult func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + return vc!.add(action: style, title: title, handler: handler) + } + + /// 插入按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter handler: 事件处理 + @discardableResult mutating func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + + return UIButton() + } + + /// 更新按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter handler: 事件处理 + mutating func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) { + // vc?.privUpdateButton(action: index, style: style, title: title, handler) + } + + /// 移除按钮 + /// - Parameter index: 索引 + func remove(action index: Int...) { + for (i, idx) in index.enumerated() { + vc!.privRemoveAction(index: idx-i) + } + } + + +} diff --git a/ProHUD/HUDController.swift b/ProHUD/HUDController.swift index bf6e53f..af7b0a5 100644 --- a/ProHUD/HUDController.swift +++ b/ProHUD/HUDController.swift @@ -10,9 +10,6 @@ import UIKit public class HUDController: UIViewController { - /// ID标识 - public var identifier = String(Date().timeIntervalSince1970) - /// 消失回调 internal var disappearCallback: (() -> Void)? diff --git a/ProHUD/Toast/ToastController.swift b/ProHUD/Toast/ToastController.swift index dadcbee..dd62b52 100644 --- a/ProHUD/Toast/ToastController.swift +++ b/ProHUD/Toast/ToastController.swift @@ -243,7 +243,7 @@ public extension Toast { /// - Parameter title: 标题 /// - Parameter message: 内容 /// - Parameter actions: 更多操作 - @discardableResult class func push(toast scene: Toast.Scene, title: String? = nil, message: String? = nil, actions: ((Toast) -> Void)? = nil) -> Toast { + @discardableResult class func push(scene: Toast.Scene, title: String? = nil, message: String? = nil, actions: ((Toast) -> Void)? = nil) -> Toast { return Toast(scene: scene, title: title, message: message, actions: actions).push() } @@ -252,7 +252,7 @@ public extension Toast { class func toasts(_ identifier: String?) -> [Toast] { var tt = [Toast]() for t in toasts { - if t.identifier == identifier { + if t.model.identifier == identifier { tt.append(t) } } diff --git a/ProHUD/Toast/ToastModel.swift b/ProHUD/Toast/ToastModel.swift index 2cafc4a..f3ad850 100644 --- a/ProHUD/Toast/ToastModel.swift +++ b/ProHUD/Toast/ToastModel.swift @@ -35,6 +35,9 @@ public extension Toast { struct ViewModel { + /// ID标识 + public var identifier = String(Date().timeIntervalSince1970) + /// 使用场景 public var scene = Scene.default