mirror of https://github.com/xaoxuu/ProHUD
支持倒计时
This commit is contained in:
parent
4072478569
commit
a636c0d6b4
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ open class AlertTarget: BaseController, HUDTargetType {
|
|||
didSet {
|
||||
if let vm = vm {
|
||||
vm.title = title
|
||||
titleLabel.text = title
|
||||
} else {
|
||||
vm = .title(title)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ public class CapsuleConfiguration: CommonConfiguration {
|
|||
|
||||
override var cardMaxHeightByDefault: CGFloat { cardMaxHeight ?? 120 }
|
||||
|
||||
/// 最小宽度(当设置了最小宽度而内容没有达到时,内容布局默认靠左)
|
||||
public var cardMinWidth: CGFloat? = nil
|
||||
|
||||
/// 最小高度
|
||||
public var cardMinHeight = CGFloat(40)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ open class CapsuleTarget: BaseController, HUDTargetType {
|
|||
didSet {
|
||||
if let vm = vm {
|
||||
vm.title = title
|
||||
textLabel.text = title
|
||||
} else {
|
||||
vm = .title(title)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)?
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
@ -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: 图层
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ open class ToastTarget: BaseController, HUDTargetType {
|
|||
didSet {
|
||||
if let vm = vm {
|
||||
vm.title = title
|
||||
titleLabel.text = title
|
||||
} else {
|
||||
vm = .title(title)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue