mirror of https://github.com/xaoxuu/ProHUD
代码优化
This commit is contained in:
parent
69afc5dd6a
commit
cbd654d80b
|
@ -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")
|
||||||
|
|
|
@ -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 {
|
||||||
|
// Provider的作用就是push一个target
|
||||||
|
// 如果没有任何初始化代码就没有target,就是个无意义的Provider
|
||||||
|
// 但为了支持lazyPush(找到已有实例并更新),所以就需要支持无意义的Provider
|
||||||
|
// 详见子类中的 self.init(initializer: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let t = Target()
|
||||||
|
initializer(t)
|
||||||
|
t.push()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
||||||
|
/// - 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据ViewModel创建一个Target并显示
|
||||||
|
/// - 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
// Provider的作用就是push一个target
|
||||||
|
// 如果没有任何初始化代码就没有target,就是个无意义的Provider
|
||||||
|
// 但为了支持lazyPush(找到已有实例并更新),所以就需要支持无意义的Provider
|
||||||
|
// 详见子类中的 self.init(initializer: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let t = Target()
|
||||||
|
initializer(t)
|
||||||
|
t.push()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
||||||
|
/// - 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据ViewModel创建一个Target并显示
|
||||||
|
/// - 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: 配置代码
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
// Provider的作用就是push一个target
|
||||||
|
// 如果没有任何初始化代码就没有target,就是个无意义的Provider
|
||||||
|
// 但为了支持lazyPush(找到已有实例并更新),所以就需要支持无意义的Provider
|
||||||
|
// 详见子类中的 self.init(initializer: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let t = Target()
|
||||||
|
initializer(t)
|
||||||
|
t.push()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
||||||
|
@ -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> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据ViewModel创建一个Target并显示
|
/// 根据ViewModel创建一个Target并显示
|
||||||
/// - 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
|
||||||
|
|
|
@ -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?
|
||||||
|
|
||||||
|
|
|
@ -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: 配置代码
|
||||||
|
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
// 在这个pop的同时push下一个
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
// Provider的作用就是push一个target
|
||||||
|
// 如果没有任何初始化代码就没有target,就是个无意义的Provider
|
||||||
|
// 但为了支持lazyPush(找到已有实例并更新),所以就需要支持无意义的Provider
|
||||||
|
// 详见子类中的 self.init(initializer: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let t = Target()
|
||||||
|
initializer(t)
|
||||||
|
t.push()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}()
|
|
||||||
|
|
||||||
/// 内容容器(imageView、textLabel)
|
/// 内容容器(imageView、textLabel)
|
||||||
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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -188,7 +188,7 @@ open class CommonConfiguration: NSObject {
|
||||||
customContentViewMask = callback
|
customContentViewMask = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
override init() {
|
public override init() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
|
||||||
// Provider的作用就是push一个target
|
|
||||||
// 如果没有任何初始化代码就没有target,就是个无意义的Provider
|
|
||||||
// 但为了支持lazyPush(找到已有实例并更新),所以就需要支持无意义的Provider
|
|
||||||
// 详见子类中的 self.init(initializer: nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var t = Target()
|
|
||||||
initializer(t)
|
|
||||||
t.push()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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: 配置代码
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
// Provider的作用就是push一个target
|
||||||
|
// 如果没有任何初始化代码就没有target,就是个无意义的Provider
|
||||||
|
// 但为了支持lazyPush(找到已有实例并更新),所以就需要支持无意义的Provider
|
||||||
|
// 详见子类中的 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) })
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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: 配置代码
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
// Provider的作用就是push一个target
|
||||||
|
// 如果没有任何初始化代码就没有target,就是个无意义的Provider
|
||||||
|
// 但为了支持lazyPush(找到已有实例并更新),所以就需要支持无意义的Provider
|
||||||
|
// 详见子类中的 self.init(initializer: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let t = Target()
|
||||||
|
initializer(t)
|
||||||
|
t.push()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue