diff --git a/PHDemo/PHDemo/DemoAlertVC.swift b/PHDemo/PHDemo/DemoAlertVC.swift index 2ab055f..e596f33 100644 --- a/PHDemo/PHDemo/DemoAlertVC.swift +++ b/PHDemo/PHDemo/DemoAlertVC.swift @@ -47,9 +47,9 @@ class DemoAlertVC: ListVC { section.add(title: "图标 + 文字") { Alert(.loading.message("正在加载")) { alert in updateProgress(in: 4) { percent in - alert.update(progress: percent) + alert.vm?.progress(percent) } completion: { - alert.update { alert in + alert.reloadData { alert in alert.vm = .success.message("加载成功") alert.add(action: "OK") } diff --git a/PHDemo/PHDemo/DemoCapsuleVC.swift b/PHDemo/PHDemo/DemoCapsuleVC.swift index b3511aa..0a5e870 100644 --- a/PHDemo/PHDemo/DemoCapsuleVC.swift +++ b/PHDemo/PHDemo/DemoCapsuleVC.swift @@ -59,14 +59,33 @@ class DemoCapsuleVC: ListVC { section.add(title: "下载进度") { let capsule = CapsuleTarget() capsule.vm = .loading(.infinity).message("正在下载") - capsule.update(progress: 0) capsule.push() updateProgress(in: 4) { percent in - capsule.update(progress: percent) + capsule.vm?.progress(percent) } completion: { - capsule.update { toast in - toast.vm = .success(5).message("下载成功") - } + capsule.vm(.success(5).message("下载成功")) + } + } + section.add(title: "倒计时3s") { + Capsule(.icon(.init(named: "twemoji")).title("倒计时3s").duration(4)) { capsule in + capsule.config.cardMinWidth = 140 // 估算一下合适的宽度并固定,不然数字变化时总宽度也在一直变化,观感不佳 + capsule.vm?.countdown(seconds: 3, onUpdate: { progress in + capsule.title = .init(format: "倒计时%.1fs", progress.current) + }, onCompletion: { + // 重新创建一个新的vm + capsule.vm(.success(3).title("倒计时结束")) + }) + } + } + section.add(title: "倒计时10s") { + Capsule(.icon(.init(named: "twemoji")).title("倒计时3s").duration(10)) { capsule in + capsule.config.cardMinWidth = 140 // 估算一下合适的宽度并固定,不然数字变化时总宽度也在一直变化,观感不佳 + capsule.vm?.countdown(seconds: 10, onUpdate: { progress in + capsule.title = .init(format: "倒计时%.1fs", progress.current) + }, onCompletion: { + // 重新创建一个新的vm + capsule.vm(.success(3).title("倒计时结束")) + }) } } section.add(title: "接口报错提示") { @@ -283,7 +302,7 @@ class GradientCapsule: HUDProviderType { /// - initializer: 初始化代码 @discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ capsule: Target) -> Void)?) { if let id = vm.identifier, id.count > 0, let target = CapsuleManager.find(identifier: id).last as? Target { - target.update { capsule in + target.reloadData { capsule in capsule.vm = vm initializer?(capsule as! GradientCapsule.Target) } diff --git a/PHDemo/PHDemo/DemoToastVC.swift b/PHDemo/PHDemo/DemoToastVC.swift index fc5eeea..7e2cfec 100644 --- a/PHDemo/PHDemo/DemoToastVC.swift +++ b/PHDemo/PHDemo/DemoToastVC.swift @@ -128,25 +128,23 @@ class DemoToastVC: ListVC { let s2 = "这通常不会太久" let toast = ToastTarget(.loading.title(s1).message(s2)) toast.push() - toast.update(progress: 0) updateProgress(in: 4) { percent in - toast.update(progress: percent) + toast.vm?.progress(percent) } completion: { - toast.update { toast in - toast.vm = .success(5) - .title("加载成功") - .message("这条通知5s后消失") - .icon(.init(named: "twemoji")) - } + toast.vm( + .success(5) + .title("加载成功") + .message("这条通知5s后消失") + .icon(.init(named: "twemoji")) + ) } } section.add(title: "倒计时") { let s1 = "笑容正在消失" let s2 = "这通常不会太久" - Toast { toast in - toast.vm = .title(s1).message(s2).icon(UIImage(named: "twemoji")) + Toast(.title(s1).message(s2).icon(UIImage(named: "twemoji"))) { toast in updateProgress(in: 5) { percent in - toast.update(progress: 1 - percent) + toast.vm?.progress(1 - percent) } completion: { toast.pop() } diff --git a/Sources/ProHUD/Alert/AlertDefaultLayout.swift b/Sources/ProHUD/Alert/AlertDefaultLayout.swift index cb38768..28cf8bf 100644 --- a/Sources/ProHUD/Alert/AlertDefaultLayout.swift +++ b/Sources/ProHUD/Alert/AlertDefaultLayout.swift @@ -131,13 +131,6 @@ extension AlertTarget: DefaultLayout { extension AlertTarget { func setupImageView() { - // 移除动画 - stopRotate(animateLayer) - animateLayer = nil - animation = nil - - // 移除进度 - progressView?.removeFromSuperview() if vm?.icon != nil || vm?.iconURL != nil { imageView.image = vm?.icon @@ -154,9 +147,6 @@ extension AlertTarget { mk.height.equalTo(config.iconSizeByDefault.height) } } - if let rotation = vm?.rotation { - startRotate(rotation) - } } else { if contentStack.arrangedSubviews.contains(imageView) { contentStack.removeArrangedSubview(imageView) @@ -164,6 +154,9 @@ extension AlertTarget { imageView.removeFromSuperview() } + vm?.updateRotation() + vm?.updateProgress() + } func setupTextStack() { let titleCount = vm?.title?.count ?? 0 diff --git a/Sources/ProHUD/Alert/AlertManager.swift b/Sources/ProHUD/Alert/AlertManager.swift index 30b60c2..40649c2 100644 --- a/Sources/ProHUD/Alert/AlertManager.swift +++ b/Sources/ProHUD/Alert/AlertManager.swift @@ -65,14 +65,26 @@ extension AlertTarget { } } - /// 更新HUD实例 - /// - Parameter callback: 实例更新代码 - @objc open func update(handler: @escaping (_ alert: AlertTarget) -> Void) { + /// 更新VC + /// - Parameter handler: 更新操作 + @objc open func reloadData(handler: @escaping (_ capsule: AlertTarget) -> Void) { handler(self) reloadData() - UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { - self.view.layoutIfNeeded() - } + } + + /// 更新vm并刷新UI + /// - Parameter handler: 更新操作 + @objc open func vm(handler: @escaping (_ vm: ViewModel) -> ViewModel) { + let new = handler(vm ?? .init()) + vm?.update(another: new) + reloadData() + } + + /// 重设vm并刷新UI + /// - Parameter vm: 新的vm + @objc open func vm(_ vm: ViewModel) { + self.vm = vm + reloadData() } func updateTimeoutDuration() { @@ -133,7 +145,7 @@ public class AlertManager: NSObject { @discardableResult public static func find(identifier: String, update handler: ((_ alert: AlertTarget) -> Void)? = nil) -> [AlertTarget] { let arr = AppContext.alertWindow.values.flatMap({ $0.alerts }).filter({ $0.identifier == identifier }) if let handler = handler { - arr.forEach({ $0.update(handler: handler) }) + arr.forEach({ $0.reloadData(handler: handler) }) } return arr } diff --git a/Sources/ProHUD/Alert/AlertProvider.swift b/Sources/ProHUD/Alert/AlertProvider.swift index 04ca4fb..9d74123 100644 --- a/Sources/ProHUD/Alert/AlertProvider.swift +++ b/Sources/ProHUD/Alert/AlertProvider.swift @@ -14,7 +14,7 @@ open class AlertProvider: HUDProviderType { /// 根据自定义的初始化代码创建一个Target并显示 /// - Parameter initializer: 初始化代码(传空值时不会做任何事) - @discardableResult public required init(initializer: ((_ target: Target) -> Void)?) { + @discardableResult public required init(initializer: ((_ alert: Target) -> Void)?) { guard let initializer = initializer else { // Provider的作用就是push一个target // 如果没有任何初始化代码就没有target,就是个无意义的Provider @@ -33,7 +33,7 @@ open class AlertProvider: HUDProviderType { /// - initializer: 自定义的初始化代码 @discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ alert: Target) -> Void)?) { if let id = vm.identifier, id.count > 0, let target = AlertManager.find(identifier: id).last { - target.update { t in + target.reloadData { t in t.vm = vm initializer?(t) } diff --git a/Sources/ProHUD/Alert/AlertTarget.swift b/Sources/ProHUD/Alert/AlertTarget.swift index eb7b3b8..ff6335e 100644 --- a/Sources/ProHUD/Alert/AlertTarget.swift +++ b/Sources/ProHUD/Alert/AlertTarget.swift @@ -82,6 +82,7 @@ open class AlertTarget: BaseController, HUDTargetType { didSet { if let vm = vm { vm.title = title + titleLabel.text = title } else { vm = .title(title) } diff --git a/Sources/ProHUD/Capsule/CapsuleConfiguration.swift b/Sources/ProHUD/Capsule/CapsuleConfiguration.swift index 8b4e162..f41f461 100644 --- a/Sources/ProHUD/Capsule/CapsuleConfiguration.swift +++ b/Sources/ProHUD/Capsule/CapsuleConfiguration.swift @@ -31,6 +31,9 @@ public class CapsuleConfiguration: CommonConfiguration { override var cardMaxHeightByDefault: CGFloat { cardMaxHeight ?? 120 } + /// 最小宽度(当设置了最小宽度而内容没有达到时,内容布局默认靠左) + public var cardMinWidth: CGFloat? = nil + /// 最小高度 public var cardMinHeight = CGFloat(40) diff --git a/Sources/ProHUD/Capsule/CapsuleDefaultLayout.swift b/Sources/ProHUD/Capsule/CapsuleDefaultLayout.swift index 0811d15..2d9afe0 100644 --- a/Sources/ProHUD/Capsule/CapsuleDefaultLayout.swift +++ b/Sources/ProHUD/Capsule/CapsuleDefaultLayout.swift @@ -74,20 +74,18 @@ extension CapsuleTarget: DefaultLayout { if contentStack.superview == nil { view.addSubview(contentStack) contentStack.snp.remakeConstraints { make in - make.center.equalToSuperview() + make.centerY.equalToSuperview() + if config.cardMinWidth != nil { + make.left.greaterThanOrEqualToSuperview().inset(config.cardEdgeInsetsByDefault.left) + } else { + make.centerX.equalToSuperview() + } } } } private func setupImageView() { - // 移除动画 - stopRotate(animateLayer) - animateLayer = nil - animation = nil - - // 移除进度 - progressView?.removeFromSuperview() if vm?.icon == nil && vm?.iconURL == nil { contentStack.removeArrangedSubview(imageView) @@ -106,9 +104,9 @@ extension CapsuleTarget: DefaultLayout { if let iconURL = vm?.iconURL { config.customWebImage?(imageView, iconURL) } - if let rotation = vm?.rotation { - startRotate(rotation) - } + + vm?.updateRotation() + vm?.updateProgress() } @@ -118,7 +116,7 @@ extension CapsuleTarget: DefaultLayout { var size = contentStack.frame.size let width = min(config.cardMaxWidthByDefault, size.width + cardEdgeInsetsByDefault.left + cardEdgeInsetsByDefault.right) let height = min(config.cardMaxHeightByDefault, size.height + cardEdgeInsetsByDefault.top + cardEdgeInsetsByDefault.bottom) - return .init(width: width, height: max(height, config.cardMinHeight)) + return .init(width: max(width, config.cardMinWidth ?? 0), height: max(height, config.cardMinHeight)) } func getWindowFrame(size: CGSize) -> CGRect { diff --git a/Sources/ProHUD/Capsule/CapsuleManager.swift b/Sources/ProHUD/Capsule/CapsuleManager.swift index 80e5900..17ab8b3 100644 --- a/Sources/ProHUD/Capsule/CapsuleManager.swift +++ b/Sources/ProHUD/Capsule/CapsuleManager.swift @@ -172,15 +172,26 @@ extension CapsuleTarget { } } - /// 更新HUD实例 - /// - Parameter handler: 实例更新代码 - @objc open func update(handler: @escaping (_ capsule: CapsuleTarget) -> Void) { + /// 更新VC + /// - Parameter handler: 更新操作 + @objc open func reloadData(handler: @escaping (_ capsule: CapsuleTarget) -> Void) { handler(self) - reloadData() - UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { - self.view.layoutIfNeeded() - } + } + + /// 更新vm并刷新UI + /// - Parameter handler: 更新操作 + @objc open func vm(handler: @escaping (_ vm: ViewModel) -> ViewModel) { + let new = handler(vm ?? .init()) + vm?.update(another: new) + reloadData() + } + + /// 重设vm并刷新UI + /// - Parameter vm: 新的vm + @objc open func vm(_ vm: ViewModel) { + self.vm = vm + reloadData() } func updateTimeoutDuration() { @@ -204,7 +215,22 @@ public class CapsuleManager: NSObject { let allCapsules = allPositions.compactMap({ $0.capsule }) let arr = (allCapsules + AppContext.capsuleInQueue).filter({ $0.identifier == identifier || $0.vm?.identifier == identifier }) if let handler = handler { - arr.forEach({ $0.update(handler: handler) }) + arr.forEach({ $0.reloadData(handler: handler) }) + } + return arr + } + + /// 查找HUD实例 + /// - Parameters: + /// - position: 位置 + /// - handler: 更新 + /// - Returns: HUD实例 + @discardableResult public static func find(position: CapsuleViewModel.Position, update handler: ((_ capsule: CapsuleTarget) -> Void)? = nil) -> [CapsuleTarget] { + let allPositions = AppContext.capsuleWindows.values.flatMap({ $0.values }) + let allCapsules = allPositions.compactMap({ $0.capsule }) + let arr = (allCapsules + AppContext.capsuleInQueue).filter({ $0.vm?.position == position }) + if let handler = handler { + arr.forEach({ $0.reloadData(handler: handler) }) } return arr } diff --git a/Sources/ProHUD/Capsule/CapsuleProvider.swift b/Sources/ProHUD/Capsule/CapsuleProvider.swift index 10ae9f3..523c4b9 100644 --- a/Sources/ProHUD/Capsule/CapsuleProvider.swift +++ b/Sources/ProHUD/Capsule/CapsuleProvider.swift @@ -14,7 +14,7 @@ open class CapsuleProvider: HUDProviderType { /// 根据自定义的初始化代码创建一个Target并显示 /// - Parameter initializer: 初始化代码(传空值时不会做任何事) - @discardableResult public required init(initializer: ((_ target: Target) -> Void)?) { + @discardableResult public required init(initializer: ((_ capsule: Target) -> Void)?) { guard let initializer = initializer else { // Provider的作用就是push一个target // 如果没有任何初始化代码就没有target,就是个无意义的Provider @@ -33,7 +33,7 @@ open class CapsuleProvider: HUDProviderType { /// - initializer: 初始化代码 @discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ capsule: Target) -> Void)?) { if let id = vm.identifier, id.count > 0, let target = CapsuleManager.find(identifier: id).last { - target.update { t in + target.reloadData { t in t.vm = vm initializer?(t) } diff --git a/Sources/ProHUD/Capsule/CapsuleTarget.swift b/Sources/ProHUD/Capsule/CapsuleTarget.swift index 7dfde5e..a1463ca 100644 --- a/Sources/ProHUD/Capsule/CapsuleTarget.swift +++ b/Sources/ProHUD/Capsule/CapsuleTarget.swift @@ -55,6 +55,7 @@ open class CapsuleTarget: BaseController, HUDTargetType { didSet { if let vm = vm { vm.title = title + textLabel.text = title } else { vm = .title(title) } diff --git a/Sources/ProHUD/Core/Models/BaseViewModel.swift b/Sources/ProHUD/Core/Models/BaseViewModel.swift index fe4ab77..dacda60 100644 --- a/Sources/ProHUD/Core/Models/BaseViewModel.swift +++ b/Sources/ProHUD/Core/Models/BaseViewModel.swift @@ -50,6 +50,13 @@ open class BaseViewModel: NSObject, HUDViewModelType { /// 持续时间(为空代表根据场景不同的默认配置,为0代表无穷大) open var duration: TimeInterval? + /// 进度条,大于0时显示,取值区间: 0~1 + @objc open var progress: TimeProgress? { + didSet { + updateProgress() + } + } + weak var vc: BaseController? { didSet { if let id = tmpStoredIdentifier { @@ -96,6 +103,108 @@ open class BaseViewModel: NSObject, HUDViewModelType { timeoutTimer = nil } + @objc open func update(another vm: BaseViewModel) { + self.title(vm.title) + .message(vm.message) + .icon(vm.icon) + .icon(vm.iconURL) + .duration(vm.duration) + .rotation(vm.rotation) + .tintColor(vm.tintColor) + .progress(vm.progress) + } + +} + +extension BaseViewModel { + + // MARK: rotation + + func updateRotation() { + guard let vc = vc as? LoadingAnimation else { return } + DispatchQueue.main.async { + if let rotation = self.rotation { + vc.startRotate(rotation) + } else { + vc.stopRotate(vc.animateLayer) + vc.animateLayer = nil + vc.animation = nil + } + } + } + + // MARK: progress + + @discardableResult + public func progress(_ progress: TimeProgress?) -> Self { + self.progress = progress + return self + } + + @discardableResult + public func progress(_ newPercent: CGFloat) -> Self { + if progress == nil { + let p: TimeProgress = .init(total: 1) + p.set(newPercent: newPercent) + self.progress = p + } else { + self.progress?.set(newPercent: newPercent) + self.updateProgress() + } + return self + } + + func updateProgress() { + guard let vc = vc as? LoadingAnimation else { return } + guard let superview = vc.imageView.superview else { return } + DispatchQueue.main.async { + if let progress = self.progress, progress.percent > 0 { + if vc.progressView == nil { + let width = vc.imageView.frame.size.width + ProgressView.lineWidth * 2 + let v = ProgressView(frame: .init(origin: .zero, size: .init(width: width, height: width))) + superview.addSubview(v) + v.tintColor = superview.tintColor + v.snp.remakeConstraints { (mk) in + mk.center.equalTo(vc.imageView) + mk.width.height.equalTo(width) + } + vc.progressView = v + } + } else { + vc.progressView?.removeFromSuperview() + vc.progressView = nil + } + if let v = vc.progressView, let progress = self.progress { + v.progress = progress.percent + } + } + } + + // MARK: countdown + + public func countdown(seconds: TimeInterval, onUpdate: ((_ progress: TimeProgress) -> Void)?, onCompletion: (() -> Void)?) { + guard let vc = vc as? LoadingAnimation else { return } + guard seconds > 0 else { + // stop countdown + self.progress = nil + return + } + let progress: TimeProgress = .init(total: seconds, direction: .counterclockwise, onUpdate: onUpdate, onCompletion: onCompletion) + self.progress = progress + countdownLoop(after: progress.interval) + } + + func countdownLoop(after: TimeInterval) { + DispatchQueue.main.asyncAfter(deadline: .now() + after) { + if let p = self.progress, p.isFinish == false { + self.progress?.next() + self.updateProgress() + self.countdownLoop(after: p.interval) + } + } + + } + } // MARK: - convenience func diff --git a/Sources/ProHUD/Core/Models/Rotation.swift b/Sources/ProHUD/Core/Models/Rotation.swift index 9ede3c9..a938173 100644 --- a/Sources/ProHUD/Core/Models/Rotation.swift +++ b/Sources/ProHUD/Core/Models/Rotation.swift @@ -10,20 +10,13 @@ import Foundation public struct Rotation { /// 旋转方向 - public enum Direction: Double { - /// 顺时针 - case clockwise = 1 - /// 逆时针 - case counterclockwise = -1 - } - - public var direction: Direction = .clockwise + public var direction: TimeDirection = .clockwise public var speed: CFTimeInterval = 2 public var repeatCount: Float = .infinity - public init(direction: Direction = .clockwise, speed: CFTimeInterval = 2, repeatCount: Float = .infinity) { + public init(direction: TimeDirection = .clockwise, speed: CFTimeInterval = 2, repeatCount: Float = .infinity) { self.direction = direction self.speed = speed self.repeatCount = repeatCount diff --git a/Sources/ProHUD/Core/Models/TimeDirection.swift b/Sources/ProHUD/Core/Models/TimeDirection.swift new file mode 100644 index 0000000..d547179 --- /dev/null +++ b/Sources/ProHUD/Core/Models/TimeDirection.swift @@ -0,0 +1,15 @@ +// +// TimeDirection.swift +// +// +// Created by xaoxuu on 2023/8/25. +// + +import Foundation + +public enum TimeDirection: Double { + /// 顺时针 + case clockwise = 1 + /// 逆时针 + case counterclockwise = -1 +} diff --git a/Sources/ProHUD/Core/Models/TimeProgress.swift b/Sources/ProHUD/Core/Models/TimeProgress.swift new file mode 100644 index 0000000..392a98a --- /dev/null +++ b/Sources/ProHUD/Core/Models/TimeProgress.swift @@ -0,0 +1,70 @@ +// +// TimeProgress.swift +// +// +// Created by xaoxuu on 2023/8/25. +// + +import UIKit + +public class TimeProgress: NSObject { + + public var total: TimeInterval + + public var current: TimeInterval + + // 每秒10跳 + var interval: TimeInterval = 0.1 + + public var direction: TimeDirection + + public var percent: CGFloat { + guard total > 0 else { return 0 } + return current / total + } + + public var isFinish: Bool { + switch direction { + case .clockwise: + return current >= total + case .counterclockwise: + return current <= 0 + } + } + + init(total: TimeInterval, direction: TimeDirection = .clockwise, onUpdate: ((_ progress: TimeProgress) -> Void)? = nil, onCompletion: (() -> Void)? = nil) { + self.total = total + self.direction = direction + switch direction { + case .clockwise: + // 顺时针从0开始 + self.current = 0 + case .counterclockwise: + // 逆时针(倒计时)从total开始 + self.current = total + } + self.onUpdate = onUpdate + self.onCompletion = onCompletion + } + + func next() { + switch direction { + case .clockwise: + current = min(total, current + interval) + case .counterclockwise: + current = max(0, current - interval) + } + onUpdate?(self) + if isFinish { + onCompletion?() + } + } + + func set(newPercent: CGFloat) { + current = total * newPercent + } + + var onUpdate: ((_ progress: TimeProgress) -> Void)? + var onCompletion: (() -> Void)? + +} diff --git a/Sources/ProHUD/Core/Protocols/LoadingAnimation.swift b/Sources/ProHUD/Core/Protocols/LoadingAnimation.swift index 2d0575e..7e6ad8b 100644 --- a/Sources/ProHUD/Core/Protocols/LoadingAnimation.swift +++ b/Sources/ProHUD/Core/Protocols/LoadingAnimation.swift @@ -13,8 +13,4 @@ public protocol LoadingAnimation: BaseController { var imageView: UIImageView { get } var progressView: ProgressView? { get set } - /// 更新进度 - /// - Parameter progress: 进度百分比(0~1) - func update(progress: CGFloat) - } diff --git a/Sources/ProHUD/Core/Utils/RotateAnimation.swift b/Sources/ProHUD/Core/Utils/RotateAnimation.swift index 7ed636d..d5dc197 100644 --- a/Sources/ProHUD/Core/Utils/RotateAnimation.swift +++ b/Sources/ProHUD/Core/Utils/RotateAnimation.swift @@ -9,27 +9,6 @@ import UIKit extension LoadingAnimation { - /// 更新进度(如果需要显示进度,需要先调用一次 updateProgress(0) 来初始化) - /// - Parameter progress: 进度(0~1) - public func update(progress: CGFloat) { - guard isViewAppeared else { return } - guard let superview = imageView.superview else { return } - if progressView == nil { - let width = imageView.frame.size.width + ProgressView.lineWidth * 2 - let v = ProgressView(frame: .init(origin: .zero, size: .init(width: width, height: width))) - superview.addSubview(v) - v.tintColor = superview.tintColor - v.snp.remakeConstraints { (mk) in - mk.center.equalTo(imageView) - mk.width.height.equalTo(width) - } - progressView = v - } - if let v = progressView { - v.updateProgress(progress: progress) - } - } - /// 旋转动画 /// - Parameters: /// - layer: 图层 diff --git a/Sources/ProHUD/Core/Views/ProgressView.swift b/Sources/ProHUD/Core/Views/ProgressView.swift index d1f5238..62c653b 100644 --- a/Sources/ProHUD/Core/Views/ProgressView.swift +++ b/Sources/ProHUD/Core/Views/ProgressView.swift @@ -10,6 +10,12 @@ import UIKit /// 进度指示器 public class ProgressView: UIView { + var progress: CGFloat = 0 { + didSet { + updateProgress() + } + } + var progressLayer = CAShapeLayer() static var lineWidth: CGFloat { 4 } @@ -57,12 +63,16 @@ public class ProgressView: UIView { fatalError("init(coder:) has not been implemented") } - func updateProgress(progress: CGFloat) { - if progressLayer.superlayer == nil { - progressLayer.strokeEnd = 0 - layer.addSublayer(progressLayer) + func updateProgress() { + if progress > 0 { + if progressLayer.superlayer == nil { + progressLayer.strokeEnd = 0 + layer.addSublayer(progressLayer) + } + progressLayer.strokeEnd = max(min(progress, 1), 0) + } else { + progressLayer.removeFromSuperlayer() } - progressLayer.strokeEnd = max(min(progress, 1), 0) } } diff --git a/Sources/ProHUD/Toast/ToastDefaultLayout.swift b/Sources/ProHUD/Toast/ToastDefaultLayout.swift index 5b331cf..fe3dcc3 100644 --- a/Sources/ProHUD/Toast/ToastDefaultLayout.swift +++ b/Sources/ProHUD/Toast/ToastDefaultLayout.swift @@ -138,21 +138,14 @@ extension ToastTarget { } func setupImageView() { - // 移除动画 - stopRotate(animateLayer) - animateLayer = nil - animation = nil - - // 移除进度 - progressView?.removeFromSuperview() imageView.image = vm?.icon if let iconURL = vm?.iconURL { config.customWebImage?(imageView, iconURL) } - if let rotation = vm?.rotation { - startRotate(rotation) - } + + vm?.updateRotation() + vm?.updateProgress() } diff --git a/Sources/ProHUD/Toast/ToastManager.swift b/Sources/ProHUD/Toast/ToastManager.swift index 4cf9e65..24bad51 100644 --- a/Sources/ProHUD/Toast/ToastManager.swift +++ b/Sources/ProHUD/Toast/ToastManager.swift @@ -76,14 +76,26 @@ extension ToastTarget { } } - /// 更新HUD实例 - /// - Parameter handler: 实例更新代码 - @objc open func update(handler: @escaping (_ toast: ToastTarget) -> Void) { + /// 更新VC + /// - Parameter handler: 更新操作 + @objc open func reloadData(handler: @escaping (_ capsule: ToastTarget) -> Void) { handler(self) reloadData() - UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { - self.view.layoutIfNeeded() - } + } + + /// 更新vm并刷新UI + /// - Parameter handler: 更新操作 + @objc open func vm(handler: @escaping (_ vm: ViewModel) -> ViewModel) { + let new = handler(vm ?? .init()) + vm?.update(another: new) + reloadData() + } + + /// 重设vm并刷新UI + /// - Parameter vm: 新的vm + @objc open func vm(_ vm: ViewModel) { + self.vm = vm + reloadData() } func updateTimeoutDuration() { @@ -163,7 +175,7 @@ public class ToastManager: NSObject { @discardableResult public static func find(identifier: String, update handler: ((_ toast: ToastTarget) -> Void)? = nil) -> [ToastTarget] { let arr = AppContext.toastWindows.values.flatMap({ $0 }).compactMap({ $0.toast }).filter({ $0.identifier == identifier }) if let handler = handler { - arr.forEach({ $0.update(handler: handler) }) + arr.forEach({ $0.reloadData(handler: handler) }) } return arr } diff --git a/Sources/ProHUD/Toast/ToastProvider.swift b/Sources/ProHUD/Toast/ToastProvider.swift index 0a3398c..eaf167a 100644 --- a/Sources/ProHUD/Toast/ToastProvider.swift +++ b/Sources/ProHUD/Toast/ToastProvider.swift @@ -14,7 +14,7 @@ open class ToastProvider: HUDProviderType { /// 根据自定义的初始化代码创建一个Target并显示 /// - Parameter initializer: 初始化代码(传空值时不会做任何事) - @discardableResult public required init(initializer: ((_ target: Target) -> Void)?) { + @discardableResult public required init(initializer: ((_ toast: Target) -> Void)?) { guard let initializer = initializer else { // Provider的作用就是push一个target // 如果没有任何初始化代码就没有target,就是个无意义的Provider @@ -33,7 +33,7 @@ open class ToastProvider: HUDProviderType { /// - initializer: 自定义的初始化代码 @discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ toast: Target) -> Void)?) { if let id = vm.identifier, id.count > 0, let target = ToastManager.find(identifier: id).last { - target.update { t in + target.reloadData { t in t.vm = vm initializer?(t) } diff --git a/Sources/ProHUD/Toast/ToastTarget.swift b/Sources/ProHUD/Toast/ToastTarget.swift index 9eede2d..8417e35 100644 --- a/Sources/ProHUD/Toast/ToastTarget.swift +++ b/Sources/ProHUD/Toast/ToastTarget.swift @@ -94,6 +94,7 @@ open class ToastTarget: BaseController, HUDTargetType { didSet { if let vm = vm { vm.title = title + titleLabel.text = title } else { vm = .title(title) }