代码优化

This commit is contained in:
xaoxuu 2023-08-22 02:00:58 +08:00
parent 69afc5dd6a
commit cbd654d80b
35 changed files with 481 additions and 267 deletions

View File

@ -311,13 +311,12 @@ class DemoAlertVC: ListVC {
} }
section.add(title: "弹出loading如果已经存在就更新") { section.add(title: "弹出loading如果已经存在就更新") {
func f(i: Int) { func f(i: Int) {
Alert.lazyPush(identifier: "haha") { alert in Alert(.identifier("haha")) { alert in
if i < 2 { if i < 2 {
alert.vm = .loading.title("\(i)次弹") alert.vm = .loading.title("\(i)次弹")
let btn = alert.add(action: "请稍等", identifier: "btn") let btn = alert.add(action: "请稍等", identifier: "btn")
btn.isEnabled = false btn.isEnabled = false
} else { } else {
alert.update(progress: 1)
alert.vm = .success.title("\(i)次弹").message("只更新内容") alert.vm = .success.title("\(i)次弹").message("只更新内容")
alert.reloadTextStack() alert.reloadTextStack()
alert.update(action: "完成", style: .filled(color: .systemGreen), for: "btn") alert.update(action: "完成", style: .filled(color: .systemGreen), for: "btn")

View File

@ -84,15 +84,15 @@ class DemoCapsuleVC: ListVC {
} }
} }
list.add(title: "不同位置、不同动画,队列推送") { section in list.add(title: "不同位置、不同动画") { section in
section.add(title: "顶部,默认动画") { section.add(title: "顶部,默认动画") {
Capsule(.info("一条简短的消息").queuedPush(true).duration(1)) Capsule(.info("一条简短的消息").duration(1))
} }
section.add(title: "中间,默认动画") { section.add(title: "中间,默认动画") {
Capsule(.middle.queuedPush(true).info("一条简短的消息").duration(2)) Capsule(.middle.info("一条简短的消息").duration(1))
} }
section.add(title: "中间,黑底白字,透明渐变") { section.add(title: "中间,黑底白字,透明渐变") {
Capsule(.middle.queuedPush(true).info("一条简短的消息").duration(1)) { capsule in Capsule(.middle.info("一条简短的消息").duration(1)) { capsule in
capsule.config.tintColor = .white capsule.config.tintColor = .white
capsule.config.cardCornerRadius = 8 capsule.config.cardCornerRadius = 8
capsule.config.contentViewMask { mask in capsule.config.contentViewMask { mask in
@ -119,22 +119,25 @@ class DemoCapsuleVC: ListVC {
} }
} }
section.add(title: "底部,渐变背景,默认回弹滑入") { section.add(title: "底部,渐变背景,默认回弹滑入") {
Capsule(.bottom.queuedPush(true).enter("点击进入").duration(1)) { capsule in Capsule(.bottom.enter("点击进入").duration(2)) { capsule in
capsule.config.tintColor = .white capsule.config.tintColor = .white
capsule.config.cardEdgeInsets = .init(top: 12, left: 20, bottom: 12, right: 20) capsule.config.cardEdgeInsets = .init(top: 12, left: 20, bottom: 12, right: 20)
capsule.config.customTextLabel { label in capsule.config.customTextLabel { label in
label.textColor = .white label.textColor = .white
label.font = .boldSystemFont(ofSize: 16) label.font = .boldSystemFont(ofSize: 16)
} }
capsule.config.contentViewMask { mask in capsule.config.contentViewMask { [weak capsule] mask in
mask.effect = .none mask.effect = .none
mask.backgroundColor = .clear mask.backgroundColor = .clear
let gradientLayer = CAGradientLayer() let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.view.bounds if let f = capsule?.view.bounds {
gradientLayer.frame = f
} else {
gradientLayer.frame = .init(x: 0, y: 0, width: 300, height: 100)
}
gradientLayer.colors = [UIColor("#0091FF").cgColor, UIColor("#00FDFF").cgColor] gradientLayer.colors = [UIColor("#0091FF").cgColor, UIColor("#00FDFF").cgColor]
gradientLayer.startPoint = .init(x: 0.2, y: 0.6) gradientLayer.startPoint = .init(x: 0.2, y: 0.6)
gradientLayer.endPoint = .init(x: 0.6, y: 0.2) gradientLayer.endPoint = .init(x: 0.6, y: 0.2)
gradientLayer.frame = .init(x: 0, y: 0, width: 300, height: 100)
mask.layer.sublayers?.forEach({ $0.removeFromSuperlayer() }) mask.layer.sublayers?.forEach({ $0.removeFromSuperlayer() })
mask.layer.insertSublayer(gradientLayer, at: 0) mask.layer.insertSublayer(gradientLayer, at: 0)
} }
@ -146,25 +149,155 @@ class DemoCapsuleVC: ListVC {
} }
} }
} }
list.add(title: "lazy push") { section in list.add(title: "自定义子类,队列推送") { section in
section.add(title: "id:1, text:1") { section.add(title: "视频采集title:开") {
Capsule(.test1("111:111")) GradientCapsule(.videoRecord(on: true, text: "视频采集:开"))
} }
section.add(title: "id:1, text:2") { section.add(title: "视频推流title:开") {
Capsule(.test1("111:222")) GradientCapsule(.videoPush(on: true, text: "视频推流:开"))
} }
section.add(title: "id:2, text:1") { section.add(title: "音频采集title:开") {
Capsule(.test2("222:111")) GradientCapsule(.audioRecord(on: true, text: "音频采集:开"))
} }
section.add(title: "id:2, text:2") { section.add(title: "音频推流title:开") {
Capsule(.test2("222:222")) GradientCapsule(.audioPush(on: true, text: "音频推流:开"))
} }
section.add(title: "id:2, text:2") { section.add(title: "视频采集title:关") {
Capsule(.test2("222:222")) GradientCapsule(.videoRecord(on: false, text: "视频采集:关"))
Capsule(.test2("222:111")) }
section.add(title: "视频推流title:关") {
GradientCapsule(.videoPush(on: false, text: "视频推流:关"))
}
section.add(title: "音频采集title:关") {
GradientCapsule(.audioRecord(on: false, text: "音频采集:关"))
}
section.add(title: "音频推流title:关") {
GradientCapsule(.audioPush(on: false, text: "音频推流:关"))
} }
} }
var i = 0
list.add(title: "lazy push") { section in
section.add(title: "以当前代码位置作为唯一标识符") {
i += 1
Capsule(.codeIdentifier().message("id=\(i)"))
}
section.add(title: "指定id=a, haha") {
Capsule(.identifier("a").message("id=a, haha"))
}
section.add(title: "指定id=a, hahaha") {
Capsule(.identifier("a").message("id=a, hahaha"))
}
}
}
}
class GradientCapsuleTarget: CapsuleTarget {
override var config: CapsuleConfiguration {
get { customConfig }
set { customConfig = newValue}
}
private lazy var customConfig: CapsuleConfiguration = {
let cfg = CapsuleConfiguration()
cfg.tintColor = .white
cfg.customTextLabel { label in
label.textColor = .white
}
cfg.contentViewMask { [weak self] mask in
mask.effect = .none
mask.backgroundColor = .clear
let gradientLayer = CAGradientLayer()
if let f = self?.view.bounds {
gradientLayer.frame = f
} else {
gradientLayer.frame = .init(x: 0, y: 0, width: 300, height: 100)
}
gradientLayer.colors = [UIColor.systemGreen.cgColor, UIColor("#B6F598").cgColor]
gradientLayer.startPoint = .init(x: 0.2, y: 0.6)
gradientLayer.endPoint = .init(x: 0.6, y: 0.2)
mask.layer.sublayers?.forEach({ $0.removeFromSuperlayer() })
mask.layer.insertSublayer(gradientLayer, at: 0)
self?.gradientLayer = gradientLayer
}
return cfg
}()
var gradientLayer: CAGradientLayer?
override func viewDidLoad() {
super.viewDidLoad()
let id = vm?.identifier ?? ""
if id.contains("video") == true {
if id.contains(":true") {
gradientLayer?.colors = [UIColor("#7EF19B").cgColor, UIColor.systemGreen.cgColor]
} else {
gradientLayer?.colors = [UIColor("#1A7531").cgColor, UIColor("#2A5133").cgColor]
}
} else {
if id.contains(":true") {
gradientLayer?.colors = [UIColor("#FFC470").cgColor, UIColor.systemOrange.cgColor]
} else {
gradientLayer?.colors = [UIColor("#8F5300").cgColor, UIColor("#51412A").cgColor]
}
}
}
}
class GradientCapsule: HUDProviderType {
public typealias ViewModel = CapsuleViewModel
public typealias Target = GradientCapsuleTarget
/// Target
/// - Parameter initializer:
@discardableResult required init(initializer: ((_ target: Target) -> Void)?) {
guard let initializer = initializer else {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
}
/// ViewModelTarget
/// - Parameters:
/// - vm:
/// - 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
capsule.vm = vm
initializer?(capsule as! GradientCapsule.Target)
}
self.init(initializer: nil)
} else {
self.init { target in
target.vm = vm
initializer?(target)
}
}
}
/// ViewModelTarget
/// - Parameter vm:
@discardableResult public convenience init(_ vm: ViewModel) {
self.init(vm, initializer: nil)
}
/// Target
/// - Parameter text:
@discardableResult public convenience init(_ text: String) {
self.init(.message(text), initializer: nil)
} }
} }
@ -198,22 +331,40 @@ extension CapsuleViewModel {
.message(text) .message(text)
} }
static func test1(_ text: String) -> CapsuleViewModel { static func videoRecord(on: Bool, text: String) -> CapsuleViewModel {
.identifier("id:1") .middle
.icon(.init(systemName: "video.circle.fill")) .identifier("video.record:\(on)")
.tintColor(.systemGreen) .icon(.init(systemName: on ? "video.fill" : "video.slash.fill"))
.duration(1) .duration(0.5)
.queuedPush(true) .queuedPush(true)
.message(text) .message(text)
} }
static func test2(_ text: String) -> CapsuleViewModel { static func audioRecord(on: Bool, text: String) -> CapsuleViewModel {
.identifier("id:2") .middle
.icon(.init(systemName: "mic.circle.fill")) .identifier("audio.record:\(on)")
.tintColor(.systemOrange) .icon(.init(systemName: on ? "mic.fill" : "mic.slash.fill"))
.duration(1) .duration(0.5)
.queuedPush(true) .queuedPush(true)
.message(text) .message(text)
} }
static func videoPush(on: Bool, text: String) -> CapsuleViewModel {
.middle
.identifier("video.push:\(on)")
.icon(.init(systemName: on ? "icloud.fill" : "icloud.slash.fill"))
.duration(0.5)
.queuedPush(true)
.message(text)
}
static func audioPush(on: Bool, text: String) -> CapsuleViewModel {
.middle
.identifier("audio.push:\(on)")
.icon(.init(systemName: on ? "icloud.fill" : "icloud.slash.fill"))
.duration(0.5)
.queuedPush(true)
.message(text)
}
} }

View File

@ -74,12 +74,13 @@ class DemoSheetVC: ListVC {
sheet.add(action: "确认") sheet.add(action: "确认")
sheet.add(action: "取消", style: .gray) sheet.add(action: "取消", style: .gray)
sheet.onTappedBackground { sheet in sheet.onTappedBackground { sheet in
Toast.lazyPush(identifier: "alert") { toast in Toast(
toast.vm = .error .error
.title("点击了背景") .lazyIdentifier()
.message("点击背景将不会dismiss必须在下方做出选择才能关掉") .title("点击了背景")
.duration(2) .message("点击背景将不会dismiss必须在下方做出选择才能关掉")
} .duration(2)
)
} }
} }
} }

View File

@ -34,14 +34,61 @@ let isTesting: Bool = true
class TestToastTarget: ToastTarget { class TestToastTarget: ToastTarget {
override func push() { override func push() {
print("isTesting: \(isTesting)")
guard isTesting else { return } guard isTesting else { return }
super.push() super.push()
} }
} }
//typealias TestToast = HUDProvider<ToastViewModel, TestToastTarget> class TestToast: HUDProviderType {
class TestToast: ToastProvider {
typealias Target = TestToastTarget public typealias ViewModel = ToastViewModel
public typealias Target = TestToastTarget
/// Target
/// - Parameter initializer:
@discardableResult public required init(initializer: ((_ target: Target) -> Void)?) {
guard let initializer = initializer else {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
}
/// ViewModelTarget
/// - Parameters:
/// - vm:
/// - 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 as? Target {
target.vm = vm
initializer?(target)
self.init(initializer: nil)
} else {
self.init { target in
target.vm = vm
initializer?(target)
}
}
}
/// ViewModelTarget
/// - Parameter vm:
@discardableResult public convenience init(_ vm: ViewModel) {
self.init(vm, initializer: nil)
}
/// Target
/// - Parameter text:
@discardableResult @objc public convenience init(_ text: String) {
self.init(.message(text), initializer: nil)
}
} }
class DemoToastVC: ListVC { class DemoToastVC: ListVC {
@ -127,12 +174,12 @@ class DemoToastVC: ListVC {
section.add(title: "增加按钮") { section.add(title: "增加按钮") {
let title = "您收到了一条好友申请" let title = "您收到了一条好友申请"
let message = "丹妮莉丝·坦格利安申请添加您为好友,是否同意?" let message = "丹妮莉丝·坦格利安申请添加您为好友,是否同意?"
Toast(.title(title).message(message).icon(.init(named: "avatar"))) { toast in Toast(.title(title).message(message).icon(.init(named: "avatar")).duration(.infinity)) { toast in
toast.isRemovable = false toast.isRemovable = false
toast.imageView.layer.masksToBounds = true toast.imageView.layer.masksToBounds = true
toast.imageView.layer.cornerRadius = toast.config.iconSize.width / 2 toast.imageView.layer.cornerRadius = toast.config.iconSize.width / 2
toast.add(action: "拒绝", style: .destructive) { toast in toast.add(action: "拒绝", style: .destructive) { toast in
Alert.lazyPush(identifier: "Dracarys") { alert in Alert(.identifier("Dracarys")) { alert in
alert.vm = .message("Dracarys") alert.vm = .message("Dracarys")
.icon(UIImage(inProHUD: "prohud.windmill")) .icon(UIImage(inProHUD: "prohud.windmill"))
.rotation(.init(repeatCount: .infinity)) .rotation(.init(repeatCount: .infinity))
@ -152,13 +199,13 @@ class DemoToastVC: ListVC {
} }
} }
toast.add(action: "同意") { toast in toast.add(action: "同意") { toast in
Alert.find(identifier: "Dracarys", update: { alert in AlertManager.find(identifier: "Dracarys", update: { alert in
alert.pop() alert.pop()
}) })
toast.pop() toast.pop()
Alert(.success(1).message("Good choice!")) Alert(.success(1).message("Good choice!"))
} }
Toast.find(identifier: "loading") { toast in ToastManager.find(identifier: "loading") { toast in
toast.vm = .success(2).message("加载成功") toast.vm = .success(2).message("加载成功")
} }
} }
@ -190,12 +237,12 @@ class DemoToastVC: ListVC {
} }
section.add(title: "如果存在就更新,如果不存在就忽略") { section.add(title: "如果存在就更新,如果不存在就忽略") {
i += 1 i += 1
Toast.find(identifier: "loading") { toast in ToastManager.find(identifier: "loading") { toast in
toast.vm = .success.title("加载完成\(i)").message("这条消息不会重复显示多条").duration(2) toast.vm = .success.title("加载完成\(i)").message("这条消息不会重复显示多条").duration(2)
} }
} }
section.add(title: "移除指定实例") { section.add(title: "移除指定实例") {
Toast.find(identifier: "loading") { toast in ToastManager.find(identifier: "loading") { toast in
toast.pop() toast.pop()
} }
} }

View File

@ -14,7 +14,12 @@ public class AlertConfiguration: CommonConfiguration {
public var enableShadow: Bool = true public var enableShadow: Bool = true
static var customGlobalConfig: ((_ config: AlertConfiguration) -> Void)? private static var customGlobalConfig: ((_ config: AlertConfiguration) -> Void)?
public override init() {
super.init()
Self.customGlobalConfig?(self)
}
/// ///
/// - Parameter callback: /// - Parameter callback:

View File

@ -32,7 +32,7 @@ extension AlertTarget: InternalConvenienceLayout {
self?.pop() self?.pop()
} }
} }
if isViewDisplayed { if isViewAppeared {
self.actionStack.layoutIfNeeded() self.actionStack.layoutIfNeeded()
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.view.layoutIfNeeded() self.view.layoutIfNeeded()
@ -78,7 +78,7 @@ extension AlertTarget: InternalConvenienceLayout {
buttonEvents[view] = nil buttonEvents[view] = nil
} }
} }
if isViewDisplayed { if isViewAppeared {
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.actionStack.layoutIfNeeded() self.actionStack.layoutIfNeeded()
self.view.layoutIfNeeded() self.view.layoutIfNeeded()

View File

@ -26,8 +26,10 @@ extension AlertTarget: DefaultLayout {
} else { } else {
isFirstLayout = false isFirstLayout = false
} }
// if isViewAppeared {
updateTimeoutDuration() //
updateTimeoutDuration()
}
// custom layout // custom layout
guard customView == nil else { guard customView == nil else {
return return
@ -116,7 +118,7 @@ extension AlertTarget: DefaultLayout {
} }
func setDefaultAxis() { func setDefaultAxis() {
guard isViewDisplayed == false && config.customActionStack == nil else { return } guard isViewAppeared == false && config.customActionStack == nil else { return }
let count = actionStack.arrangedSubviews.filter({ $0.isKind(of: UIControl.self )}).count let count = actionStack.arrangedSubviews.filter({ $0.isKind(of: UIControl.self )}).count
guard count < 4 else { return } guard count < 4 else { return }
if (isPortrait && count < 3) || !isPortrait { if (isPortrait && count < 3) || !isPortrait {

View File

@ -24,6 +24,8 @@ extension AlertTarget {
view.snp.makeConstraints { make in view.snp.makeConstraints { make in
make.edges.equalToSuperview() make.edges.equalToSuperview()
} }
//
updateTimeoutDuration()
UIView.animateEaseOut(duration: config.animateDurationForBuildIn ?? config.animateDurationForBuildInByDefault) { UIView.animateEaseOut(duration: config.animateDurationForBuildIn ?? config.animateDurationForBuildInByDefault) {
self.view.transform = .identity self.view.transform = .identity
self.view.alpha = 1 self.view.alpha = 1
@ -33,7 +35,6 @@ extension AlertTarget {
window.backgroundView.alpha = 1 window.backgroundView.alpha = 1
} completion: { done in } completion: { done in
self.navEvents[.onViewDidAppear]?(self) self.navEvents[.onViewDidAppear]?(self)
self.updateTimeoutDuration()
} }
window.alerts.append(self) window.alerts.append(self)
AlertTarget.updateAlertsLayout(alerts: window.alerts) AlertTarget.updateAlertsLayout(alerts: window.alerts)
@ -76,9 +77,7 @@ extension AlertTarget {
func updateTimeoutDuration() { func updateTimeoutDuration() {
// //
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in vm?.restartTimer()
self?.pop()
})
} }
} }
@ -125,3 +124,18 @@ fileprivate extension AlertTarget {
} }
public class AlertManager: NSObject {
/// HUD
/// - Parameter identifier:
/// - Returns: HUD
@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) })
}
return arr
}
}

View File

@ -7,13 +7,24 @@
import UIKit import UIKit
open class AlertProvider: HUDProvider<AlertViewModel, AlertTarget> { public final class AlertProvider: HUDProviderType {
public typealias ViewModel = AlertViewModel public typealias ViewModel = AlertViewModel
public typealias Target = AlertTarget public typealias Target = AlertTarget
@discardableResult @objc public required init(initializer: ((_ alert: Target) -> Void)?) { /// Target
super.init(initializer: initializer) /// - Parameter initializer:
@discardableResult public required init(initializer: ((_ target: Target) -> Void)?) {
guard let initializer = initializer else {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
} }
/// ViewModelTarget /// ViewModelTarget
@ -21,10 +32,10 @@ open class AlertProvider: HUDProvider<AlertViewModel, AlertTarget> {
/// - vm: /// - vm:
/// - initializer: /// - initializer:
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ alert: Target) -> Void)?) { @discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ alert: Target) -> Void)?) {
if let id = vm.identifier, id.count > 0 { if let id = vm.identifier, id.count > 0, let target = AlertManager.find(identifier: id).last {
Self.lazyPush(identifier: id) { target in target.update { t in
target.vm = vm t.vm = vm
initializer?(target) initializer?(t)
} }
self.init(initializer: nil) self.init(initializer: nil)
} else { } else {
@ -34,39 +45,13 @@ open class AlertProvider: HUDProvider<AlertViewModel, AlertTarget> {
} }
} }
} }
/// ViewModelTarget /// ViewModelTarget
/// - Parameter vm: /// - Parameter vm:
@discardableResult public convenience init(_ vm: ViewModel) { @discardableResult public convenience init(_ vm: ViewModel) {
self.init(vm, initializer: nil) self.init(vm, initializer: nil)
} }
/// HUD
/// - Parameters:
/// - identifier:
/// - handler:
public static func lazyPush(identifier: String? = nil, file: String = #file, line: Int = #line, handler: @escaping (_ alert: AlertTarget) -> Void, onExists: ((_ alert: AlertTarget) -> Void)? = nil) {
let id = identifier ?? (file + "#\(line)")
if let vc = find(identifier: id).last {
vc.update(handler: onExists ?? handler)
} else {
Self.init { alert in
alert.identifier = id
handler(alert)
}
}
}
/// HUD
/// - Parameter identifier:
/// - Returns: HUD
@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) })
}
return arr
}
} }
public typealias Alert = AlertProvider public typealias Alert = AlertProvider

View File

@ -11,11 +11,7 @@ open class AlertTarget: BaseController, HUDTargetType {
public typealias ViewModel = AlertViewModel public typealias ViewModel = AlertViewModel
public lazy var config: AlertConfiguration = { public lazy var config = AlertConfiguration()
var cfg = AlertConfiguration()
AlertConfiguration.customGlobalConfig?(cfg)
return cfg
}()
public var progressView: ProgressView? public var progressView: ProgressView?

View File

@ -11,7 +11,12 @@ public class CapsuleConfiguration: CommonConfiguration {
public typealias CustomAnimateHandler = ((_ window: UIWindow, _ completion: @escaping () -> Void) -> Void) public typealias CustomAnimateHandler = ((_ window: UIWindow, _ completion: @escaping () -> Void) -> Void)
static var customGlobalConfig: ((_ config: CapsuleConfiguration) -> Void)? private static var customGlobalConfig: ((_ config: CapsuleConfiguration) -> Void)?
public override init() {
super.init()
Self.customGlobalConfig?(self)
}
/// ///
/// - Parameter callback: /// - Parameter callback:

View File

@ -43,10 +43,9 @@ extension CapsuleTarget: DefaultLayout {
view.layoutIfNeeded() view.layoutIfNeeded()
// if isViewAppeared {
updateTimeoutDuration() //
updateTimeoutDuration()
if isViewDisplayed {
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.view.layoutIfNeeded() self.view.layoutIfNeeded()
} }
@ -102,4 +101,5 @@ extension CapsuleTarget: DefaultLayout {
} }
} }
} }

View File

@ -35,6 +35,7 @@ extension CapsuleTarget {
// //
isNew = false isNew = false
window = w window = w
window.capsule = self
} }
} else { } else {
// //
@ -42,7 +43,7 @@ extension CapsuleTarget {
window = CapsuleWindow(capsule: self) window = CapsuleWindow(capsule: self)
windows[position] = nil windows[position] = nil
} }
window.isUserInteractionEnabled = tapActionCallback != nil
// frame // frame
let cardEdgeInsetsByDefault = config.cardEdgeInsetsByDefault let cardEdgeInsetsByDefault = config.cardEdgeInsetsByDefault
view.layoutIfNeeded() view.layoutIfNeeded()
@ -83,10 +84,10 @@ extension CapsuleTarget {
// toast // toast
ToastWindow.updateToastWindowsLayout() ToastWindow.updateToastWindowsLayout()
} }
//
updateTimeoutDuration()
func completion() { func completion() {
self.navEvents[.onViewDidAppear]?(self) self.navEvents[.onViewDidAppear]?(self)
self.updateTimeoutDuration()
} }
if isNew { if isNew {
window.isHidden = false window.isHidden = false
@ -149,10 +150,10 @@ extension CapsuleTarget {
window.transform = .identity window.transform = .identity
self.navEvents[.onViewDidDisappear]?(self) self.navEvents[.onViewDidDisappear]?(self)
} }
var duration = config.animateDurationForBuildOutByDefault
if let animateBuildOut = config.animateBuildOut { if let animateBuildOut = config.animateBuildOut {
animateBuildOut(window, completion) animateBuildOut(window, completion)
} else { } else {
let duration = config.animateDurationForBuildOutByDefault
let oldFrame = window.frame let oldFrame = window.frame
switch position { switch position {
case .top: case .top:
@ -162,13 +163,13 @@ extension CapsuleTarget {
completion() completion()
} }
case .middle: case .middle:
let duration = config.animateDurationForBuildInByDefault * 1 duration = config.animateDurationForBuildInByDefault
UIView.animateEaseIn(duration: duration) { UIView.animateEaseIn(duration: duration) {
window.transform = .init(translationX: 0, y: -24) window.transform = .init(translationX: 0, y: -24)
} completion: { done in } completion: { done in
completion() completion()
} }
UIView.animateLinear(duration: duration * 0.5, delay: duration * 0.3) { UIView.animateEaseIn(duration: duration * 0.5, delay: duration * 0.5) {
window.alpha = 0 window.alpha = 0
} }
case .bottom: case .bottom:
@ -182,7 +183,8 @@ extension CapsuleTarget {
} }
if let next = AppContext.capsuleInQueue.first(where: { $0.preferredWindowScene == windowScene && $0.vm?.position == position }) { if let next = AppContext.capsuleInQueue.first(where: { $0.preferredWindowScene == windowScene && $0.vm?.position == position }) {
AppContext.capsuleInQueue.removeAll(where: { $0 == next }) AppContext.capsuleInQueue.removeAll(where: { $0 == next })
DispatchQueue.main.asyncAfter(deadline: .now() + config.animateDurationForBuildOutByDefault * 0.8) { // poppush
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
next.push() next.push()
} }
} }
@ -205,9 +207,24 @@ extension CapsuleTarget {
vm?.duration = config.defaultDuration vm?.duration = config.defaultDuration
} }
// //
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in vm?.restartTimer()
self?.pop() }
})
}
public class CapsuleManager: NSObject {
/// HUD
/// - Parameter identifier:
/// - Returns: HUD
@discardableResult public static func find(identifier: String, 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.identifier == identifier || $0.vm?.identifier == identifier })
if let handler = handler {
arr.forEach({ $0.update(handler: handler) })
}
return arr
} }
} }

View File

@ -7,13 +7,24 @@
import UIKit import UIKit
open class CapsuleProvider: HUDProvider<CapsuleViewModel, CapsuleTarget> { public final class CapsuleProvider: HUDProviderType {
public typealias ViewModel = CapsuleViewModel public typealias ViewModel = CapsuleViewModel
public typealias Target = CapsuleTarget public typealias Target = CapsuleTarget
@discardableResult @objc public required init(initializer: ((_ capsule: Target) -> Void)?) { /// Target
super.init(initializer: initializer) /// - Parameter initializer:
@discardableResult public required init(initializer: ((_ target: Target) -> Void)?) {
guard let initializer = initializer else {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
} }
/// ViewModelTarget /// ViewModelTarget
@ -21,10 +32,10 @@ open class CapsuleProvider: HUDProvider<CapsuleViewModel, CapsuleTarget> {
/// - vm: /// - vm:
/// - initializer: /// - initializer:
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ capsule: Target) -> Void)?) { @discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ capsule: Target) -> Void)?) {
if let id = vm.identifier, id.count > 0 { if let id = vm.identifier, id.count > 0, let target = CapsuleManager.find(identifier: id).last {
Self.lazyPush(identifier: id) { target in target.update { t in
target.vm = vm t.vm = vm
initializer?(target) initializer?(t)
} }
self.init(initializer: nil) self.init(initializer: nil)
} else { } else {
@ -47,36 +58,6 @@ open class CapsuleProvider: HUDProvider<CapsuleViewModel, CapsuleTarget> {
self.init(.message(text), initializer: nil) self.init(.message(text), initializer: nil)
} }
/// HUD
/// - Parameters:
/// - identifier:
/// - handler:
public static func lazyPush(identifier: String? = nil, file: String = #file, line: Int = #line, handler: @escaping (_ capsule: CapsuleTarget) -> Void, onExists: ((_ capsule: CapsuleTarget) -> Void)? = nil) {
let id = identifier ?? (file + "#\(line)")
if let vc = find(identifier: id).last {
vc.update(handler: onExists ?? handler)
} else {
Self.init { capsule in
capsule.identifier = id
handler(capsule)
}
}
}
/// HUD
/// - Parameter identifier:
/// - Returns: HUD
@discardableResult public static func find(identifier: String, 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.identifier == identifier })
if let handler = handler {
arr.forEach({ $0.update(handler: handler) })
}
return arr
}
} }
public typealias Capsule = CapsuleProvider public typealias Capsule = CapsuleProvider

View File

@ -11,11 +11,7 @@ open class CapsuleTarget: BaseController, HUDTargetType {
public typealias ViewModel = CapsuleViewModel public typealias ViewModel = CapsuleViewModel
@objc public lazy var config: CapsuleConfiguration = { @objc open lazy var config = CapsuleConfiguration()
var cfg = CapsuleConfiguration()
CapsuleConfiguration.customGlobalConfig?(cfg)
return cfg
}()
/// imageViewtextLabel) /// imageViewtextLabel)
public lazy var contentStack: StackView = { public lazy var contentStack: StackView = {
@ -49,6 +45,9 @@ open class CapsuleTarget: BaseController, HUDTargetType {
}() }()
@objc public var vm: CapsuleViewModel? { @objc public var vm: CapsuleViewModel? {
willSet {
vm?.cancelTimer()
}
didSet { didSet {
vm?.vc = self vm?.vc = self
} }
@ -64,7 +63,7 @@ open class CapsuleTarget: BaseController, HUDTargetType {
} }
} }
private var tapActionCallback: ((_ capsule: CapsuleTarget) -> Void)? var tapActionCallback: ((_ capsule: CapsuleTarget) -> Void)?
required public override init() { required public override init() {
super.init() super.init()
@ -79,7 +78,7 @@ open class CapsuleTarget: BaseController, HUDTargetType {
self.vm = vm self.vm = vm
} }
public override func viewDidLoad() { open override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
view.layer.shadowRadius = 8 view.layer.shadowRadius = 8
view.layer.shadowOffset = .init(width: 0, height: 5) view.layer.shadowOffset = .init(width: 0, height: 5)

View File

@ -14,6 +14,7 @@ class CapsuleWindow: Window {
init(capsule: CapsuleTarget) { init(capsule: CapsuleTarget) {
self.capsule = capsule self.capsule = capsule
super.init(frame: .zero) super.init(frame: .zero)
isUserInteractionEnabled = false
windowScene = AppContext.windowScene windowScene = AppContext.windowScene
switch capsule.vm?.position { switch capsule.vm?.position {
case .top, .none: case .top, .none:

View File

@ -25,7 +25,7 @@ open class BaseController: UIViewController {
open var customView: UIView? open var customView: UIView?
public internal(set) var isViewDisplayed = false public internal(set) var isViewAppeared = false
/// ///
var buttonEvents = [UIView: () -> Void]() var buttonEvents = [UIView: () -> Void]()
@ -43,7 +43,7 @@ open class BaseController: UIViewController {
consolePrint("👌", self, "deinit") consolePrint("👌", self, "deinit")
} }
override public func viewDidLoad() { override open func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// Do any additional setup after loading the view. // Do any additional setup after loading the view.
@ -89,7 +89,7 @@ open class BaseController: UIViewController {
open override func viewDidAppear(_ animated: Bool) { open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
isViewDisplayed = true isViewAppeared = true
} }
} }

View File

@ -48,11 +48,7 @@ open class BaseViewModel: NSObject, HUDViewModelType {
@objc open var tintColor: UIColor? @objc open var tintColor: UIColor?
/// 0 /// 0
open var duration: TimeInterval? { open var duration: TimeInterval?
didSet {
resetTimeoutHandler()
}
}
weak var vc: BaseController? { weak var vc: BaseController? {
didSet { didSet {
@ -72,25 +68,32 @@ open class BaseViewModel: NSObject, HUDViewModelType {
self.duration = duration self.duration = duration
} }
/// private var timeoutTimer: Timer?
var timeoutHandler: DispatchWorkItem? {
didSet { func restartTimer() {
resetTimeoutHandler() cancelTimer()
if let duration = duration {
if duration > 0 {
let timer = Timer(timeInterval: duration, repeats: false, block: { [weak self] t in
if let vc = self?.vc as? any HUDTargetType {
vc.pop()
}
})
RunLoop.main.add(timer, forMode: .common)
timeoutTimer = timer
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
if let vc = self.vc as? any HUDTargetType {
vc.pop()
}
}
}
} }
} }
var timeoutTimer: Timer? func cancelTimer() {
func resetTimeoutHandler() {
timeoutTimer?.invalidate() timeoutTimer?.invalidate()
timeoutTimer = nil timeoutTimer = nil
if let t = duration, t > 0 {
let timer = Timer(timeInterval: t, repeats: false, block: { [weak self] t in
self?.timeoutHandler?.perform()
})
RunLoop.main.add(timer, forMode: .common)
timeoutTimer = timer
}
} }
} }
@ -103,6 +106,11 @@ public extension BaseViewModel {
return self return self
} }
func lazyIdentifier(file: String = #file, line: Int = #line) -> Self {
self.identifier = (file + "#\(line)")
return self
}
func icon(_ image: UIImage?) -> Self { func icon(_ image: UIImage?) -> Self {
self.icon = image self.icon = image
return self return self
@ -143,11 +151,18 @@ public extension BaseViewModel {
// MARK: - example scenes // MARK: - example scenes
public extension BaseViewModel { public extension BaseViewModel {
///
/// - Parameter text:
static func identifier(_ text: String?) -> Self { static func identifier(_ text: String?) -> Self {
.init() .init()
.identifier(text) .identifier(text)
} }
///
static func codeIdentifier(file: String = #file, line: Int = #line) -> Self {
identifier((file + "#\(line)"))
}
// MARK: plain // MARK: plain
static func title(_ text: String?) -> Self { static func title(_ text: String?) -> Self {
.init() .init()

View File

@ -188,7 +188,7 @@ open class CommonConfiguration: NSObject {
customContentViewMask = callback customContentViewMask = callback
} }
override init() { public override init() {
} }

View File

@ -18,21 +18,3 @@ public protocol HUDProviderType {
} }
open class HUDProvider<ViewModel: HUDViewModelType, Target: HUDTargetType>: NSObject, HUDProviderType {
/// Target
/// - Parameter initializer:
@discardableResult public required init(initializer: ((_ target: Target) -> Void)?) {
guard let initializer = initializer else {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
var t = Target()
initializer(t)
t.push()
}
}

View File

@ -14,6 +14,7 @@ import UIKit
public protocol HUDTargetType: HUDControllerType { public protocol HUDTargetType: HUDControllerType {
associatedtype ViewModel = HUDViewModelType associatedtype ViewModel = HUDViewModelType
var identifier: String { get set }
var vm: ViewModel? { get set } var vm: ViewModel? { get set }
init() init()
} }

View File

@ -12,7 +12,7 @@ extension LoadingAnimation {
/// updateProgress(0) /// updateProgress(0)
/// - Parameter progress: 0~1 /// - Parameter progress: 0~1
public func update(progress: CGFloat) { public func update(progress: CGFloat) {
guard isViewDisplayed else { return } guard isViewAppeared else { return }
guard let superview = imageView.superview else { return } guard let superview = imageView.superview else { return }
if progressView == nil { if progressView == nil {
let width = imageView.frame.size.width + ProgressView.lineWidth * 2 let width = imageView.frame.size.width + ProgressView.lineWidth * 2

View File

@ -12,8 +12,8 @@ extension UIView {
static func animateLinear(duration: TimeInterval, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) { static func animateLinear(duration: TimeInterval, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) {
animate(withDuration: duration, delay: delay, options: [.allowUserInteraction], animations: animations, completion: completion) animate(withDuration: duration, delay: delay, options: [.allowUserInteraction], animations: animations, completion: completion)
} }
static func animateEaseIn(duration: TimeInterval, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) { static func animateEaseIn(duration: TimeInterval, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) {
animate(withDuration: duration, delay: 0, options: [.allowUserInteraction, .curveEaseIn], animations: animations, completion: completion) animate(withDuration: duration, delay: delay, options: [.allowUserInteraction, .curveEaseIn], animations: animations, completion: completion)
} }
static func animateEaseOut(duration: TimeInterval, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) { static func animateEaseOut(duration: TimeInterval, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) {
animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseOut], animations: animations, completion: completion) animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseOut], animations: animations, completion: completion)

View File

@ -26,7 +26,12 @@ public class SheetConfiguration: CommonConfiguration {
customSubtitleLabel = handler customSubtitleLabel = handler
} }
static var customGlobalConfig: ((_ config: SheetConfiguration) -> Void)? private static var customGlobalConfig: ((_ config: SheetConfiguration) -> Void)?
public override init() {
super.init()
Self.customGlobalConfig?(self)
}
/// ///
/// - Parameter callback: /// - Parameter callback:

View File

@ -28,7 +28,7 @@ extension SheetTarget: ConvenienceLayout {
self?.pop() self?.pop()
} }
} }
if isViewDisplayed { if isViewAppeared {
self.contentStack.layoutIfNeeded() self.contentStack.layoutIfNeeded()
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.view.layoutIfNeeded() self.view.layoutIfNeeded()
@ -74,7 +74,7 @@ extension SheetTarget: ConvenienceLayout {
buttonEvents[view] = nil buttonEvents[view] = nil
} }
} }
if isViewDisplayed { if isViewAppeared {
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.contentStack.layoutIfNeeded() self.contentStack.layoutIfNeeded()
self.view.layoutIfNeeded() self.view.layoutIfNeeded()

View File

@ -29,7 +29,7 @@ extension SheetTarget: DefaultLayout {
// content // content
loadContentViewIfNeeded() loadContentViewIfNeeded()
if isViewDisplayed { if isViewAppeared {
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.view.layoutIfNeeded() self.view.layoutIfNeeded()
} }

View File

@ -17,11 +17,13 @@ extension SheetTarget {
if let w = windows.first(where: { $0.sheet == self }) { if let w = windows.first(where: { $0.sheet == self }) {
isNew = false isNew = false
window = w window = w
window.sheet = self
} else { } else {
window = SheetWindow(sheet: self) window = SheetWindow(sheet: self)
isNew = true isNew = true
} }
window.rootViewController = self window.rootViewController = self
if windows.contains(window) == false { if windows.contains(window) == false {
windows.append(window) windows.append(window)
setContextWindows(windows) setContextWindows(windows)

View File

@ -7,20 +7,31 @@
import UIKit import UIKit
open class SheetProvider: HUDProvider<SheetViewModel, SheetTarget> { public final class SheetProvider: HUDProviderType {
public typealias ViewModel = SheetViewModel public typealias ViewModel = SheetViewModel
public typealias Target = SheetTarget public typealias Target = SheetTarget
@discardableResult @objc public required init(initializer: ((_ sheet: Target) -> Void)?) { /// Target
super.init(initializer: initializer) /// - Parameter initializer:
@discardableResult public required init(initializer: ((_ target: Target) -> Void)?) {
guard let initializer = initializer else {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
} }
/// HUD /// HUD
/// - Parameters: /// - Parameters:
/// - identifier: /// - identifier:
/// - handler: /// - handler:
public static func lazyPush(identifier: String? = nil, file: String = #file, line: Int = #line, handler: @escaping (_ sheet: SheetTarget) -> Void, onExists: ((_ sheet: SheetTarget) -> Void)? = nil) { public static func lazyPush(identifier: String? = nil, file: String = #file, line: Int = #line, handler: @escaping (_ sheet: Target) -> Void, onExists: ((_ sheet: Target) -> Void)? = nil) {
let id = identifier ?? (file + "#\(line)") let id = identifier ?? (file + "#\(line)")
if let vc = find(identifier: id).last { if let vc = find(identifier: id).last {
vc.update(handler: onExists ?? handler) vc.update(handler: onExists ?? handler)
@ -35,7 +46,7 @@ open class SheetProvider: HUDProvider<SheetViewModel, SheetTarget> {
/// HUD /// HUD
/// - Parameter identifier: /// - Parameter identifier:
/// - Returns: HUD /// - Returns: HUD
@discardableResult public static func find(identifier: String, update handler: ((_ sheet: SheetTarget) -> Void)? = nil) -> [SheetTarget] { @discardableResult public static func find(identifier: String, update handler: ((_ sheet: Target) -> Void)? = nil) -> [Target] {
let arr = AppContext.sheetWindows.values.flatMap({ $0 }).compactMap({ $0.sheet }).filter({ $0.identifier == identifier }) let arr = AppContext.sheetWindows.values.flatMap({ $0 }).compactMap({ $0.sheet }).filter({ $0.identifier == identifier })
if let handler = handler { if let handler = handler {
arr.forEach({ $0.update(handler: handler) }) arr.forEach({ $0.update(handler: handler) })

View File

@ -11,11 +11,7 @@ open class SheetTarget: BaseController, HUDTargetType {
weak var window: SheetWindow? weak var window: SheetWindow?
public lazy var config: SheetConfiguration = { public lazy var config = SheetConfiguration()
var cfg = SheetConfiguration()
SheetConfiguration.customGlobalConfig?(cfg)
return cfg
}()
public lazy var backgroundView: UIView = { public lazy var backgroundView: UIView = {
let v = UIView() let v = UIView()

View File

@ -9,7 +9,12 @@ import UIKit
public class ToastConfiguration: CommonConfiguration { public class ToastConfiguration: CommonConfiguration {
static var customGlobalConfig: ((_ config: ToastConfiguration) -> Void)? private static var customGlobalConfig: ((_ config: ToastConfiguration) -> Void)?
public override init() {
super.init()
Self.customGlobalConfig?(self)
}
/// ///
/// - Parameter callback: /// - Parameter callback:

View File

@ -51,7 +51,7 @@ extension ToastTarget: ConvenienceLayout {
self?.pop() self?.pop()
} }
} }
if isViewDisplayed { if isViewAppeared {
self.actionStack.layoutIfNeeded() self.actionStack.layoutIfNeeded()
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.view.layoutIfNeeded() self.view.layoutIfNeeded()
@ -99,7 +99,7 @@ extension ToastTarget: ConvenienceLayout {
buttonEvents[view] = nil buttonEvents[view] = nil
} }
} }
if isViewDisplayed { if isViewAppeared {
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.contentStack.layoutIfNeeded() self.contentStack.layoutIfNeeded()
self.view.layoutIfNeeded() self.view.layoutIfNeeded()

View File

@ -87,8 +87,10 @@ extension ToastTarget: DefaultLayout {
bodyLabel.text = vm?.message bodyLabel.text = vm?.message
view.layoutIfNeeded() view.layoutIfNeeded()
// if isViewAppeared {
updateTimeoutDuration() //
updateTimeoutDuration()
}
setupImageView() setupImageView()

View File

@ -43,6 +43,7 @@ extension ToastTarget {
if let w = windows.first(where: { $0.toast == self }) { if let w = windows.first(where: { $0.toast == self }) {
isNew = false isNew = false
window = w window = w
window.toast = self
} else { } else {
window = ToastWindow(toast: self) window = ToastWindow(toast: self)
isNew = true isNew = true
@ -66,10 +67,10 @@ extension ToastTarget {
setContextWindows(windows) setContextWindows(windows)
} }
ToastWindow.updateToastWindowsLayout(windows: windows) ToastWindow.updateToastWindowsLayout(windows: windows)
//
updateTimeoutDuration()
func completion() { func completion() {
self.navEvents[.onViewDidAppear]?(self) self.navEvents[.onViewDidAppear]?(self)
self.updateTimeoutDuration()
} }
if isNew { if isNew {
window.transform = .init(translationX: 0, y: -window.frame.maxY) window.transform = .init(translationX: 0, y: -window.frame.maxY)
@ -126,9 +127,7 @@ extension ToastTarget {
vm?.duration = config.defaultDuration vm?.duration = config.defaultDuration
} }
// //
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in vm?.restartTimer()
self?.pop()
})
} }
} }
@ -190,3 +189,18 @@ extension ToastWindow {
updateToastWindowsLayout(windows: wins) updateToastWindowsLayout(windows: wins)
} }
} }
public class ToastManager: NSObject {
/// HUD
/// - Parameter identifier:
/// - Returns: HUD
@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) })
}
return arr
}
}

View File

@ -7,13 +7,24 @@
import UIKit import UIKit
open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> { public final class ToastProvider: HUDProviderType {
public typealias ViewModel = ToastViewModel public typealias ViewModel = ToastViewModel
public typealias Target = ToastTarget public typealias Target = ToastTarget
@discardableResult @objc public required init(initializer: ((_ toast: Target) -> Void)?) { /// Target
super.init(initializer: initializer) /// - Parameter initializer:
@discardableResult public required init(initializer: ((_ target: Target) -> Void)?) {
guard let initializer = initializer else {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
} }
/// ViewModelTarget /// ViewModelTarget
@ -21,10 +32,10 @@ open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> {
/// - vm: /// - vm:
/// - initializer: /// - initializer:
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ toast: Target) -> Void)?) { @discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ toast: Target) -> Void)?) {
if let id = vm.identifier, id.count > 0 { if let id = vm.identifier, id.count > 0, let target = ToastManager.find(identifier: id).last {
Self.lazyPush(identifier: id) { target in target.update { t in
target.vm = vm t.vm = vm
initializer?(target) initializer?(t)
} }
self.init(initializer: nil) self.init(initializer: nil)
} else { } else {
@ -47,34 +58,6 @@ open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> {
self.init(.message(text), initializer: nil) self.init(.message(text), initializer: nil)
} }
/// HUD
/// - Parameters:
/// - identifier:
/// - handler:
@objc public static func lazyPush(identifier: String? = nil, file: String = #file, line: Int = #line, handler: @escaping (_ toast: ToastTarget) -> Void, onExists: ((_ toast: ToastTarget) -> Void)? = nil) {
let id = identifier ?? (file + "#\(line)")
if let vc = find(identifier: id).last {
vc.update(handler: onExists ?? handler)
} else {
Self.init { toast in
toast.identifier = id
handler(toast)
}
}
}
/// HUD
/// - Parameter identifier:
/// - Returns: HUD
@discardableResult @objc 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) })
}
return arr
}
} }
public typealias Toast = ToastProvider public typealias Toast = ToastProvider

View File

@ -13,11 +13,7 @@ open class ToastTarget: BaseController, HUDTargetType {
weak var window: ToastWindow? weak var window: ToastWindow?
public lazy var config: ToastConfiguration = { public lazy var config = ToastConfiguration()
var cfg = ToastConfiguration()
ToastConfiguration.customGlobalConfig?(cfg)
return cfg
}()
public var progressView: ProgressView? public var progressView: ProgressView?
@ -149,7 +145,7 @@ fileprivate extension ToastTarget {
/// ///
/// - Parameter sender: /// - Parameter sender:
@objc func _onPanGesture(_ sender: UIPanGestureRecognizer) { @objc func _onPanGesture(_ sender: UIPanGestureRecognizer) {
vm?.timeoutTimer?.invalidate() vm?.cancelTimer()
let point = sender.translation(in: sender.view) let point = sender.translation(in: sender.view)
window?.transform = .init(translationX: 0, y: point.y) window?.transform = .init(translationX: 0, y: point.y)
if sender.state == .recognized { if sender.state == .recognized {
@ -164,8 +160,7 @@ fileprivate extension ToastTarget {
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) { UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.window?.transform = .identity self.window?.transform = .identity
} completion: { done in } completion: { done in
let d = self.vm?.duration self.vm?.restartTimer()
self.vm?.duration = d
} }
} }
} }