diff --git a/Example/Example/AppDelegate.swift b/Example/Example/AppDelegate.swift index dd25882..3acc6da 100644 --- a/Example/Example/AppDelegate.swift +++ b/Example/Example/AppDelegate.swift @@ -22,8 +22,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { - - return true } diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 62dd439..3838190 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -17,6 +17,8 @@ class ViewController: UIViewController { ProHUD.config { (cfg) in + cfg.rootViewController = self + cfg.alert { (a) in a.duration = 1 a.forceQuitTimer = 3 @@ -29,7 +31,11 @@ class ViewController: UIViewController { } cfg.toast { (t) in -// t.iconSize = .init(width: 300, height: 30) +// t.iconSize = .init(width: 30, height: 30) +// t.iconForScene { (scene) -> UIImage? in +// return UIImage(named: "icon_download") +// } + } } @@ -51,6 +57,7 @@ class ViewController: UIViewController { } a.update { (vm) in vm.add(action: .default, title: "OK", handler: nil) + } // a.update() // Alert.push(scene: .loading, title: "Loading") { (a) in @@ -127,67 +134,107 @@ class ViewController: UIViewController { } func testToast() { - let t = Toast(scene: .loading, title: "正在加载", message: "请稍候片刻") - - let a = Alert.push(scene : .loading, title: "正在加载", message: "请稍候片刻") - a.didForceQuit { - t.push() - } - - t.didTapped { [weak t] in - t?.pop() - Alert.push(scene: .loading, title: "正在加载", message: "马上就要成功了") - DispatchQueue.main.asyncAfter(deadline: .now()+1) { - 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) - } - - } - } - + func f() { + Toast.push { (vm) in + vm.scene = .error + vm.title = "正在加载" + // vm.duration = 1 + vm.message = "请稍候片刻" + }.didTapped { + debugPrint("didTapped") + }.didDisappear { + debugPrint("didDisappear") } - } + f() + + +// let t = Toast(scene: .loading, title: "正在加载", message: "请稍候片刻") + +// let a = Alert.push(scene : .loading, title: "正在加载", message: "请稍候片刻") +// a.didForceQuit { +// t.push() +// } +// +// t.didTapped { [weak t] in +// t?.pop() +// Alert.push(scene: .loading, title: "正在加载", message: "马上就要成功了") +// DispatchQueue.main.asyncAfter(deadline: .now()+1) { +// 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) +// } +// +// } +// } +// +// +// } +// +// } + } func testGuard() { - let g = ProHUD.Guard(title: "请求权限", message: "请打开相机权限开关,否则无法进行测量。") - - g.add(title: "呵呵") - g.add(message: "请打开相机权限开关,否则无法进行测量。请打开相机权限开关,否则无法进行测量。") - g.add(action: .default, title: "测试弹窗", handler: { [weak self] in - self?.testToast() - }) - g.add(action: .destructive, title: "测试删除弹窗", handler: { [weak self] in - self?.testDelete() - }) - g.add(action: .cancel, title: "我知道了", handler: nil) - - g.push(to: self) - debugPrint("test: ", g) + Guard.push { (vm) in + vm.add(title: "大标题") + vm.add(subTitle: "副标题") + vm.add(message: "请打开相机权限开关,否则无法进行测量。请打开相机权限开关,否则无法进行测量。") + vm.add(action: .default, title: "OK") { [weak vm] in + vm?.insert(action: 0, style: .destructive, title: "Del") { [weak vm] in + vm?.update(action: 0, style: .destructive, title: "Delete") { + vm?.remove(action: 0) + } + } + } + vm.insert(action: 0, style: .destructive, title: "Del") { [weak vm] in + + vm?.update(action: 0, style: .destructive, title: "Delete") { + vm?.remove(action: 0) + } + } + vm.add(action: .cancel, title: "Cancel") { + + } + + + } +// let g = ProHUD.Guard(title: "请求权限", message: "请打开相机权限开关,否则无法进行测量。") +// +// g.add(title: "呵呵") +// g.add(message: "请打开相机权限开关,否则无法进行测量。请打开相机权限开关,否则无法进行测量。") +// g.add(action: .default, title: "测试弹窗", handler: { [weak self] in +// self?.testToast() +// }) +// g.add(action: .destructive, title: "测试删除弹窗", handler: { [weak self] in +// self?.testDelete() +// }) +// g.add(action: .cancel, title: "我知道了", handler: nil) +// +// g.push(to: self) +// debugPrint("test: ", g) } func testUpdateAction() { diff --git a/ProHUD.xcodeproj/project.pbxproj b/ProHUD.xcodeproj/project.pbxproj index bd7f55f..a8e2f4c 100644 --- a/ProHUD.xcodeproj/project.pbxproj +++ b/ProHUD.xcodeproj/project.pbxproj @@ -10,16 +10,13 @@ 1AE9C44ABAF3F797A5518CE8 /* Pods_ProHUD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2011798511AD590A613E54E /* Pods_ProHUD.framework */; }; CD16490B22EF09AB0077988C /* AlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16490A22EF09AB0077988C /* AlertModel.swift */; }; CD16490D22EF09B40077988C /* AlertConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16490C22EF09B40077988C /* AlertConfig.swift */; }; - CD16490F22EF09D50077988C /* AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16490E22EF09D50077988C /* AlertView.swift */; }; CD16491222EF0D900077988C /* HUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16491122EF0D900077988C /* HUDView.swift */; }; CD16491422EF12220077988C /* ProHUD.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD16491322EF12220077988C /* ProHUD.xcassets */; }; CD6CD86C22F1858F00F4FD4A /* ToastModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD86B22F1858F00F4FD4A /* ToastModel.swift */; }; - CD6CD86E22F185A000F4FD4A /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD86D22F185A000F4FD4A /* ToastView.swift */; }; CD6CD87022F185A700F4FD4A /* ToastController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD86F22F185A700F4FD4A /* ToastController.swift */; }; CD6CD87222F185AF00F4FD4A /* ToastConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD87122F185AF00F4FD4A /* ToastConfig.swift */; }; CD6CD87522F185C200F4FD4A /* GuardController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD87422F185C200F4FD4A /* GuardController.swift */; }; CD6CD87922F185D000F4FD4A /* GuardConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD87822F185D000F4FD4A /* GuardConfig.swift */; }; - CD6CD87B22F185D600F4FD4A /* GuardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD87A22F185D600F4FD4A /* GuardView.swift */; }; CD95D22122E72C4C007559A3 /* ProHUD.h in Headers */ = {isa = PBXBuildFile; fileRef = CD95D21F22E72C4C007559A3 /* ProHUD.h */; settings = {ATTRIBUTES = (Public, ); }; }; CD95D26922E72DA1007559A3 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD95D26822E72DA1007559A3 /* AlertController.swift */; }; CD95D26B22E72DB3007559A3 /* ProHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD95D26A22E72DB3007559A3 /* ProHUD.swift */; }; @@ -33,16 +30,13 @@ C2011798511AD590A613E54E /* Pods_ProHUD.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ProHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CD16490A22EF09AB0077988C /* AlertModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertModel.swift; sourceTree = ""; }; CD16490C22EF09B40077988C /* AlertConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertConfig.swift; sourceTree = ""; }; - CD16490E22EF09D50077988C /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = ""; }; CD16491122EF0D900077988C /* HUDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDView.swift; sourceTree = ""; }; CD16491322EF12220077988C /* ProHUD.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = ProHUD.xcassets; sourceTree = ""; }; CD6CD86B22F1858F00F4FD4A /* ToastModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastModel.swift; sourceTree = ""; }; - CD6CD86D22F185A000F4FD4A /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; CD6CD86F22F185A700F4FD4A /* ToastController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastController.swift; sourceTree = ""; }; CD6CD87122F185AF00F4FD4A /* ToastConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastConfig.swift; sourceTree = ""; }; CD6CD87422F185C200F4FD4A /* GuardController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardController.swift; sourceTree = ""; }; CD6CD87822F185D000F4FD4A /* GuardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardConfig.swift; sourceTree = ""; }; - CD6CD87A22F185D600F4FD4A /* GuardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardView.swift; sourceTree = ""; }; CD95D21C22E72C4C007559A3 /* ProHUD.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ProHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CD95D21F22E72C4C007559A3 /* ProHUD.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProHUD.h; sourceTree = ""; }; CD95D22022E72C4C007559A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -90,7 +84,6 @@ CD95D26822E72DA1007559A3 /* AlertController.swift */, CD16490A22EF09AB0077988C /* AlertModel.swift */, CD16490C22EF09B40077988C /* AlertConfig.swift */, - CD16490E22EF09D50077988C /* AlertView.swift */, ); path = Alert; sourceTree = ""; @@ -99,7 +92,6 @@ isa = PBXGroup; children = ( CD6CD86B22F1858F00F4FD4A /* ToastModel.swift */, - CD6CD86D22F185A000F4FD4A /* ToastView.swift */, CD6CD86F22F185A700F4FD4A /* ToastController.swift */, CD6CD87122F185AF00F4FD4A /* ToastConfig.swift */, ); @@ -111,7 +103,6 @@ children = ( CD6CD87422F185C200F4FD4A /* GuardController.swift */, CD6CD87822F185D000F4FD4A /* GuardConfig.swift */, - CD6CD87A22F185D600F4FD4A /* GuardView.swift */, CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */, ); path = Guard; @@ -280,12 +271,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CD6CD87B22F185D600F4FD4A /* GuardView.swift in Sources */, CD95D26922E72DA1007559A3 /* AlertController.swift in Sources */, CD6CD87922F185D000F4FD4A /* GuardConfig.swift in Sources */, CDB6A07D22EEF19D00AF6CF0 /* HUDConfig.swift in Sources */, - CD16490F22EF09D50077988C /* AlertView.swift in Sources */, - CD6CD86E22F185A000F4FD4A /* ToastView.swift in Sources */, CD6CD87222F185AF00F4FD4A /* ToastConfig.swift in Sources */, CD6CD87522F185C200F4FD4A /* GuardController.swift in Sources */, CD6CD87022F185A700F4FD4A /* ToastController.swift in Sources */, diff --git a/ProHUD/Alert/AlertConfig.swift b/ProHUD/Alert/AlertConfig.swift index b11711f..0d075e6 100644 --- a/ProHUD/Alert/AlertConfig.swift +++ b/ProHUD/Alert/AlertConfig.swift @@ -22,12 +22,14 @@ public extension ProHUD.Configuration { /// 填充:元素内部控件距离元素边界的距离 public var padding = CGFloat(16) - // MARK: 图标样式 - /// 图标、default按钮的颜色 + /// 颜色 public var tintColor: UIColor? + + // MARK: 图标样式 /// 图标尺寸 public var iconSize = CGSize(width: 48, height: 48) - + /// 某个场景的默认图片 + /// - Parameter callback: 回调 public func iconForScene(_ callback: @escaping (ProHUD.Alert.Scene) -> UIImage?) { privIconForScene = callback } @@ -38,7 +40,7 @@ public extension ProHUD.Configuration { /// 标题最多行数 public var titleMaxLines = Int(1) - /// 加粗正文字体(如果只有标题或者只有正文,则显示这种字体) + /// 加粗字体(如果只有标题或者只有正文,则显示这种字体) public var boldTextFont = UIFont.boldSystemFont(ofSize: 18) /// 正文字体 @@ -78,7 +80,6 @@ public extension ProHUD.Configuration { // MARK: - 内部调用 - internal extension ProHUD.Configuration.Alert { var reloadData: (ProHUD.Alert) -> Void { return privReloadData @@ -87,7 +88,6 @@ internal extension ProHUD.Configuration.Alert { // MARK: - 默认实现 - fileprivate var privLayoutContentView: (ProHUD.Alert) -> Void = { return { (vc) in if vc.contentView.superview == nil { @@ -263,7 +263,7 @@ fileprivate var privLoadForceQuitButton: (ProHUD.Alert) -> Void = { let config = cfg.alert let btn = ProHUD.Alert.Button.forceQuitButton() btn.setTitle(cfg.alert.forceQuitTitle, for: .normal) - let bg = ProHUD.BlurView() + let bg = createBlurView() bg.layer.masksToBounds = true bg.layer.cornerRadius = config.cornerRadius if let last = vc.view.subviews.last { diff --git a/ProHUD/Alert/AlertController.swift b/ProHUD/Alert/AlertController.swift index b19133b..d93e879 100644 --- a/ProHUD/Alert/AlertController.swift +++ b/ProHUD/Alert/AlertController.swift @@ -19,7 +19,7 @@ public extension ProHUD { internal static var alertWindow: UIWindow? /// 内容视图 - public var contentView = BlurView() + public var contentView = createBlurView() /// 内容容器(包括icon、textStack、actionStack) public var contentStack: StackContainer = { @@ -88,12 +88,10 @@ public extension ProHUD { public extension Alert { - // MARK: 生命周期函数 - /// 推入屏幕 @discardableResult func push() -> Alert { if Alert.alerts.contains(self) == false { - let window = Alert.getAlertWindow(self) + let window = Alert.privGetAlertWindow(self) window.makeKeyAndVisible() window.resignKey() window.addSubview(view) @@ -106,14 +104,14 @@ public extension Alert { } Alert.alerts.append(self) } - Alert.updateAlertsLayout() + Alert.privUpdateAlertsLayout() return self } /// 弹出屏幕 func pop() { - let window = Alert.getAlertWindow(self) - Alert.removeItemFromArray(alert: self) + let window = Alert.privGetAlertWindow(self) + Alert.privRemoveItemFromArray(alert: self) UIView.animateForAlertBuildOut(animations: { self.view.alpha = 0 self.view.transform = .init(scaleX: 1.08, y: 1.08) @@ -132,29 +130,24 @@ public extension Alert { } } - - // MARK: 设置函数 + /// 更新 + /// - Parameter callback: 回调 + func update(_ callback: ((inout ViewModel) -> Void)? = nil) { + callback?(&vm) + cfg.alert.reloadData(self) + } /// 最小化事件 /// - Parameter callback: 事件回调 - @discardableResult func didForceQuit(_ callback: (() -> Void)?) -> Alert { + func didForceQuit(_ callback: (() -> Void)?) { vm.forceQuitCallback = callback - return self } /// 消失事件 /// - Parameter callback: 事件回调 - @discardableResult func didDisappear(_ callback: (() -> Void)?) -> Alert { + func didDisappear(_ callback: (() -> Void)?) { disappearCallback = callback - return self - } - - /// 更新 - /// - Parameter callback: 回调 - func update(_ callback: ((inout Alert.ViewModel) -> Void)? = nil) { - callback?(&vm) - cfg.alert.reloadData(self) } func animate(rotate: Bool) { @@ -177,8 +170,7 @@ public extension Alert { } -// MARK: 类函数 - +// MARK: - 实例管理器 public extension Alert { /// 推入屏幕 @@ -186,7 +178,7 @@ public extension Alert { /// - Parameter title: 标题 /// - Parameter message: 正文 /// - Parameter actions: 更多操作 - @discardableResult class func push(scene: Alert.Scene, title: String? = nil, message: String? = nil, actions: ((inout Alert.ViewModel) -> Void)? = nil) -> Alert { + @discardableResult class func push(scene: Alert.Scene = .default, title: String? = nil, message: String? = nil, actions: ((inout ViewModel) -> Void)? = nil) -> Alert { return Alert(scene: scene, title: title, message: message, actions: actions).push() } @@ -218,45 +210,30 @@ public extension Alert { } -// MARK: - 私有 +// MARK: - 创建和设置 internal extension Alert { - /// 移除按钮 - /// - Parameter index: 索引 - @discardableResult func privRemoveAction(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() - } - UIView.animateForAlert { - self.view.layoutIfNeeded() - } - return self - } - @discardableResult func privAddButton(custom button: UIButton, at index: Int? = nil, handler: (() -> Void)?) -> UIButton { + /// 加载一个按钮 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter action: 事件 + @discardableResult func insert(action index: Int?, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + let btn = Button.actionButton(title: title) + if let idx = index, idx < actionStack.arrangedSubviews.count { + actionStack.insertArrangedSubview(btn, at: idx) + } else { + actionStack.addArrangedSubview(btn) + } + btn.update(style: style) if actionStack.superview == nil { contentStack.addArrangedSubview(actionStack) contentStack.layoutIfNeeded() } - if let idx = index, idx < actionStack.arrangedSubviews.count { - actionStack.insertArrangedSubview(button, at: idx) - } else { - actionStack.addArrangedSubview(button) - } - - addTouchUpAction(for: button) { [weak self] in + addTouchUpAction(for: btn) { [weak self] in handler?() - if button.tag == UIAlertAction.Style.cancel.rawValue { + if btn.tag == UIAlertAction.Style.cancel.rawValue { self?.pop() } } @@ -266,10 +243,10 @@ internal extension Alert { self.view.layoutIfNeeded() } } - return button + return btn } - func privUpdateButton(action index: Int, style: UIAlertAction.Style, title: String?, _ handler: (() -> Void)?) { + func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) { if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton { btn.setTitle(title, for: .normal) if let b = btn as? Button { @@ -287,10 +264,30 @@ internal extension Alert { } } + /// 移除按钮 + /// - Parameter index: 索引 + @discardableResult func remove(action index: Int) -> Alert { + if index < 0 { + for view in self.actionStack.arrangedSubviews { + if let btn = view as? UIButton { + btn.removeFromSuperview() + } + } + } 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() + } + UIView.animateForAlert { + self.view.layoutIfNeeded() + } + return self + } } fileprivate extension Alert { - class func updateAlertsLayout() { + class func privUpdateAlertsLayout() { for (i, a) in alerts.reversed().enumerated() { let scale = CGFloat(pow(0.7, CGFloat(i))) UIView.animate(withDuration: 1.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseInOut], animations: { @@ -301,7 +298,7 @@ fileprivate extension Alert { } } } - class func getAlertWindow(_ vc: UIViewController) -> UIWindow { + class func privGetAlertWindow(_ vc: UIViewController) -> UIWindow { if let w = alertWindow { return w } @@ -313,7 +310,7 @@ fileprivate extension Alert { return w } - class func removeItemFromArray(alert: Alert) { + class func privRemoveItemFromArray(alert: Alert) { if alerts.count > 1 { for (i, a) in alerts.enumerated() { if a == alert { @@ -322,7 +319,7 @@ fileprivate extension Alert { } } } - updateAlertsLayout() + privUpdateAlertsLayout() } else if alerts.count == 1 { alerts.removeAll() } else { diff --git a/ProHUD/Alert/AlertModel.swift b/ProHUD/Alert/AlertModel.swift index eaa8223..f38c438 100644 --- a/ProHUD/Alert/AlertModel.swift +++ b/ProHUD/Alert/AlertModel.swift @@ -33,7 +33,7 @@ public extension Alert { } - struct ViewModel { + class ViewModel { /// ID标识 public var identifier = String(Date().timeIntervalSince1970) @@ -67,9 +67,8 @@ public extension Alert { didSet { durationBlock?.cancel() if let t = duration, t > 0 { - let v = vc - durationBlock = DispatchWorkItem(block: { - v?.pop() + durationBlock = DispatchWorkItem(block: { [weak self] in + self?.vc?.pop() }) DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: durationBlock!) } else { @@ -80,6 +79,8 @@ public extension Alert { public weak var vc: Alert? + // MARK: 私有 + /// 持续时间 internal var durationBlock: DispatchWorkItem? @@ -99,13 +100,9 @@ public extension Alert.ViewModel { /// - Parameter style: 样式 /// - Parameter text: 标题 /// - Parameter handler: 事件处理 - @discardableResult mutating func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + @discardableResult 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! + return vc!.insert(action: nil, style: style, title: title, handler: handler) } /// 插入按钮 @@ -113,13 +110,9 @@ public extension Alert.ViewModel { /// - Parameter style: 样式 /// - Parameter title: 标题 /// - Parameter handler: 事件处理 - @discardableResult mutating func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + @discardableResult 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! + return vc!.insert(action: index, style: style, title: title, handler: handler) } /// 更新按钮 @@ -127,16 +120,15 @@ public extension Alert.ViewModel { /// - 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) + func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) { + vc?.update(action: index, style: style, title: title, handler: handler) } /// 移除按钮 /// - Parameter index: 索引 - mutating func remove(action index: Int...) { - guard let alert = self.vc else { return } + func remove(action index: Int...) { for (i, idx) in index.enumerated() { - alert.privRemoveAction(index: idx-i) + vc?.remove(action: idx-i) } } diff --git a/ProHUD/Alert/AlertView.swift b/ProHUD/Alert/AlertView.swift deleted file mode 100644 index 607c668..0000000 --- a/ProHUD/Alert/AlertView.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// AlertView.swift -// ProHUD -// -// Created by xaoxuu on 2019/7/29. -// Copyright © 2019 Titan Studio. All rights reserved. -// - -import UIKit - -internal extension Alert { - class Button: UIButton { - class func actionButton(title: String?) -> UIButton { - let btn = Button(type: .system) - btn.setTitle(title, for: .normal) - btn.layer.cornerRadius = cfg.alert.cornerRadius / 2 - btn.titleLabel?.font = cfg.alert.buttonFont - return btn - } - - func update(style: UIAlertAction.Style) { - let pd = CGFloat(8) - if style != .cancel { - 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 { - case .default: - setTitleColor(tintColor, for: .normal) - case .destructive: - setTitleColor(.init(red: 244/255, green: 67/255, blue: 54/255, alpha: 1), for: .normal) - case .cancel: - setTitleColor(cfg.secondaryLabelColor, for: .normal) - @unknown default: - break - } - tag = style.rawValue - } - - class func forceQuitButton() -> UIButton { - let btn = Button(type: .system) - let pd = cfg.alert.padding/2 - btn.contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5) - btn.imageEdgeInsets.right = pd*1.5 - btn.setTitleColor(UIColor(red:1.00, green:0.55, blue:0.21, alpha:1.00), for: .normal) - btn.titleLabel?.font = cfg.alert.buttonFont - return btn - } - - } - -} diff --git a/ProHUD/Guard/GuardConfig.swift b/ProHUD/Guard/GuardConfig.swift index bcb3ccd..0e25973 100644 --- a/ProHUD/Guard/GuardConfig.swift +++ b/ProHUD/Guard/GuardConfig.swift @@ -22,7 +22,6 @@ public extension ProHUD.Configuration { /// 填充:元素内部控件距离元素边界的距离 public var padding = CGFloat(16) - // MARK: 图标样式 /// 颜色 public var tintColor: UIColor? @@ -41,12 +40,6 @@ public extension ProHUD.Configuration { /// 按钮圆角半径 public var buttonCornerRadius = CGFloat(12) - /// 加载视图 - /// - Parameter callback: 回调代码 - public mutating func loadSubviews(_ callback: @escaping (ProHUD.Guard) -> Void) { - privLoadSubviews = callback - } - /// 更新视图 /// - Parameter callback: 回调代码 public mutating func reloadData(_ callback: @escaping (ProHUD.Guard) -> Void) { @@ -56,15 +49,14 @@ public extension ProHUD.Configuration { } } -// MARK: - 默认实现 +// MARK: - 内部调用 internal extension ProHUD.Configuration.Guard { - var loadSubviews: (ProHUD.Guard) -> Void { - return privLoadSubviews - } + var reloadData: (ProHUD.Guard) -> Void { return privReloadData } + var reloadStack: (ProHUD.Guard) -> Void { return { (vc) in if vc.textStack.arrangedSubviews.count > 0 { @@ -82,14 +74,7 @@ internal extension ProHUD.Configuration.Guard { } -fileprivate var privLoadSubviews: (ProHUD.Guard) -> Void = { - return { (vc) in - debug(vc, "loadSubviews") - let config = cfg.guard - - } -}() - +// MARK: - 默认实现 fileprivate var privReloadData: (ProHUD.Guard) -> Void = { return { (vc) in debug(vc, "reloadData") @@ -114,10 +99,10 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = { vc.contentView.snp.makeConstraints { (mk) in mk.centerX.equalToSuperview() if UIDevice.current.userInterfaceIdiom == .phone { - if width == config.cardMaxWidth { - mk.bottom.equalToSuperview().offset(-Inspire.shared.screen.safeAreaInsets.bottom) - } else { + if width < config.cardMaxWidth { mk.bottom.equalToSuperview() + } else { + mk.bottom.equalToSuperview().offset(-Inspire.shared.screen.safeAreaInsets.bottom) } } else if UIDevice.current.userInterfaceIdiom == .pad { mk.centerY.equalToSuperview() @@ -126,12 +111,17 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = { } // stack vc.contentStack.snp.makeConstraints { (mk) in - mk.top.equalToSuperview().offset(config.padding + config.margin) + mk.top.equalToSuperview().offset(config.padding) mk.centerX.equalToSuperview() - if width == config.cardMaxWidth { - mk.bottom.equalToSuperview().offset(-config.padding) + if width < config.cardMaxWidth { + let bottom = Inspire.shared.screen.safeAreaInsets.bottom + if bottom == 0 { + mk.bottom.equalToSuperview().offset(-config.padding) + } else { + mk.bottom.equalToSuperview().offset(-config.padding/2 - bottom) + } } else { - mk.bottom.equalToSuperview().offset(-config.padding-Inspire.shared.screen.safeAreaInsets.bottom) + mk.bottom.equalToSuperview().offset(-config.padding) } if isPortrait { mk.width.equalToSuperview().offset(-config.padding * 2) @@ -139,6 +129,6 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = { mk.width.equalToSuperview().offset(-config.padding * 4) } } - + config.reloadStack(vc) } }() diff --git a/ProHUD/Guard/GuardController.swift b/ProHUD/Guard/GuardController.swift index 04f2ba9..3218285 100644 --- a/ProHUD/Guard/GuardController.swift +++ b/ProHUD/Guard/GuardController.swift @@ -15,7 +15,7 @@ public extension ProHUD { class Guard: HUDController { /// 内容视图 - public var contentView = BlurView() + public var contentView = createBlurView() /// 内容容器(包括textStack、actionStack,可以自己插入需要的控件) public var contentStack: StackContainer = { @@ -53,6 +53,7 @@ public extension ProHUD { public var vm = ViewModel() // MARK: 生命周期 + internal var isLoadFinished = false /// 实例化 /// - Parameter title: 标题 @@ -69,29 +70,27 @@ public extension ProHUD { } actions?(&vm) - view.tintColor = cfg.guard.tintColor - cfg.guard.loadSubviews(self) - cfg.guard.reloadData(self) - cfg.guard.reloadStack(self) - // 点击空白处 let tap = UITapGestureRecognizer(target: self, action: #selector(privDidTapped(_:))) view.addGestureRecognizer(tap) - } + public override func viewDidLoad() { + super.viewDidLoad() + view.tintColor = cfg.guard.tintColor + cfg.guard.reloadData(self) + isLoadFinished = true + } + } } // MARK: - 实例函数 - public extension Guard { - // MARK: 生命周期函数 - /// 推入某个视图控制器 /// - Parameter viewController: 视图控制器 @discardableResult func push(to viewController: UIViewController? = nil) -> Guard { @@ -104,11 +103,11 @@ public extension Guard { mk.edges.equalToSuperview() } if displaying == false { - translateOut() + privTranslateOut() } displaying = true UIView.animateForGuard { - self.translateIn() + self.privTranslateIn() } } if let vc = viewController ?? cfg.rootViewController { @@ -127,7 +126,7 @@ public extension Guard { view.isUserInteractionEnabled = false self.removeFromParent() UIView.animateForGuard(animations: { - self.translateOut() + self.privTranslateOut() }) { (done) in if self.displaying == false { self.view.removeFromSuperview() @@ -136,92 +135,23 @@ public extension Guard { } } - // MARK: 设置函数 - - /// 加载一个标题 - /// - Parameter text: 文本 - @discardableResult func add(title: String?) -> UILabel { - let lb = UILabel() - lb.font = cfg.guard.titleFont - lb.textColor = cfg.primaryLabelColor - lb.numberOfLines = 0 - lb.textAlignment = .justified - lb.text = title - textStack.addArrangedSubview(lb) - if #available(iOS 11.0, *) { - let count = textStack.arrangedSubviews.count - if count > 1 { - textStack.setCustomSpacing(cfg.guard.margin * 2, after: textStack.arrangedSubviews[count-2]) - } - } else { - // Fallback on earlier versions - } - cfg.guard.reloadStack(self) - return lb + /// 更新 + /// - Parameter callback: 回调 + func update(_ callback: ((inout ViewModel) -> Void)? = nil) { + callback?(&vm) + cfg.guard.reloadData(self) } - /// 加载一个副标题 - /// - Parameter text: 文本 - @discardableResult func add(subTitle: String?) -> UILabel { - let lb = add(title: subTitle) - lb.font = cfg.guard.subTitleFont - return lb - } - - /// 加载一段正文 - /// - Parameter text: 文本 - @discardableResult func add(message: String?) -> UILabel { - let lb = UILabel() - lb.font = cfg.guard.bodyFont - lb.textColor = cfg.secondaryLabelColor - lb.numberOfLines = 0 - lb.textAlignment = .justified - lb.text = message - textStack.addArrangedSubview(lb) - cfg.guard.reloadStack(self) - return lb - } - - /// 加载一个按钮 - /// - Parameter style: 样式 - /// - Parameter title: 标题 - /// - Parameter action: 事件 - @discardableResult func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { - let btn = Button.actionButton(title: title) - btn.titleLabel?.font = cfg.guard.buttonFont - actionStack.addArrangedSubview(btn) - cfg.guard.reloadStack(self) - btn.update(style: style) - addTouchUpAction(for: btn) { [weak self] in - handler?() - if btn.tag == UIAlertAction.Style.cancel.rawValue { - self?.pop() - } - } - return btn - } - - /// 移除按钮 - /// - Parameter index: 索引 - @discardableResult func remove(action index: Int...) -> Guard { - for (i, idx) in index.enumerated() { - privRemoveAction(index: idx-i) - } - return self - } /// 消失事件 /// - Parameter callback: 事件回调 - @discardableResult func didDisappear(_ callback: (() -> Void)?) -> Guard { + func didDisappear(_ callback: (() -> Void)?) { disappearCallback = callback - return self } } - -// MARK: 类函数 - +// MARK: - 实例管理器 public extension Guard { /// 推入屏幕 @@ -261,7 +191,7 @@ public extension Guard { `guard`.pop() } - /// 弹出屏幕 + /// 弹出所有实例 /// - Parameter identifier: 指定实例的标识 class func pop(from viewController: UIViewController?) { for g in guards(from: viewController) { @@ -273,36 +203,103 @@ public extension Guard { -// MARK: - 私有 - +// MARK: - 创建和设置 internal extension Guard { - /// 点击事件 - /// - Parameter sender: 手势 - @objc func privDidTapped(_ sender: UITapGestureRecognizer) { - let point = sender.location(in: contentView) - if point.x < 0 || point.y < 0 { - if force == false { - // 点击到操作区域外部 - pop() + /// 加载一个标题 + /// - Parameter text: 文本 + @discardableResult func add(title: String?) -> UILabel { + let lb = UILabel() + lb.font = cfg.guard.titleFont + lb.textColor = cfg.primaryLabelColor + lb.numberOfLines = 0 + lb.textAlignment = .center + lb.text = title + textStack.addArrangedSubview(lb) + if #available(iOS 11.0, *) { + let count = textStack.arrangedSubviews.count + if count > 1 { + textStack.setCustomSpacing(cfg.guard.margin * 2, after: textStack.arrangedSubviews[count-2]) + } + } else { + // Fallback on earlier versions + } + cfg.guard.reloadStack(self) + return lb + } + + /// 加载一个副标题 + /// - Parameter text: 文本 + @discardableResult func add(subTitle: String?) -> UILabel { + let lb = add(title: subTitle) + lb.font = cfg.guard.subTitleFont + lb.textAlignment = .justified + return lb + } + + /// 加载一段正文 + /// - Parameter text: 文本 + @discardableResult func add(message: String?) -> UILabel { + let lb = UILabel() + lb.font = cfg.guard.bodyFont + lb.textColor = cfg.secondaryLabelColor + lb.numberOfLines = 0 + lb.textAlignment = .justified + lb.text = message + textStack.addArrangedSubview(lb) + cfg.guard.reloadStack(self) + return lb + } + + /// 加载一个按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter action: 事件 + @discardableResult func insert(action index: Int?, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + let btn = Button.actionButton(title: title) + btn.titleLabel?.font = cfg.guard.buttonFont + if let idx = index, idx < actionStack.arrangedSubviews.count { + actionStack.insertArrangedSubview(btn, at: idx) + } else { + actionStack.addArrangedSubview(btn) + } + btn.update(style: style) + cfg.guard.reloadStack(self) + addTouchUpAction(for: btn) { [weak self] in + handler?() + if btn.tag == UIAlertAction.Style.cancel.rawValue { + self?.pop() } } - + if isLoadFinished { + UIView.animateForGuard { + self.view.layoutIfNeeded() + } + } + return btn } - - func translateIn() { - view.backgroundColor = backgroundColor - contentView.transform = .identity - } - - func translateOut() { - view.backgroundColor = UIColor(white: 0, alpha: 0) - contentView.transform = .init(translationX: 0, y: view.frame.size.height - contentView.frame.minY + cfg.guard.margin) + func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) { + if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton { + btn.setTitle(title, for: .normal) + if let b = btn as? Button { + b.update(style: style) + } + if let _ = buttonEvents[btn] { + buttonEvents.removeValue(forKey: btn) + } + addTouchUpAction(for: btn) { [weak self] in + handler?() + if btn.tag == UIAlertAction.Style.cancel.rawValue { + self?.pop() + } + } + } } /// 移除按钮 /// - Parameter index: 索引 - @discardableResult func privRemoveAction(index: Int) -> Guard { + @discardableResult func remove(index: Int) -> Guard { if index < 0 { for view in self.actionStack.arrangedSubviews { if let btn = view as? UIButton { @@ -322,3 +319,29 @@ internal extension Guard { } +fileprivate extension Guard { + + /// 点击事件 + /// - Parameter sender: 手势 + @objc func privDidTapped(_ sender: UITapGestureRecognizer) { + let point = sender.location(in: contentView) + if point.x < 0 || point.y < 0 { + if force == false { + // 点击到操作区域外部 + pop() + } + } + + } + + func privTranslateIn() { + view.backgroundColor = backgroundColor + contentView.transform = .identity + } + + func privTranslateOut() { + view.backgroundColor = UIColor(white: 0, alpha: 0) + contentView.transform = .init(translationX: 0, y: view.frame.size.height - contentView.frame.minY + cfg.guard.margin) + } + +} diff --git a/ProHUD/Guard/GuardModel.swift b/ProHUD/Guard/GuardModel.swift index e9e483a..ae8f765 100644 --- a/ProHUD/Guard/GuardModel.swift +++ b/ProHUD/Guard/GuardModel.swift @@ -10,7 +10,7 @@ import UIKit public extension Guard { - struct ViewModel { + class ViewModel { /// ID标识 public var identifier = String(Date().timeIntervalSince1970) @@ -46,7 +46,7 @@ public extension Guard.ViewModel { /// - 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) + return vc!.insert(action: nil, style: style, title: title, handler: handler) } /// 插入按钮 @@ -54,9 +54,8 @@ public extension Guard.ViewModel { /// - Parameter style: 样式 /// - Parameter title: 标题 /// - Parameter handler: 事件处理 - @discardableResult mutating func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { - - return UIButton() + @discardableResult func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + return vc!.insert(action: index, style: style, title: title, handler: handler) } /// 更新按钮 @@ -64,15 +63,15 @@ public extension Guard.ViewModel { /// - 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) + func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) { + vc?.update(action: index, style: style, title: title, handler: handler) } /// 移除按钮 /// - Parameter index: 索引 func remove(action index: Int...) { for (i, idx) in index.enumerated() { - vc!.privRemoveAction(index: idx-i) + vc?.remove(index: idx-i) } } diff --git a/ProHUD/Guard/GuardView.swift b/ProHUD/Guard/GuardView.swift deleted file mode 100644 index 5d335ec..0000000 --- a/ProHUD/Guard/GuardView.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// GuardView.swift -// ProHUD -// -// Created by xaoxuu on 2019/7/31. -// Copyright © 2019 Titan Studio. All rights reserved. -// - -import UIKit - -internal extension Guard { - - class Button: UIButton { - class func actionButton(title: String?) -> Button { - let btn = Button(type: .system) - btn.setTitle(title, for: .normal) - btn.layer.cornerRadius = cfg.guard.buttonCornerRadius - btn.titleLabel?.font = cfg.guard.buttonFont - return btn - } - - func update(style: UIAlertAction.Style) { - let pd = CGFloat(8) - if style != .cancel { - contentEdgeInsets = .init(top: pd*1.5+2, left: pd*1.5, bottom: pd*1.5+2, right: pd*1.5) - } else { - contentEdgeInsets = .init(top: pd*1+2, left: pd*1.5, bottom: pd*1+2, right: pd*1.5) - } - switch style { - case .default: - backgroundColor = tintColor - setTitleColor(.white, for: .normal) - case .destructive: - backgroundColor = .init(red: 244/255, green: 67/255, blue: 54/255, alpha: 1) - setTitleColor(.white, for: .normal) - case .cancel: - setTitleColor(cfg.secondaryLabelColor, for: .normal) - @unknown default: - break - } - tag = style.rawValue - } - - } -} - diff --git a/ProHUD/HUDConfig.swift b/ProHUD/HUDConfig.swift index 2b1bb70..b5047e5 100644 --- a/ProHUD/HUDConfig.swift +++ b/ProHUD/HUDConfig.swift @@ -19,19 +19,19 @@ public extension ProHUD { /// 动态颜色(适配iOS13) public lazy var dynamicColor: UIColor = { -// if #available(iOS 13.0, *) { -// let color = UIColor { (traitCollection: UITraitCollection) -> UIColor in -// if traitCollection.userInterfaceStyle == .dark { -// return .white -// } else { -// return .black -// } -// } -// return color -// } else { -// // Fallback on earlier versions -// } - return .init(white: 0.15, alpha: 1) + if #available(iOS 13.0, *) { + let color = UIColor { (traitCollection: UITraitCollection) -> UIColor in + if traitCollection.userInterfaceStyle == .dark { + return .init(white: 1, alpha: 1) + } else { + return .init(white: 0.1, alpha: 1) + } + } + return color + } else { + // Fallback on earlier versions + } + return .init(white: 0.1, alpha: 1) }() /// 主标签文本颜色 @@ -41,9 +41,13 @@ public extension ProHUD { /// 次级标签文本颜色 public lazy var secondaryLabelColor: UIColor = { - return dynamicColor.withAlphaComponent(0.6) + return dynamicColor.withAlphaComponent(0.66) }() + public func blurView(_ callback: @escaping () -> UIVisualEffectView) { + createBlurView = callback + } + public var toast = Toast() public var alert = Alert() @@ -78,3 +82,19 @@ public extension ProHUD { config(&cfg) } } + +internal var createBlurView: () -> UIVisualEffectView = { + return { + let vev = UIVisualEffectView() + if #available(iOS 13.0, *) { + vev.effect = UIBlurEffect(style: .systemMaterial) +// vev.effect = UIBlurEffect(style: .extraLight) + } else if #available(iOS 11.0, *) { + vev.effect = UIBlurEffect(style: .extraLight) + } else { + vev.effect = .none + vev.backgroundColor = .white + } + return vev + } +}() diff --git a/ProHUD/HUDView.swift b/ProHUD/HUDView.swift index 5e5462b..8e8f588 100644 --- a/ProHUD/HUDView.swift +++ b/ProHUD/HUDView.swift @@ -27,34 +27,94 @@ public extension ProHUD { } - class BlurView: UIVisualEffectView { - - init() { - - if #available(iOS 13.0, *) { -// super.init(effect: UIBlurEffect(style: .systemMaterial)) - super.init(effect: UIBlurEffect(style: .extraLight)) - } else if #available(iOS 11.0, *) { - super.init(effect: UIBlurEffect(style: .extraLight)) - } else { - super.init(effect: .none) - backgroundColor = .white - } - +} + +internal extension Alert { + class Button: UIButton { + class func actionButton(title: String?) -> Button { + let btn = Button(type: .system) + btn.setTitle(title, for: .normal) + btn.layer.cornerRadius = cfg.alert.cornerRadius / 2 + btn.titleLabel?.font = cfg.alert.buttonFont + return btn } - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + func update(style: UIAlertAction.Style) { + let pd = CGFloat(8) + if style != .cancel { + 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 { + case .default: + setTitleColor(tintColor, for: .normal) + case .destructive: + setTitleColor(.init(red: 244/255, green: 67/255, blue: 54/255, alpha: 1), for: .normal) + case .cancel: + setTitleColor(cfg.secondaryLabelColor, for: .normal) + @unknown default: + break + } + tag = style.rawValue + } + + class func forceQuitButton() -> UIButton { + let btn = Button(type: .system) + let pd = cfg.alert.padding/2 + btn.contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5) + btn.imageEdgeInsets.right = pd*1.5 + btn.setTitleColor(UIColor(red:1.00, green:0.55, blue:0.21, alpha:1.00), for: .normal) + btn.titleLabel?.font = cfg.alert.buttonFont + return btn } } +} + +internal extension Guard { + class Button: UIButton { + class func actionButton(title: String?) -> Button { + let btn = Button(type: .system) + btn.setTitle(title, for: .normal) + btn.layer.cornerRadius = cfg.guard.buttonCornerRadius + btn.titleLabel?.font = cfg.guard.buttonFont + return btn + } + + func update(style: UIAlertAction.Style) { + let pd = CGFloat(8) + if style != .cancel { + contentEdgeInsets = .init(top: pd*1.5+2, left: pd*1.5, bottom: pd*1.5+2, right: pd*1.5) + } else { + contentEdgeInsets = .init(top: pd*1+2, left: pd*1.5, bottom: pd*1+2, right: pd*1.5) + } + switch style { + case .default: + backgroundColor = tintColor + setTitleColor(.white, for: .normal) + case .destructive: + backgroundColor = .init(red: 244/255, green: 67/255, blue: 54/255, alpha: 1) + setTitleColor(.white, for: .normal) + case .cancel: + backgroundColor = .clear + setTitleColor(cfg.secondaryLabelColor, for: .normal) + @unknown default: + break + } + tag = style.rawValue + } + + } } internal extension UIView { - class func animateEaseOut(duration: TimeInterval = 1, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { + private class func animateEaseOut(duration: TimeInterval = 1, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { animate(withDuration: duration, delay: delay, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.allowUserInteraction, .curveEaseOut], animations: animations, completion: completion) } @@ -64,27 +124,26 @@ internal extension UIView { class func animateForAlertBuildOut(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { animateEaseOut(duration: 0.38, delay: 0, animations: animations, completion: completion) } - class func animateForAlert(animations: @escaping () -> Void) { - animateEaseOut(duration: 1, delay: 0, animations: animations, completion: nil) - } class func animateForAlert(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { animateEaseOut(duration: 1, delay: 0, animations: animations, completion: completion) } - - class func animateForToast(animations: @escaping () -> Void) { - animateEaseOut(duration: 1, delay: 0, animations: animations, completion: nil) + class func animateForAlert(animations: @escaping () -> Void) { + animateForAlert(animations: animations, completion: nil) } class func animateForToast(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { - animateEaseOut(duration: 1, delay: 0, animations: animations, completion: completion) + animateEaseOut(duration: 1.2, delay: 0, animations: animations, completion: completion) } - - class func animateForGuard(animations: @escaping () -> Void) { - animateForGuard(animations: animations, completion: nil) + class func animateForToast(animations: @escaping () -> Void) { + animateForToast(animations: animations, completion: nil) } class func animateForGuard(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { animateEaseOut(duration: 0.6, delay: 0, animations: animations, completion: completion) } + class func animateForGuard(animations: @escaping () -> Void) { + animateForGuard(animations: animations, completion: nil) + } + } diff --git a/ProHUD/Toast/ToastConfig.swift b/ProHUD/Toast/ToastConfig.swift index 673eed1..0861868 100644 --- a/ProHUD/Toast/ToastConfig.swift +++ b/ProHUD/Toast/ToastConfig.swift @@ -21,10 +21,13 @@ public extension ProHUD.Configuration { public var padding = CGFloat(16) // MARK: 图标样式 - /// 图标、default按钮的颜色 - public var tintColor: UIColor? /// 图标尺寸 public var iconSize = CGSize(width: 48, height: 48) + /// 某个场景的默认图片 + /// - Parameter callback: 回调 + public func iconForScene(_ callback: @escaping (ProHUD.Toast.Scene) -> UIImage?) { + privIconForScene = callback + } // MARK: 文本样式 /// 标题字体 @@ -37,71 +40,49 @@ public extension ProHUD.Configuration { /// 正文最多行数 public var bodyMaxLines = Int(10) - /// 加载视图(如果需要完全自定义整个View,可以重写这个) - /// - Parameter callback: 回调代码 - public mutating func loadSubviews(_ callback: @escaping (ProHUD.Toast) -> Void) { - privLoadSubviews = callback - } - /// 更新视图 /// - Parameter callback: 回调代码 public mutating func reloadData(_ callback: @escaping (ProHUD.Toast) -> Void) { privReloadData = callback } + /// 非Loading弹窗的默认持续时间 + public var duration = TimeInterval(3) + } } -// MARK: - 默认实现 +// MARK: - 内部调用 internal extension ProHUD.Configuration.Toast { - var loadSubviews: (ProHUD.Toast) -> Void { - return privLoadSubviews - } + var reloadData: (ProHUD.Toast) -> Void { return privReloadData } + } -fileprivate var privLoadSubviews: (ProHUD.Toast) -> Void = { - return { (vc) in - debug(vc, "loadSubviews") - vc.view.tintColor = cfg.toast.tintColor - vc.view.addSubview(vc.titleLabel) - vc.view.addSubview(vc.bodyLabel) - vc.view.addSubview(vc.imageView) - } -}() - +// MARK: - 默认实现 fileprivate var privReloadData: (ProHUD.Toast) -> Void = { return { (vc) in debug(vc, "reloadData") let config = cfg.toast - let scene = vc.model.scene - // 设置数据 - let imgStr: String - switch vc.model.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 scene = vc.vm.scene + if vc.titleLabel.superview == nil { + vc.view.addSubview(vc.titleLabel) } - let img = vc.model.icon ?? ProHUD.image(named: imgStr) - vc.imageView.image = img + if vc.bodyLabel.superview == nil { + vc.view.addSubview(vc.bodyLabel) + } + if vc.imageView.superview == nil { + vc.view.addSubview(vc.imageView) + } + // 设置数据 + vc.imageView.image = vc.vm.icon ?? privIconForScene(vc.vm.scene) vc.titleLabel.textColor = cfg.primaryLabelColor - vc.titleLabel.text = vc.model.title + vc.titleLabel.text = vc.vm.title vc.bodyLabel.textColor = cfg.secondaryLabelColor - vc.bodyLabel.text = vc.model.message + vc.bodyLabel.text = vc.vm.message // 更新布局 vc.imageView.snp.makeConstraints { (mk) in @@ -123,13 +104,35 @@ fileprivate var privReloadData: (ProHUD.Toast) -> Void = { } vc.view.layoutIfNeeded() - switch vc.model.scene { - case .loading: - vc.duration(nil) - default: - vc.duration(3) + // 设置默认持续时间 + if vc.vm.duration == nil { + if vc.vm.scene == .loading { + vc.vm.duration = 0 + } else { + vc.vm.duration = config.duration + } } - } }() + + + +fileprivate var privIconForScene: (ProHUD.Toast.Scene) -> UIImage? = { + return { (scene) in + let imgStr: String + switch scene { + case .success: + imgStr = "ProHUDSuccess" + case .warning: + imgStr = "ProHUDWarning" + case .error: + imgStr = "ProHUDError" + case .loading: + imgStr = "ProHUDLoading" + default: + imgStr = "ProHUDMessage" + } + return ProHUD.image(named: imgStr) + } +}() diff --git a/ProHUD/Toast/ToastController.swift b/ProHUD/Toast/ToastController.swift index dd62b52..da19233 100644 --- a/ProHUD/Toast/ToastController.swift +++ b/ProHUD/Toast/ToastController.swift @@ -47,23 +47,16 @@ public extension ProHUD { /// 背景层 public var backgroundView: UIVisualEffectView = { - let vev = UIVisualEffectView() - if #available(iOS 13.0, *) { -// vev.effect = UIBlurEffect.init(style: .systemMaterial)) - vev.effect = UIBlurEffect.init(style: .extraLight) - } else if #available(iOS 11.0, *) { - vev.effect = UIBlurEffect.init(style: .extraLight) - } else { - vev.effect = .none - vev.backgroundColor = .white - } + let vev = createBlurView() vev.layer.masksToBounds = true vev.layer.cornerRadius = cfg.toast.cornerRadius return vev }() /// 视图模型 - public var model = ViewModel() + public var vm = ViewModel() + + internal var maxY = CGFloat(0) // MARK: 生命周期 @@ -72,17 +65,15 @@ public extension ProHUD { /// - Parameter title: 标题 /// - Parameter message: 内容 /// - Parameter icon: 图标 - public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil, actions: ((Toast) -> Void)? = nil) { + public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil, actions: ((inout ViewModel) -> Void)? = nil) { self.init() + vm.vc = self - model.scene = scene - model.title = title - model.message = message - model.icon = icon - actions?(self) - // 布局 - cfg.toast.loadSubviews(self) - cfg.toast.reloadData(self) + vm.scene = scene + vm.title = title + vm.message = message + vm.icon = icon + actions?(&vm) // 点击 let tap = UITapGestureRecognizer(target: self, action: #selector(privDidTapped(_:))) @@ -94,16 +85,21 @@ public extension ProHUD { } + public override func viewDidLoad() { + super.viewDidLoad() + + cfg.toast.reloadData(self) + + } + + } } // MARK: - 实例函数 - public extension Toast { - // MARK: 生命周期函数 - /// 推入屏幕 @discardableResult func push() -> Toast { let config = cfg.toast @@ -145,7 +141,7 @@ public extension Toast { if Toast.toasts.contains(self) == false { Toast.toasts.append(self) } - Toast.updateToastsLayout() + Toast.privUpdateToastsLayout() if isNew { window.transform = .init(translationX: 0, y: -window.frame.maxY) UIView.animateForToast { @@ -159,32 +155,21 @@ public extension Toast { /// 弹出屏幕 func pop() { - Toast.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 - } + Toast.pop(self) } - // MARK: 设置函数 - /// 设置持续时间 - /// - Parameter duration: 持续时间 - @discardableResult func duration(_ duration: TimeInterval?) -> Toast { - model.setupDuration(duration: duration) { [weak self] in - self?.pop() - } - return self + /// 更新 + /// - Parameter callback: 回调 + func update(_ callback: ((inout ViewModel) -> Void)? = nil) { + callback?(&vm) + cfg.toast.reloadData(self) } /// 点击事件 /// - Parameter callback: 事件回调 @discardableResult func didTapped(_ callback: (() -> Void)?) -> Toast { - model.tapCallback = callback + vm.tapCallback = callback return self } @@ -195,47 +180,10 @@ public extension Toast { return self } - /// 更新 - /// - Parameter scene: 场景 - /// - Parameter title: 标题 - /// - Parameter message: 内容 - @discardableResult func update(scene: Scene, title: String?, message: String?) -> Toast { - model.scene = scene - model.title = title - model.message = message - cfg.toast.reloadData(self) - return self - } - - /// 更新标题 - /// - Parameter title: 标题 - @discardableResult func update(title: String?) -> Toast { - model.title = title - cfg.toast.reloadData(self) - return self - } - - /// 更新文本 - /// - Parameter message: 消息 - @discardableResult func update(message: String?) -> Toast { - model.message = message - cfg.toast.reloadData(self) - return self - } - - /// 更新图标 - /// - Parameter icon: 图标 - @discardableResult func update(icon: UIImage?) -> Toast { - model.icon = icon - cfg.toast.reloadData(self) - return self - } - } -// MARK: 类函数 - +// MARK: - 实例管理器 public extension Toast { /// 推入屏幕 @@ -243,7 +191,7 @@ public extension Toast { /// - Parameter title: 标题 /// - Parameter message: 内容 /// - Parameter actions: 更多操作 - @discardableResult class func push(scene: Toast.Scene, title: String? = nil, message: String? = nil, actions: ((Toast) -> Void)? = nil) -> Toast { + @discardableResult class func push(scene: Toast.Scene = .default, title: String? = nil, message: String? = nil, actions: ((inout ViewModel) -> Void)? = nil) -> Toast { return Toast(scene: scene, title: title, message: message, actions: actions).push() } @@ -252,7 +200,7 @@ public extension Toast { class func toasts(_ identifier: String?) -> [Toast] { var tt = [Toast]() for t in toasts { - if t.model.identifier == identifier { + if t.vm.identifier == identifier { tt.append(t) } } @@ -262,7 +210,25 @@ public extension Toast { /// 弹出屏幕 /// - Parameter toast: 实例 class func pop(_ toast: Toast) { - toast.pop() + if toasts.count > 1 { + for (i, t) in toasts.enumerated() { + if t == toast { + toasts.remove(at: i) + } + } + privUpdateToastsLayout() + } else if toasts.count == 1 { + toasts.removeAll() + } else { + debug("漏洞:已经没有toast了") + } + UIView.animateForToast(animations: { + toast.window?.transform = .init(translationX: 0, y: -20-toast.maxY) + }) { (done) in + toast.view.removeFromSuperview() + toast.removeFromParent() + toast.window = nil + } } /// 弹出屏幕 @@ -275,57 +241,40 @@ public extension Toast { } -// MARK: 私有 - -fileprivate var willUpdateToastsLayout: DispatchWorkItem? +// MARK: - 创建和设置 +fileprivate var willprivUpdateToastsLayout: DispatchWorkItem? fileprivate extension Toast { /// 点击事件 /// - Parameter sender: 手势 @objc func privDidTapped(_ sender: UITapGestureRecognizer) { - model.tapCallback?() + vm.tapCallback?() } /// 拖拽事件 /// - Parameter sender: 手势 @objc func privDidPan(_ sender: UIPanGestureRecognizer) { - model.durationBlock?.cancel() + vm.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 model.removable == true && (((window?.frame.origin.y ?? 0) < 0 && v.y < 0) || v.y < -1200) { + if vm.removable == true && (((window?.frame.origin.y ?? 0) < 0 && v.y < 0) || v.y < -1200) { // 移除 self.pop() } else { UIView.animateForToast(animations: { self.window?.transform = .identity }) { (done) in - // FIXME: 重置计时器 - + let d = self.vm.duration + self.vm.duration = d } } } } - /// 从数组中移除 - /// - Parameter toast: 实例 - class 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 { - debug("漏洞:已经没有toast了") - } - } - class func updateToastsLayout() { + class func privUpdateToastsLayout() { func f() { let top = Inspire.shared.screen.updatedSafeAreaInsets.top for (i, e) in toasts.enumerated() { @@ -342,18 +291,20 @@ fileprivate extension Toast { let lastY = toasts[i-1].window?.frame.maxY ?? .zero y = lastY + config.margin } + e.maxY = y + window.frame.size.height UIView.animateForToast { - e.window?.frame.origin.y = y + window.frame.origin.y = y } } } } - willUpdateToastsLayout?.cancel() - willUpdateToastsLayout = DispatchWorkItem(block: { + willprivUpdateToastsLayout?.cancel() + willprivUpdateToastsLayout = DispatchWorkItem(block: { f() }) - DispatchQueue.main.asyncAfter(deadline: .now()+0.001, execute: willUpdateToastsLayout!) + DispatchQueue.main.asyncAfter(deadline: .now()+0.001, execute: willprivUpdateToastsLayout!) } + } diff --git a/ProHUD/Toast/ToastModel.swift b/ProHUD/Toast/ToastModel.swift index f3ad850..fab95e8 100644 --- a/ProHUD/Toast/ToastModel.swift +++ b/ProHUD/Toast/ToastModel.swift @@ -16,12 +16,6 @@ public extension Toast { /// 加载中场景 case loading - /// 确认场景 - case confirm - - /// 删除场景 - case delete - /// 成功场景 case success @@ -33,7 +27,7 @@ public extension Toast { } - struct ViewModel { + class ViewModel { /// ID标识 public var identifier = String(Date().timeIntervalSince1970) @@ -42,40 +36,56 @@ public extension Toast { 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? + public var duration: TimeInterval? { + didSet { + durationBlock?.cancel() + if let t = duration, t > 0 { + durationBlock = DispatchWorkItem(block: { [weak self] in + self?.vc?.pop() + }) + DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: durationBlock!) + } else { + durationBlock = nil + } + } + } + + public weak var vc: Toast? + + /// 是否可以通过手势移除(向上滑出屏幕) + public var removable = true + + + // MARK: 私有 /// 持续时间 internal var durationBlock: DispatchWorkItem? - /// 是否可以通过手势移除(向上划) - public var removable = true - /// 点击事件回调 internal var tapCallback: (() -> Void)? - 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 - } - } - } } - - diff --git a/ProHUD/Toast/ToastView.swift b/ProHUD/Toast/ToastView.swift deleted file mode 100644 index 39a4593..0000000 --- a/ProHUD/Toast/ToastView.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// ToastView.swift -// ProHUD -// -// Created by xaoxuu on 2019/7/31. -// Copyright © 2019 Titan Studio. All rights reserved. -// - -import Foundation