diff --git a/Example-Old/Example/TestAlertVC.swift b/Example-Old/Example/TestAlertVC.swift index 487464b..6ee94f9 100644 --- a/Example-Old/Example/TestAlertVC.swift +++ b/Example-Old/Example/TestAlertVC.swift @@ -35,12 +35,12 @@ class TestAlertVC: BaseListVC { func f() { Alert.push(scene: .loading, title: "正在同步", message: "请稍等片刻") { (a) in a.identifier = "loading" - a.rotate() + a.startRotate() a.didForceQuit { let t = Toast.push(scene: .loading, title: "正在同步", message: "请稍等片刻(点击展开为Alert)") { (vm) in vm.identifier = "loading" } - t.rotate() + t.startRotate() t.didTapped { [weak t] in t?.pop() f() @@ -52,7 +52,7 @@ class TestAlertVC: BaseListVC { } else if row == 1 { Alert.push() { (a) in a.identifier = "loading" - a.rotate() + a.startRotate() a.update { (vm) in vm.scene = .loading vm.title = "正在同步" @@ -72,7 +72,7 @@ class TestAlertVC: BaseListVC { let a = Alert.push() { (a) in a.identifier = "loading" } - a.rotate() + a.startRotate() a.update { (vm) in vm.scene = .loading vm.title = "正在同步" @@ -99,7 +99,7 @@ class TestAlertVC: BaseListVC { vm.message = "请稍等片刻" vm.remove(action: 0, 1) } - a.rotate() + a.startRotate() DispatchQueue.main.asyncAfter(deadline: .now()+2) { a.update { (vm) in vm.scene = .error @@ -138,7 +138,7 @@ class TestAlertVC: BaseListVC { vm.scene = .loading vm.title = "正在加载" } - a.rotate() + a.startRotate() } } } @@ -161,7 +161,7 @@ class TestAlertVC: BaseListVC { } else if row == 5 { func f(_ i: Int) { Alert.push() { (a) in - a.rotate() + a.startRotate() a.update { (vm) in vm.scene = .loading vm.title = "正在同步" + String(i) diff --git a/Example-Old/Example/TestGuardVC.swift b/Example-Old/Example/TestGuardVC.swift index ccd08f1..0dc131a 100644 --- a/Example-Old/Example/TestGuardVC.swift +++ b/Example-Old/Example/TestGuardVC.swift @@ -65,7 +65,7 @@ class TestGuardVC: BaseListVC { vm.message = "请稍等片刻" vm.remove(action: 0, 1) }) - vc?.rotate() + vc?.startRotate() DispatchQueue.main.asyncAfter(deadline: .now()+1) { vc?.update({ (vm) in vm.scene = .success diff --git a/Example-Old/Example/TestToastVC.swift b/Example-Old/Example/TestToastVC.swift index 736380d..ed2187b 100644 --- a/Example-Old/Example/TestToastVC.swift +++ b/Example-Old/Example/TestToastVC.swift @@ -40,7 +40,7 @@ class TestToastVC: BaseListVC { if row == 0 { Toast.push(scene: .loading, title: "正在同步", message: "请稍等片刻") { (vm) in vm.identifier = "loading" - }.rotate() + }.startRotate() simulateSync() } else if row == 1 { let t = Toast.push(scene: .success, title: "同步成功", message: "点击查看详情") @@ -163,7 +163,7 @@ class TestToastVC: BaseListVC { mk.center.equalToSuperview() mk.width.height.equalTo(18) } - t.rotate(imgv.layer, speed: 4) + t.startRotate(imgv.layer, speed: 4) } simulateSync() } diff --git a/ProHUD.xcodeproj/project.pbxproj b/ProHUD.xcodeproj/project.pbxproj index 9296ece..93ba8b5 100644 --- a/ProHUD.xcodeproj/project.pbxproj +++ b/ProHUD.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ CDC39CFD22FD6DDF0070E914 /* GuardModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */; }; CDC67BE92490A19100CC6FE6 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = CDC67BE82490A19100CC6FE6 /* SnapKit */; }; CDC67BEC2490A1D100CC6FE6 /* Inspire in Frameworks */ = {isa = PBXBuildFile; productRef = CDC67BEB2490A1D100CC6FE6 /* Inspire */; }; + CDF2C58924A0572C002ECDD5 /* HUDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF2C58824A0572C002ECDD5 /* HUDUtils.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -47,6 +48,7 @@ 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 = ""; }; + CDF2C58824A0572C002ECDD5 /* HUDUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDUtils.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 */ @@ -147,6 +149,7 @@ CDB6A07C22EEF19D00AF6CF0 /* HUDConfig.swift */, CDB6A07A22EEF06500AF6CF0 /* HUDController.swift */, CD16491122EF0D900077988C /* HUDView.swift */, + CDF2C58824A0572C002ECDD5 /* HUDUtils.swift */, CD6CD87322F185B400F4FD4A /* Toast */, CD16491022EF0A4B0077988C /* Alert */, CD6CD87C22F185DA00F4FD4A /* Guard */, @@ -280,6 +283,7 @@ CD6CD86C22F1858F00F4FD4A /* ToastModel.swift in Sources */, CD16490B22EF09AB0077988C /* AlertModel.swift in Sources */, CD16491222EF0D900077988C /* HUDView.swift in Sources */, + CDF2C58924A0572C002ECDD5 /* HUDUtils.swift in Sources */, CDB6A07B22EEF06500AF6CF0 /* HUDController.swift in Sources */, CDC39CFD22FD6DDF0070E914 /* GuardModel.swift in Sources */, ); diff --git a/Source/Alert/AlertConfig.swift b/Source/Alert/AlertConfig.swift index 245c5d2..fc507f1 100644 --- a/Source/Alert/AlertConfig.swift +++ b/Source/Alert/AlertConfig.swift @@ -65,8 +65,8 @@ public extension ProHUD.Configuration { /// 加载强制退出按钮 /// - Parameter callback: 回调代码 - public func loadForceQuitButton(_ callback: @escaping (ProHUD.Alert) -> Void) { - privLoadForceQuitButton = callback + public func loadHideButton(_ callback: @escaping (ProHUD.Alert) -> Void) { + privLoadHideButton = callback } @@ -234,11 +234,11 @@ fileprivate var privUpdateActionStack: (ProHUD.Alert) -> Void = { } }() -fileprivate var privLoadForceQuitButton: (ProHUD.Alert) -> Void = { +fileprivate var privLoadHideButton: (ProHUD.Alert) -> Void = { return { (vc) in debug(vc, "showNavButtons") let config = cfg.alert - let btn = ProHUD.Alert.Button.forceQuitButton() + let btn = ProHUD.Alert.Button.createHideButton() btn.setTitle(cfg.alert.forceQuitTitle, for: .normal) let bg = createBlurView() bg.layer.masksToBounds = true @@ -312,19 +312,19 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { // 设置持续时间 vc.vm.updateDuration() - // 强制退出按钮 - vc.vm.forceQuitTimerBlock?.cancel() + // 「隐藏」按钮出现的时间 + vc.vm.hideTimerBlock?.cancel() if vc.buttonEvents.count == 0 { - vc.vm.forceQuitTimerBlock = DispatchWorkItem(block: { [weak vc] in + vc.vm.hideTimerBlock = DispatchWorkItem(block: { [weak vc] in if let vc = vc { if vc.buttonEvents.count == 0 { - privLoadForceQuitButton(vc) + privLoadHideButton(vc) } } }) - DispatchQueue.main.asyncAfter(deadline: .now() + config.forceQuitTimer, execute: vc.vm.forceQuitTimerBlock!) + DispatchQueue.main.asyncAfter(deadline: .now() + config.forceQuitTimer, execute: vc.vm.hideTimerBlock!) } else { - vc.vm.forceQuitTimerBlock = nil + vc.vm.hideTimerBlock = nil } } diff --git a/Source/Alert/AlertController.swift b/Source/Alert/AlertController.swift index efe0434..fff2b90 100644 --- a/Source/Alert/AlertController.swift +++ b/Source/Alert/AlertController.swift @@ -90,6 +90,7 @@ public extension Alert { /// 推入屏幕 @discardableResult func push() -> Alert { if Alert.alerts.contains(self) == false { + willAppearCallback?() let window = Alert.privGetAlertWindow(self) window.makeKeyAndVisible() window.resignKey() @@ -107,6 +108,7 @@ public extension Alert { window.backgroundColor = window.backgroundColor?.withAlphaComponent(0.6) } Alert.alerts.append(self) + didAppearCallback?() } Alert.privUpdateAlertsLayout() return self @@ -123,6 +125,7 @@ public extension Alert { }) { (done) in self.view.removeFromSuperview() self.removeFromParent() + self.didDisappearCallback?() } // hide window let count = Alert.alerts.count @@ -153,9 +156,9 @@ public extension Alert { } -extension Alert: RotateAnimation { - -} + +// MARK: 支持加载动画 +extension Alert: LoadingRotateAnimation {} // MARK: - 实例管理器 public extension Alert { @@ -218,7 +221,7 @@ internal extension Alert { /// - Parameter title: 标题 /// - Parameter action: 事件 @discardableResult func insert(action index: Int?, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { - let btn = Button.actionButton(title: title) + let btn = Button.createActionButton(title: title) if let idx = index, idx < actionStack.arrangedSubviews.count { actionStack.insertArrangedSubview(btn, at: idx) } else { diff --git a/Source/Alert/AlertModel.swift b/Source/Alert/AlertModel.swift index d262c4c..39360fc 100644 --- a/Source/Alert/AlertModel.swift +++ b/Source/Alert/AlertModel.swift @@ -50,7 +50,7 @@ public extension Alert { internal var durationBlock: DispatchWorkItem? /// 强制退出按钮 - internal var forceQuitTimerBlock: DispatchWorkItem? + internal var hideTimerBlock: DispatchWorkItem? /// 强制退出代码 internal var forceQuitCallback: (() -> Void)? diff --git a/Source/Guard/GuardController.swift b/Source/Guard/GuardController.swift index a908312..59f0e11 100644 --- a/Source/Guard/GuardController.swift +++ b/Source/Guard/GuardController.swift @@ -99,6 +99,7 @@ public extension Guard { /// - Parameter viewController: 视图控制器 @discardableResult func push(to viewController: UIViewController? = nil) -> Guard { func f(_ vc: UIViewController) -> Guard { + willAppearCallback?() view.layoutIfNeeded() vc.addChild(self) vc.view.addSubview(view) @@ -113,6 +114,7 @@ public extension Guard { UIView.animateForGuard { self.privTranslateIn() } + didAppearCallback?() return self } if let vc = viewController ?? cfg.rootViewController { @@ -150,6 +152,7 @@ public extension Guard { }) { (done) in if self.isDisplaying == false { self.view.removeFromSuperview() + self.didDisappearCallback?() } } } @@ -285,7 +288,7 @@ internal extension Guard { /// - Parameter title: 标题 /// - Parameter action: 事件 @discardableResult func insert(action index: Int?, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { - let btn = Button.actionButton(title: title) + let btn = Button.createActionButton(title: title) btn.titleLabel?.font = cfg.guard.buttonFont if let idx = index, idx < actionStack.arrangedSubviews.count { actionStack.insertArrangedSubview(btn, at: idx) diff --git a/Source/HUDController.swift b/Source/HUDController.swift index 358d0fe..b6a0609 100644 --- a/Source/HUDController.swift +++ b/Source/HUDController.swift @@ -19,7 +19,7 @@ public class HUDController: UIViewController { internal var didDisappearCallback: (() -> Void)? /// 执行动画的图层 - public var animateLayer: CALayer? + var animateLayer: CALayer? internal var animation: CAAnimation? /// 按钮事件 @@ -74,6 +74,7 @@ public class HUDController: UIViewController { } +// MARK: - 事件 internal extension HUDController { func addTouchUpAction(for button: UIButton, action: @escaping () -> Void) { @@ -89,15 +90,55 @@ internal extension HUDController { } +// MARK: - 动画 + +/// 图片动画 +public protocol LoadingAnimationView: HUDController { + var imageView: UIImageView { get } +} + +/// 旋转动画 +public protocol LoadingRotateAnimation: LoadingAnimationView {} +public extension LoadingRotateAnimation { + + /// 旋转动画 + /// - Parameters: + /// - layer: 图层 + /// - direction: 方向 + /// - speed: 速度 + func startRotate(_ layer: CALayer? = nil, direction: ProHUD.RotateDirection = .clockwise, speed: CFTimeInterval = 2) { + DispatchQueue.main.async { + let l = layer ?? self.imageView.layer + self.animateLayer = l + l.startRotate(direction: direction, speed: speed) + NotificationCenter.default.addObserver(self, selector: #selector(self.didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) + } + } + + /// 停止旋转 + /// - Parameter layer: 图层 + func endRotate(_ layer: CALayer? = nil) { + DispatchQueue.main.async { + self.animateLayer = nil + (layer ?? self.imageView.layer)?.endRotate() + NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil) + } + } + +} + +/// 动画扩展 extension HUDController { - @objc func pauseAnimation() { + @objc func didEnterBackground() { if let layer = animateLayer { animation = layer.animation(forKey: .rotateKey) layer.timeOffset = layer.convertTime(CACurrentMediaTime(), from: nil) layer.speed = 0 } } - @objc func resumeAnimation() { + @objc func willEnterForeground() { if let layer = animateLayer, let ani = animation, layer.speed == 0 { let pauseTime = layer.timeOffset layer.timeOffset = 0 @@ -109,29 +150,3 @@ extension HUDController { } } -public protocol RotateAnimation: HUDController { - var imageView: UIImageView { get } - var animateLayer: CALayer? { get set } -} - -public extension RotateAnimation { - - func rotate(_ layer: CALayer? = nil, direction: ProHUD.RotateDirection = .clockwise, speed: CFTimeInterval = 2) { - DispatchQueue.main.async { - let l = layer ?? self.imageView.layer - self.animateLayer = l - l.startRotate(direction: direction, speed: speed) - NotificationCenter.default.addObserver(self, selector: #selector(self.pauseAnimation), name: UIApplication.didEnterBackgroundNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(self.resumeAnimation), name: UIApplication.willEnterForegroundNotification, object: nil) - } - } - func rotate(_ layer: CALayer? = nil, _ flag: Bool) { - DispatchQueue.main.async { - self.animateLayer = nil - (layer ?? self.imageView.layer)?.endRotate() - NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) - NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil) - } - } - -} diff --git a/Source/HUDUtils.swift b/Source/HUDUtils.swift new file mode 100644 index 0000000..eef8a2f --- /dev/null +++ b/Source/HUDUtils.swift @@ -0,0 +1,20 @@ +// +// HUDUtils.swift +// ProHUD +// +// Created by xaoxuu on 2020/6/22. +// Copyright © 2020 Titan Studio. All rights reserved. +// + +import UIKit +import Inspire + +extension String { + static let rotateKey = "rotationAnimation" +} + +extension ProHUD { + static var safeAreaInsets: UIEdgeInsets { + return Inspire.shared.screen.updatedSafeAreaInsets + } +} diff --git a/Source/HUDView.swift b/Source/HUDView.swift index 0044f7c..9c686e3 100644 --- a/Source/HUDView.swift +++ b/Source/HUDView.swift @@ -7,10 +7,12 @@ // import UIKit -import Inspire + +// MARK: - public public extension ProHUD { + /// 堆栈视图容器 class StackContainer: UIStackView { public override init(frame: CGRect) { @@ -27,21 +29,29 @@ public extension ProHUD { } } + /// 旋转方向 enum RotateDirection: Double { + /// 顺时针 case clockwise = 1 + /// 逆时针 case counterclockwise = -1 } } -internal extension String { - static let rotateKey = "rotationAnimation" -} +// MARK: - internal +// MARK: 弹窗 internal extension Alert { + + /// 弹窗的按钮 class Button: UIButton { - class func actionButton(title: String?) -> Button { + + /// 创建操作按钮 + /// - Parameter title: 标题 + /// - Returns: 按钮 + static func createActionButton(title: String?) -> Button { let btn = Button(type: .system) btn.setTitle(title, for: .normal) btn.layer.cornerRadius = cfg.alert.cornerRadius / 2 @@ -49,6 +59,8 @@ internal extension Alert { return btn } + /// 更新按钮 + /// - Parameter style: 样式 func update(style: UIAlertAction.Style) { let pd = CGFloat(8) if style != .cancel { @@ -71,7 +83,9 @@ internal extension Alert { tag = style.rawValue } - class func forceQuitButton() -> UIButton { + /// 创建隐藏按钮 + /// - Returns: 按钮 + static func createHideButton() -> 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) @@ -85,10 +99,16 @@ internal extension Alert { } +// MARK: 操作表 internal extension Guard { + /// 操作表的按钮 class Button: UIButton { - class func actionButton(title: String?) -> Button { + + /// 创建操作按钮 + /// - Parameter title: 标题 + /// - Returns: 按钮 + static func createActionButton(title: String?) -> Button { let btn = Button(type: .system) btn.setTitle(title, for: .normal) btn.layer.cornerRadius = cfg.guard.buttonCornerRadius @@ -122,6 +142,8 @@ internal extension Guard { } } +// MARK: - 动画 + internal extension UIView { private class func animateEaseOut(duration: TimeInterval = 1, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { @@ -158,7 +180,13 @@ internal extension UIView { } + extension CALayer { + + /// 开始旋转 + /// - Parameters: + /// - direction: 方向 + /// - speed: 速度 func startRotate(direction: ProHUD.RotateDirection, speed: CFTimeInterval) { if animation(forKey: .rotateKey) == nil { let ani = CABasicAnimation(keyPath: "transform.rotation.z") @@ -170,13 +198,10 @@ extension CALayer { add(ani, forKey: .rotateKey) } } + + /// 结束旋转 func endRotate() { removeAnimation(forKey: .rotateKey) } -} - -extension ProHUD { - static var safeAreaInsets: UIEdgeInsets { - return Inspire.shared.screen.updatedSafeAreaInsets - } + } diff --git a/Source/Toast/ToastController.swift b/Source/Toast/ToastController.swift index 705db96..62e0d16 100644 --- a/Source/Toast/ToastController.swift +++ b/Source/Toast/ToastController.swift @@ -116,6 +116,7 @@ public extension Toast { let config = cfg.toast let isNew: Bool if self.window == nil { + willAppearCallback?() let window = UIWindow(frame: .zero) self.window = window if #available(iOS 13.0, *) { @@ -166,6 +167,7 @@ public extension Toast { } else { view.layoutIfNeeded() } + didAppearCallback?() return self } @@ -205,9 +207,8 @@ public extension Toast { } -extension Toast: RotateAnimation { - -} +// MARK: 支持加载动画 +extension Toast: LoadingRotateAnimation {} // MARK: - 实例管理器 public extension Toast { @@ -267,6 +268,7 @@ public extension Toast { toast.view.removeFromSuperview() toast.removeFromParent() toast.window = nil + toast.didDisappearCallback?() } } @@ -288,6 +290,7 @@ fileprivate extension Toast { /// 点击事件 /// - Parameter sender: 手势 @objc func privDidTapped(_ sender: UITapGestureRecognizer) { + pulse() vm.tapCallback?() }