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,如果已经存在就更新") {
|
||||
func f(i: Int) {
|
||||
Alert.lazyPush(identifier: "haha") { alert in
|
||||
Alert(.identifier("haha")) { alert in
|
||||
if i < 2 {
|
||||
alert.vm = .loading.title("第\(i)次弹")
|
||||
let btn = alert.add(action: "请稍等", identifier: "btn")
|
||||
btn.isEnabled = false
|
||||
} else {
|
||||
alert.update(progress: 1)
|
||||
alert.vm = .success.title("第\(i)次弹").message("只更新内容")
|
||||
alert.reloadTextStack()
|
||||
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: "顶部,默认动画") {
|
||||
Capsule(.info("一条简短的消息").queuedPush(true).duration(1))
|
||||
Capsule(.info("一条简短的消息").duration(1))
|
||||
}
|
||||
section.add(title: "中间,默认动画") {
|
||||
Capsule(.middle.queuedPush(true).info("一条简短的消息").duration(2))
|
||||
Capsule(.middle.info("一条简短的消息").duration(1))
|
||||
}
|
||||
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.cardCornerRadius = 8
|
||||
capsule.config.contentViewMask { mask in
|
||||
|
@ -119,22 +119,25 @@ class DemoCapsuleVC: ListVC {
|
|||
}
|
||||
}
|
||||
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.cardEdgeInsets = .init(top: 12, left: 20, bottom: 12, right: 20)
|
||||
capsule.config.customTextLabel { label in
|
||||
label.textColor = .white
|
||||
label.font = .boldSystemFont(ofSize: 16)
|
||||
}
|
||||
capsule.config.contentViewMask { mask in
|
||||
capsule.config.contentViewMask { [weak capsule] mask in
|
||||
mask.effect = .none
|
||||
mask.backgroundColor = .clear
|
||||
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.startPoint = .init(x: 0.2, y: 0.6)
|
||||
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.insertSublayer(gradientLayer, at: 0)
|
||||
}
|
||||
|
@ -146,25 +149,155 @@ class DemoCapsuleVC: ListVC {
|
|||
}
|
||||
}
|
||||
}
|
||||
list.add(title: "lazy push") { section in
|
||||
section.add(title: "id:1, text:1") {
|
||||
Capsule(.test1("111:111"))
|
||||
list.add(title: "自定义子类,队列推送") { section in
|
||||
section.add(title: "视频采集,title:开") {
|
||||
GradientCapsule(.videoRecord(on: true, text: "视频采集:开"))
|
||||
}
|
||||
section.add(title: "id:1, text:2") {
|
||||
Capsule(.test1("111:222"))
|
||||
section.add(title: "视频推流,title:开") {
|
||||
GradientCapsule(.videoPush(on: true, text: "视频推流:开"))
|
||||
}
|
||||
section.add(title: "id:2, text:1") {
|
||||
Capsule(.test2("222:111"))
|
||||
section.add(title: "音频采集,title:开") {
|
||||
GradientCapsule(.audioRecord(on: true, text: "音频采集:开"))
|
||||
}
|
||||
section.add(title: "id:2, text:2") {
|
||||
Capsule(.test2("222:222"))
|
||||
section.add(title: "音频推流,title:开") {
|
||||
GradientCapsule(.audioPush(on: true, text: "音频推流:开"))
|
||||
}
|
||||
|
||||
section.add(title: "id:2, text:2") {
|
||||
Capsule(.test2("222:222"))
|
||||
Capsule(.test2("222:111"))
|
||||
section.add(title: "视频采集,title:关") {
|
||||
GradientCapsule(.videoRecord(on: false, text: "视频采集:关"))
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
static func test1(_ text: String) -> CapsuleViewModel {
|
||||
.identifier("id:1")
|
||||
.icon(.init(systemName: "video.circle.fill"))
|
||||
.tintColor(.systemGreen)
|
||||
.duration(1)
|
||||
static func videoRecord(on: Bool, text: String) -> CapsuleViewModel {
|
||||
.middle
|
||||
.identifier("video.record:\(on)")
|
||||
.icon(.init(systemName: on ? "video.fill" : "video.slash.fill"))
|
||||
.duration(0.5)
|
||||
.queuedPush(true)
|
||||
.message(text)
|
||||
}
|
||||
|
||||
static func test2(_ text: String) -> CapsuleViewModel {
|
||||
.identifier("id:2")
|
||||
.icon(.init(systemName: "mic.circle.fill"))
|
||||
.tintColor(.systemOrange)
|
||||
.duration(1)
|
||||
static func audioRecord(on: Bool, text: String) -> CapsuleViewModel {
|
||||
.middle
|
||||
.identifier("audio.record:\(on)")
|
||||
.icon(.init(systemName: on ? "mic.fill" : "mic.slash.fill"))
|
||||
.duration(0.5)
|
||||
.queuedPush(true)
|
||||
.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: "取消", style: .gray)
|
||||
sheet.onTappedBackground { sheet in
|
||||
Toast.lazyPush(identifier: "alert") { toast in
|
||||
toast.vm = .error
|
||||
Toast(
|
||||
.error
|
||||
.lazyIdentifier()
|
||||
.title("点击了背景")
|
||||
.message("点击背景将不会dismiss,必须在下方做出选择才能关掉")
|
||||
.duration(2)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,14 +34,61 @@ let isTesting: Bool = true
|
|||
|
||||
class TestToastTarget: ToastTarget {
|
||||
override func push() {
|
||||
print("isTesting: \(isTesting)")
|
||||
guard isTesting else { return }
|
||||
super.push()
|
||||
}
|
||||
}
|
||||
|
||||
//typealias TestToast = HUDProvider<ToastViewModel, TestToastTarget>
|
||||
class TestToast: ToastProvider {
|
||||
typealias Target = TestToastTarget
|
||||
class TestToast: HUDProviderType {
|
||||
|
||||
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 {
|
||||
|
@ -127,12 +174,12 @@ class DemoToastVC: ListVC {
|
|||
section.add(title: "增加按钮") {
|
||||
let title = "您收到了一条好友申请"
|
||||
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.imageView.layer.masksToBounds = true
|
||||
toast.imageView.layer.cornerRadius = toast.config.iconSize.width / 2
|
||||
toast.add(action: "拒绝", style: .destructive) { toast in
|
||||
Alert.lazyPush(identifier: "Dracarys") { alert in
|
||||
Alert(.identifier("Dracarys")) { alert in
|
||||
alert.vm = .message("Dracarys")
|
||||
.icon(UIImage(inProHUD: "prohud.windmill"))
|
||||
.rotation(.init(repeatCount: .infinity))
|
||||
|
@ -152,13 +199,13 @@ class DemoToastVC: ListVC {
|
|||
}
|
||||
}
|
||||
toast.add(action: "同意") { toast in
|
||||
Alert.find(identifier: "Dracarys", update: { alert in
|
||||
AlertManager.find(identifier: "Dracarys", update: { alert in
|
||||
alert.pop()
|
||||
})
|
||||
toast.pop()
|
||||
Alert(.success(1).message("Good choice!"))
|
||||
}
|
||||
Toast.find(identifier: "loading") { toast in
|
||||
ToastManager.find(identifier: "loading") { toast in
|
||||
toast.vm = .success(2).message("加载成功")
|
||||
}
|
||||
}
|
||||
|
@ -190,12 +237,12 @@ class DemoToastVC: ListVC {
|
|||
}
|
||||
section.add(title: "如果存在就更新,如果不存在就忽略") {
|
||||
i += 1
|
||||
Toast.find(identifier: "loading") { toast in
|
||||
ToastManager.find(identifier: "loading") { toast in
|
||||
toast.vm = .success.title("加载完成\(i)").message("这条消息不会重复显示多条").duration(2)
|
||||
}
|
||||
}
|
||||
section.add(title: "移除指定实例") {
|
||||
Toast.find(identifier: "loading") { toast in
|
||||
ToastManager.find(identifier: "loading") { toast in
|
||||
toast.pop()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,12 @@ public class AlertConfiguration: CommonConfiguration {
|
|||
|
||||
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: 配置代码
|
||||
|
|
|
@ -32,7 +32,7 @@ extension AlertTarget: InternalConvenienceLayout {
|
|||
self?.pop()
|
||||
}
|
||||
}
|
||||
if isViewDisplayed {
|
||||
if isViewAppeared {
|
||||
self.actionStack.layoutIfNeeded()
|
||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||
self.view.layoutIfNeeded()
|
||||
|
@ -78,7 +78,7 @@ extension AlertTarget: InternalConvenienceLayout {
|
|||
buttonEvents[view] = nil
|
||||
}
|
||||
}
|
||||
if isViewDisplayed {
|
||||
if isViewAppeared {
|
||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||
self.actionStack.layoutIfNeeded()
|
||||
self.view.layoutIfNeeded()
|
||||
|
|
|
@ -26,8 +26,10 @@ extension AlertTarget: DefaultLayout {
|
|||
} else {
|
||||
isFirstLayout = false
|
||||
}
|
||||
if isViewAppeared {
|
||||
// 更新时间
|
||||
updateTimeoutDuration()
|
||||
}
|
||||
// custom layout
|
||||
guard customView == nil else {
|
||||
return
|
||||
|
@ -116,7 +118,7 @@ extension AlertTarget: DefaultLayout {
|
|||
}
|
||||
|
||||
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
|
||||
guard count < 4 else { return }
|
||||
if (isPortrait && count < 3) || !isPortrait {
|
||||
|
|
|
@ -24,6 +24,8 @@ extension AlertTarget {
|
|||
view.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
// 为了更连贯,从进入动画开始时就开始计时
|
||||
updateTimeoutDuration()
|
||||
UIView.animateEaseOut(duration: config.animateDurationForBuildIn ?? config.animateDurationForBuildInByDefault) {
|
||||
self.view.transform = .identity
|
||||
self.view.alpha = 1
|
||||
|
@ -33,7 +35,6 @@ extension AlertTarget {
|
|||
window.backgroundView.alpha = 1
|
||||
} completion: { done in
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
self.updateTimeoutDuration()
|
||||
}
|
||||
window.alerts.append(self)
|
||||
AlertTarget.updateAlertsLayout(alerts: window.alerts)
|
||||
|
@ -76,9 +77,7 @@ extension AlertTarget {
|
|||
|
||||
func updateTimeoutDuration() {
|
||||
// 设置持续时间
|
||||
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||
self?.pop()
|
||||
})
|
||||
vm?.restartTimer()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
open class AlertProvider: HUDProvider<AlertViewModel, AlertTarget> {
|
||||
public final class AlertProvider: HUDProviderType {
|
||||
|
||||
public typealias ViewModel = AlertViewModel
|
||||
public typealias Target = AlertTarget
|
||||
|
||||
@discardableResult @objc public required init(initializer: ((_ alert: Target) -> Void)?) {
|
||||
super.init(initializer: initializer)
|
||||
/// 根据自定义的初始化代码创建一个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并显示
|
||||
|
@ -21,10 +32,10 @@ open class AlertProvider: HUDProvider<AlertViewModel, AlertTarget> {
|
|||
/// - vm: 数据模型
|
||||
/// - initializer: 自定义的初始化代码
|
||||
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ alert: Target) -> Void)?) {
|
||||
if let id = vm.identifier, id.count > 0 {
|
||||
Self.lazyPush(identifier: id) { target in
|
||||
target.vm = vm
|
||||
initializer?(target)
|
||||
if let id = vm.identifier, id.count > 0, let target = AlertManager.find(identifier: id).last {
|
||||
target.update { t in
|
||||
t.vm = vm
|
||||
initializer?(t)
|
||||
}
|
||||
self.init(initializer: nil)
|
||||
} else {
|
||||
|
@ -34,39 +45,13 @@ open class AlertProvider: HUDProvider<AlertViewModel, AlertTarget> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据ViewModel创建一个Target并显示
|
||||
/// - Parameter vm: 数据模型
|
||||
@discardableResult public convenience init(_ vm: ViewModel) {
|
||||
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
|
||||
|
|
|
@ -11,11 +11,7 @@ open class AlertTarget: BaseController, HUDTargetType {
|
|||
|
||||
public typealias ViewModel = AlertViewModel
|
||||
|
||||
public lazy var config: AlertConfiguration = {
|
||||
var cfg = AlertConfiguration()
|
||||
AlertConfiguration.customGlobalConfig?(cfg)
|
||||
return cfg
|
||||
}()
|
||||
public lazy var config = AlertConfiguration()
|
||||
|
||||
public var progressView: ProgressView?
|
||||
|
||||
|
|
|
@ -11,7 +11,12 @@ public class CapsuleConfiguration: CommonConfiguration {
|
|||
|
||||
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: 配置代码
|
||||
|
|
|
@ -43,10 +43,9 @@ extension CapsuleTarget: DefaultLayout {
|
|||
|
||||
view.layoutIfNeeded()
|
||||
|
||||
if isViewAppeared {
|
||||
// 更新时间
|
||||
updateTimeoutDuration()
|
||||
|
||||
if isViewDisplayed {
|
||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
|
@ -102,4 +101,5 @@ extension CapsuleTarget: DefaultLayout {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ extension CapsuleTarget {
|
|||
// 直接覆盖
|
||||
isNew = false
|
||||
window = w
|
||||
window.capsule = self
|
||||
}
|
||||
} else {
|
||||
// 空闲状态下推送一个新的
|
||||
|
@ -42,7 +43,7 @@ extension CapsuleTarget {
|
|||
window = CapsuleWindow(capsule: self)
|
||||
windows[position] = nil
|
||||
}
|
||||
|
||||
window.isUserInteractionEnabled = tapActionCallback != nil
|
||||
// frame
|
||||
let cardEdgeInsetsByDefault = config.cardEdgeInsetsByDefault
|
||||
view.layoutIfNeeded()
|
||||
|
@ -83,10 +84,10 @@ extension CapsuleTarget {
|
|||
// 更新toast防止重叠
|
||||
ToastWindow.updateToastWindowsLayout()
|
||||
}
|
||||
|
||||
// 为了更连贯,从进入动画开始时就开始计时
|
||||
updateTimeoutDuration()
|
||||
func completion() {
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
self.updateTimeoutDuration()
|
||||
}
|
||||
if isNew {
|
||||
window.isHidden = false
|
||||
|
@ -149,10 +150,10 @@ extension CapsuleTarget {
|
|||
window.transform = .identity
|
||||
self.navEvents[.onViewDidDisappear]?(self)
|
||||
}
|
||||
var duration = config.animateDurationForBuildOutByDefault
|
||||
if let animateBuildOut = config.animateBuildOut {
|
||||
animateBuildOut(window, completion)
|
||||
} else {
|
||||
let duration = config.animateDurationForBuildOutByDefault
|
||||
let oldFrame = window.frame
|
||||
switch position {
|
||||
case .top:
|
||||
|
@ -162,13 +163,13 @@ extension CapsuleTarget {
|
|||
completion()
|
||||
}
|
||||
case .middle:
|
||||
let duration = config.animateDurationForBuildInByDefault * 1
|
||||
duration = config.animateDurationForBuildInByDefault
|
||||
UIView.animateEaseIn(duration: duration) {
|
||||
window.transform = .init(translationX: 0, y: -24)
|
||||
} completion: { done in
|
||||
completion()
|
||||
}
|
||||
UIView.animateLinear(duration: duration * 0.5, delay: duration * 0.3) {
|
||||
UIView.animateEaseIn(duration: duration * 0.5, delay: duration * 0.5) {
|
||||
window.alpha = 0
|
||||
}
|
||||
case .bottom:
|
||||
|
@ -182,7 +183,8 @@ extension CapsuleTarget {
|
|||
}
|
||||
if let next = AppContext.capsuleInQueue.first(where: { $0.preferredWindowScene == windowScene && $0.vm?.position == position }) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -205,9 +207,24 @@ extension CapsuleTarget {
|
|||
vm?.duration = config.defaultDuration
|
||||
}
|
||||
// 设置持续时间
|
||||
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||
self?.pop()
|
||||
})
|
||||
vm?.restartTimer()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
open class CapsuleProvider: HUDProvider<CapsuleViewModel, CapsuleTarget> {
|
||||
public final class CapsuleProvider: HUDProviderType {
|
||||
|
||||
public typealias ViewModel = CapsuleViewModel
|
||||
public typealias Target = CapsuleTarget
|
||||
|
||||
@discardableResult @objc public required init(initializer: ((_ capsule: Target) -> Void)?) {
|
||||
super.init(initializer: initializer)
|
||||
/// 根据自定义的初始化代码创建一个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并显示
|
||||
|
@ -21,10 +32,10 @@ open class CapsuleProvider: HUDProvider<CapsuleViewModel, CapsuleTarget> {
|
|||
/// - vm: 数据模型
|
||||
/// - initializer: 初始化代码
|
||||
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ capsule: Target) -> Void)?) {
|
||||
if let id = vm.identifier, id.count > 0 {
|
||||
Self.lazyPush(identifier: id) { target in
|
||||
target.vm = vm
|
||||
initializer?(target)
|
||||
if let id = vm.identifier, id.count > 0, let target = CapsuleManager.find(identifier: id).last {
|
||||
target.update { t in
|
||||
t.vm = vm
|
||||
initializer?(t)
|
||||
}
|
||||
self.init(initializer: nil)
|
||||
} else {
|
||||
|
@ -47,36 +58,6 @@ open class CapsuleProvider: HUDProvider<CapsuleViewModel, CapsuleTarget> {
|
|||
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
|
||||
|
|
|
@ -11,11 +11,7 @@ open class CapsuleTarget: BaseController, HUDTargetType {
|
|||
|
||||
public typealias ViewModel = CapsuleViewModel
|
||||
|
||||
@objc public lazy var config: CapsuleConfiguration = {
|
||||
var cfg = CapsuleConfiguration()
|
||||
CapsuleConfiguration.customGlobalConfig?(cfg)
|
||||
return cfg
|
||||
}()
|
||||
@objc open lazy var config = CapsuleConfiguration()
|
||||
|
||||
/// 内容容器(imageView、textLabel)
|
||||
public lazy var contentStack: StackView = {
|
||||
|
@ -49,6 +45,9 @@ open class CapsuleTarget: BaseController, HUDTargetType {
|
|||
}()
|
||||
|
||||
@objc public var vm: CapsuleViewModel? {
|
||||
willSet {
|
||||
vm?.cancelTimer()
|
||||
}
|
||||
didSet {
|
||||
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() {
|
||||
super.init()
|
||||
|
@ -79,7 +78,7 @@ open class CapsuleTarget: BaseController, HUDTargetType {
|
|||
self.vm = vm
|
||||
}
|
||||
|
||||
public override func viewDidLoad() {
|
||||
open override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.layer.shadowRadius = 8
|
||||
view.layer.shadowOffset = .init(width: 0, height: 5)
|
||||
|
|
|
@ -14,6 +14,7 @@ class CapsuleWindow: Window {
|
|||
init(capsule: CapsuleTarget) {
|
||||
self.capsule = capsule
|
||||
super.init(frame: .zero)
|
||||
isUserInteractionEnabled = false
|
||||
windowScene = AppContext.windowScene
|
||||
switch capsule.vm?.position {
|
||||
case .top, .none:
|
||||
|
|
|
@ -25,7 +25,7 @@ open class BaseController: UIViewController {
|
|||
|
||||
open var customView: UIView?
|
||||
|
||||
public internal(set) var isViewDisplayed = false
|
||||
public internal(set) var isViewAppeared = false
|
||||
|
||||
/// 按钮事件
|
||||
var buttonEvents = [UIView: () -> Void]()
|
||||
|
@ -43,7 +43,7 @@ open class BaseController: UIViewController {
|
|||
consolePrint("👌", self, "deinit")
|
||||
}
|
||||
|
||||
override public func viewDidLoad() {
|
||||
override open func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
|
@ -89,7 +89,7 @@ open class BaseController: UIViewController {
|
|||
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
isViewDisplayed = true
|
||||
isViewAppeared = true
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -48,11 +48,7 @@ open class BaseViewModel: NSObject, HUDViewModelType {
|
|||
@objc open var tintColor: UIColor?
|
||||
|
||||
/// 持续时间(为空代表根据场景不同的默认配置,为0代表无穷大)
|
||||
open var duration: TimeInterval? {
|
||||
didSet {
|
||||
resetTimeoutHandler()
|
||||
}
|
||||
}
|
||||
open var duration: TimeInterval?
|
||||
|
||||
weak var vc: BaseController? {
|
||||
didSet {
|
||||
|
@ -72,26 +68,33 @@ open class BaseViewModel: NSObject, HUDViewModelType {
|
|||
self.duration = duration
|
||||
}
|
||||
|
||||
/// 超时处理
|
||||
var timeoutHandler: DispatchWorkItem? {
|
||||
didSet {
|
||||
resetTimeoutHandler()
|
||||
}
|
||||
}
|
||||
private var timeoutTimer: Timer?
|
||||
|
||||
var timeoutTimer: Timer?
|
||||
|
||||
func resetTimeoutHandler() {
|
||||
timeoutTimer?.invalidate()
|
||||
timeoutTimer = nil
|
||||
if let t = duration, t > 0 {
|
||||
let timer = Timer(timeInterval: t, repeats: false, block: { [weak self] t in
|
||||
self?.timeoutHandler?.perform()
|
||||
func restartTimer() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancelTimer() {
|
||||
timeoutTimer?.invalidate()
|
||||
timeoutTimer = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -103,6 +106,11 @@ public extension BaseViewModel {
|
|||
return self
|
||||
}
|
||||
|
||||
func lazyIdentifier(file: String = #file, line: Int = #line) -> Self {
|
||||
self.identifier = (file + "#\(line)")
|
||||
return self
|
||||
}
|
||||
|
||||
func icon(_ image: UIImage?) -> Self {
|
||||
self.icon = image
|
||||
return self
|
||||
|
@ -143,11 +151,18 @@ public extension BaseViewModel {
|
|||
// MARK: - example scenes
|
||||
public extension BaseViewModel {
|
||||
|
||||
/// 设置指定的唯一标识符
|
||||
/// - Parameter text: 唯一标识符
|
||||
static func identifier(_ text: String?) -> Self {
|
||||
.init()
|
||||
.identifier(text)
|
||||
}
|
||||
|
||||
/// 以当前代码位置作为唯一标识符
|
||||
static func codeIdentifier(file: String = #file, line: Int = #line) -> Self {
|
||||
identifier((file + "#\(line)"))
|
||||
}
|
||||
|
||||
// MARK: plain
|
||||
static func title(_ text: String?) -> Self {
|
||||
.init()
|
||||
|
|
|
@ -188,7 +188,7 @@ open class CommonConfiguration: NSObject {
|
|||
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 {
|
||||
associatedtype ViewModel = HUDViewModelType
|
||||
var identifier: String { get set }
|
||||
var vm: ViewModel? { get set }
|
||||
init()
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ extension LoadingAnimation {
|
|||
/// 更新进度(如果需要显示进度,需要先调用一次 updateProgress(0) 来初始化)
|
||||
/// - Parameter progress: 进度(0~1)
|
||||
public func update(progress: CGFloat) {
|
||||
guard isViewDisplayed else { return }
|
||||
guard isViewAppeared else { return }
|
||||
guard let superview = imageView.superview else { return }
|
||||
if progressView == nil {
|
||||
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) {
|
||||
animate(withDuration: duration, delay: delay, options: [.allowUserInteraction], animations: animations, completion: completion)
|
||||
}
|
||||
static func animateEaseIn(duration: TimeInterval, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) {
|
||||
animate(withDuration: duration, delay: 0, options: [.allowUserInteraction, .curveEaseIn], animations: animations, completion: completion)
|
||||
static func animateEaseIn(duration: TimeInterval, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
static var customGlobalConfig: ((_ config: SheetConfiguration) -> Void)?
|
||||
private static var customGlobalConfig: ((_ config: SheetConfiguration) -> Void)?
|
||||
|
||||
public override init() {
|
||||
super.init()
|
||||
Self.customGlobalConfig?(self)
|
||||
}
|
||||
|
||||
/// 全局共享配置(只能设置一次,影响所有实例)
|
||||
/// - Parameter callback: 配置代码
|
||||
|
|
|
@ -28,7 +28,7 @@ extension SheetTarget: ConvenienceLayout {
|
|||
self?.pop()
|
||||
}
|
||||
}
|
||||
if isViewDisplayed {
|
||||
if isViewAppeared {
|
||||
self.contentStack.layoutIfNeeded()
|
||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||
self.view.layoutIfNeeded()
|
||||
|
@ -74,7 +74,7 @@ extension SheetTarget: ConvenienceLayout {
|
|||
buttonEvents[view] = nil
|
||||
}
|
||||
}
|
||||
if isViewDisplayed {
|
||||
if isViewAppeared {
|
||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||
self.contentStack.layoutIfNeeded()
|
||||
self.view.layoutIfNeeded()
|
||||
|
|
|
@ -29,7 +29,7 @@ extension SheetTarget: DefaultLayout {
|
|||
// content
|
||||
loadContentViewIfNeeded()
|
||||
|
||||
if isViewDisplayed {
|
||||
if isViewAppeared {
|
||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
|
|
|
@ -17,11 +17,13 @@ extension SheetTarget {
|
|||
if let w = windows.first(where: { $0.sheet == self }) {
|
||||
isNew = false
|
||||
window = w
|
||||
window.sheet = self
|
||||
} else {
|
||||
window = SheetWindow(sheet: self)
|
||||
isNew = true
|
||||
}
|
||||
window.rootViewController = self
|
||||
|
||||
if windows.contains(window) == false {
|
||||
windows.append(window)
|
||||
setContextWindows(windows)
|
||||
|
|
|
@ -7,20 +7,31 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
open class SheetProvider: HUDProvider<SheetViewModel, SheetTarget> {
|
||||
public final class SheetProvider: HUDProviderType {
|
||||
|
||||
public typealias ViewModel = SheetViewModel
|
||||
public typealias Target = SheetTarget
|
||||
|
||||
@discardableResult @objc public required init(initializer: ((_ sheet: Target) -> Void)?) {
|
||||
super.init(initializer: initializer)
|
||||
/// 根据自定义的初始化代码创建一个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()
|
||||
}
|
||||
|
||||
/// 如果不存在就创建并弹出一个HUD实例,如果存在就更新实例
|
||||
/// - Parameters:
|
||||
/// - identifier: 实例唯一标识符(如果为空,则以代码位置为唯一标识符)
|
||||
/// - 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)")
|
||||
if let vc = find(identifier: id).last {
|
||||
vc.update(handler: onExists ?? handler)
|
||||
|
@ -35,7 +46,7 @@ open class SheetProvider: HUDProvider<SheetViewModel, SheetTarget> {
|
|||
/// 查找HUD实例
|
||||
/// - Parameter identifier: 唯一标识符
|
||||
/// - 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 })
|
||||
if let handler = handler {
|
||||
arr.forEach({ $0.update(handler: handler) })
|
||||
|
|
|
@ -11,11 +11,7 @@ open class SheetTarget: BaseController, HUDTargetType {
|
|||
|
||||
weak var window: SheetWindow?
|
||||
|
||||
public lazy var config: SheetConfiguration = {
|
||||
var cfg = SheetConfiguration()
|
||||
SheetConfiguration.customGlobalConfig?(cfg)
|
||||
return cfg
|
||||
}()
|
||||
public lazy var config = SheetConfiguration()
|
||||
|
||||
public lazy var backgroundView: UIView = {
|
||||
let v = UIView()
|
||||
|
|
|
@ -9,7 +9,12 @@ import UIKit
|
|||
|
||||
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: 配置代码
|
||||
|
|
|
@ -51,7 +51,7 @@ extension ToastTarget: ConvenienceLayout {
|
|||
self?.pop()
|
||||
}
|
||||
}
|
||||
if isViewDisplayed {
|
||||
if isViewAppeared {
|
||||
self.actionStack.layoutIfNeeded()
|
||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||
self.view.layoutIfNeeded()
|
||||
|
@ -99,7 +99,7 @@ extension ToastTarget: ConvenienceLayout {
|
|||
buttonEvents[view] = nil
|
||||
}
|
||||
}
|
||||
if isViewDisplayed {
|
||||
if isViewAppeared {
|
||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||
self.contentStack.layoutIfNeeded()
|
||||
self.view.layoutIfNeeded()
|
||||
|
|
|
@ -87,8 +87,10 @@ extension ToastTarget: DefaultLayout {
|
|||
bodyLabel.text = vm?.message
|
||||
view.layoutIfNeeded()
|
||||
|
||||
// 设置持续时间
|
||||
if isViewAppeared {
|
||||
// 更新时间
|
||||
updateTimeoutDuration()
|
||||
}
|
||||
|
||||
setupImageView()
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ extension ToastTarget {
|
|||
if let w = windows.first(where: { $0.toast == self }) {
|
||||
isNew = false
|
||||
window = w
|
||||
window.toast = self
|
||||
} else {
|
||||
window = ToastWindow(toast: self)
|
||||
isNew = true
|
||||
|
@ -66,10 +67,10 @@ extension ToastTarget {
|
|||
setContextWindows(windows)
|
||||
}
|
||||
ToastWindow.updateToastWindowsLayout(windows: windows)
|
||||
|
||||
// 为了更连贯,从进入动画开始时就开始计时
|
||||
updateTimeoutDuration()
|
||||
func completion() {
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
self.updateTimeoutDuration()
|
||||
}
|
||||
if isNew {
|
||||
window.transform = .init(translationX: 0, y: -window.frame.maxY)
|
||||
|
@ -126,9 +127,7 @@ extension ToastTarget {
|
|||
vm?.duration = config.defaultDuration
|
||||
}
|
||||
// 设置持续时间
|
||||
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||
self?.pop()
|
||||
})
|
||||
vm?.restartTimer()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -190,3 +189,18 @@ extension ToastWindow {
|
|||
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
|
||||
|
||||
open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> {
|
||||
public final class ToastProvider: HUDProviderType {
|
||||
|
||||
public typealias ViewModel = ToastViewModel
|
||||
public typealias Target = ToastTarget
|
||||
|
||||
@discardableResult @objc public required init(initializer: ((_ toast: Target) -> Void)?) {
|
||||
super.init(initializer: initializer)
|
||||
/// 根据自定义的初始化代码创建一个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并显示
|
||||
|
@ -21,10 +32,10 @@ open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> {
|
|||
/// - vm: 数据模型
|
||||
/// - initializer: 自定义的初始化代码
|
||||
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ toast: Target) -> Void)?) {
|
||||
if let id = vm.identifier, id.count > 0 {
|
||||
Self.lazyPush(identifier: id) { target in
|
||||
target.vm = vm
|
||||
initializer?(target)
|
||||
if let id = vm.identifier, id.count > 0, let target = ToastManager.find(identifier: id).last {
|
||||
target.update { t in
|
||||
t.vm = vm
|
||||
initializer?(t)
|
||||
}
|
||||
self.init(initializer: nil)
|
||||
} else {
|
||||
|
@ -47,34 +58,6 @@ open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> {
|
|||
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
|
||||
|
|
|
@ -13,11 +13,7 @@ open class ToastTarget: BaseController, HUDTargetType {
|
|||
|
||||
weak var window: ToastWindow?
|
||||
|
||||
public lazy var config: ToastConfiguration = {
|
||||
var cfg = ToastConfiguration()
|
||||
ToastConfiguration.customGlobalConfig?(cfg)
|
||||
return cfg
|
||||
}()
|
||||
public lazy var config = ToastConfiguration()
|
||||
|
||||
public var progressView: ProgressView?
|
||||
|
||||
|
@ -149,7 +145,7 @@ fileprivate extension ToastTarget {
|
|||
/// 拖拽事件
|
||||
/// - Parameter sender: 手势
|
||||
@objc func _onPanGesture(_ sender: UIPanGestureRecognizer) {
|
||||
vm?.timeoutTimer?.invalidate()
|
||||
vm?.cancelTimer()
|
||||
let point = sender.translation(in: sender.view)
|
||||
window?.transform = .init(translationX: 0, y: point.y)
|
||||
if sender.state == .recognized {
|
||||
|
@ -164,8 +160,7 @@ fileprivate extension ToastTarget {
|
|||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||
self.window?.transform = .identity
|
||||
} completion: { done in
|
||||
let d = self.vm?.duration
|
||||
self.vm?.duration = d
|
||||
self.vm?.restartTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue