代码优化

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

View File

@ -311,13 +311,12 @@ class DemoAlertVC: ListVC {
}
section.add(title: "弹出loading如果已经存在就更新") {
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")

View File

@ -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,27 +149,157 @@ 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 {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
}
/// ViewModelTarget
/// - Parameters:
/// - vm:
/// - initializer:
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ capsule: Target) -> Void)?) {
if let id = vm.identifier, id.count > 0, let target = CapsuleManager.find(identifier: id).last as? Target {
target.update { capsule in
capsule.vm = vm
initializer?(capsule as! GradientCapsule.Target)
}
self.init(initializer: nil)
} else {
self.init { target in
target.vm = vm
initializer?(target)
}
}
}
/// ViewModelTarget
/// - Parameter vm:
@discardableResult public convenience init(_ vm: ViewModel) {
self.init(vm, initializer: nil)
}
/// Target
/// - Parameter text:
@discardableResult public convenience init(_ text: String) {
self.init(.message(text), initializer: nil)
}
}
extension CapsuleViewModel {
@ -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)
}
}

View File

@ -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
.title("点击了背景")
.message("点击背景将不会dismiss必须在下方做出选择才能关掉")
.duration(2)
}
Toast(
.error
.lazyIdentifier()
.title("点击了背景")
.message("点击背景将不会dismiss必须在下方做出选择才能关掉")
.duration(2)
)
}
}
}

View File

@ -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 {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
}
/// ViewModelTarget
/// - Parameters:
/// - vm:
/// - initializer:
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ toast: Target) -> Void)?) {
if let id = vm.identifier, id.count > 0, let target = ToastManager.find(identifier: id).last as? Target {
target.vm = vm
initializer?(target)
self.init(initializer: nil)
} else {
self.init { target in
target.vm = vm
initializer?(target)
}
}
}
/// ViewModelTarget
/// - Parameter vm:
@discardableResult public convenience init(_ vm: ViewModel) {
self.init(vm, initializer: nil)
}
/// Target
/// - Parameter text:
@discardableResult @objc public convenience init(_ text: String) {
self.init(.message(text), initializer: nil)
}
}
class DemoToastVC: ListVC {
@ -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()
}
}

View File

@ -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:

View File

@ -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()

View File

@ -26,8 +26,10 @@ extension AlertTarget: DefaultLayout {
} else {
isFirstLayout = false
}
//
updateTimeoutDuration()
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 {

View File

@ -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
}
}

View File

@ -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 {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
}
/// ViewModelTarget
@ -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> {
}
}
}
/// ViewModelTarget
/// - 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

View File

@ -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?

View File

@ -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:

View File

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

View File

@ -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) {
// poppush
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
}
}

View File

@ -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 {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
}
/// ViewModelTarget
@ -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

View File

@ -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()
/// imageViewtextLabel)
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)

View File

@ -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:

View File

@ -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
}
}

View File

@ -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,25 +68,32 @@ open class BaseViewModel: NSObject, HUDViewModelType {
self.duration = duration
}
///
var timeoutHandler: DispatchWorkItem? {
didSet {
resetTimeoutHandler()
private var timeoutTimer: Timer?
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()
}
}
}
}
}
var timeoutTimer: Timer?
func resetTimeoutHandler() {
func cancelTimer() {
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()
})
RunLoop.main.add(timer, forMode: .common)
timeoutTimer = timer
}
}
}
@ -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()

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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()

View File

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

View File

@ -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)

View File

@ -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 {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// 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) })

View File

@ -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()

View File

@ -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:

View File

@ -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()

View File

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

View File

@ -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
}
}

View File

@ -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 {
// Providerpushtarget
// targetProvider
// lazyPushProvider
// self.init(initializer: nil)
return
}
let t = Target()
initializer(t)
t.push()
}
/// ViewModelTarget
@ -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

View File

@ -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()
}
}
}