代码重构

This commit is contained in:
xaoxuu 2023-08-19 14:10:59 +08:00
parent 32f9af1ae6
commit 0574844a7f
24 changed files with 272 additions and 173 deletions

View File

@ -29,14 +29,15 @@ class AlertVC: ListVC {
list.add(title: "纯文字") { section in
section.add(title: "只有一句话") {
Alert(.message("只有一句话").duration(2))
// Alert(.message("").duration(2))
//
Alert("只有一句话")
}
section.add(title: "标题 + 正文") {
let title = "这是标题"
let message = "这是正文文字支持自动换行可设置最小宽度和最大宽度。这个弹窗将会持续4秒。"
Alert { alert in
alert.vm = .title(title).message(message)
alert.vm.duration = 4
alert.vm = .title(title).message(message).duration(4)
}
}
}
@ -62,9 +63,9 @@ class AlertVC: ListVC {
}
section.add(title: "图标 + 标题 + 正文") {
Alert(.error) { alert in
alert.vm.title = "加载失败"
alert.vm.message = "请稍后重试"
alert.vm.duration = 3
alert.vm?.title = "加载失败"
alert.vm?.message = "请稍后重试"
alert.vm?.duration = 3
}
}
}
@ -81,7 +82,7 @@ class AlertVC: ListVC {
alert.config.customButton { button in
button.titleLabel?.font = .systemFont(ofSize: 15)
}
alert.vm.title = "你正在使用移动网络观看"
alert.title = "你正在使用移动网络观看"
alert.onViewDidLoad { vc in
guard let alert = vc as? AlertTarget else {
return
@ -111,7 +112,7 @@ class AlertVC: ListVC {
alert.config.customButton { button in
button.titleLabel?.font = .systemFont(ofSize: 15)
}
alert.vm.message = "为了维护社区氛围,上麦用户需进行主播认证"
alert.vm?.message = "为了维护社区氛围,上麦用户需进行主播认证"
alert.onViewDidLoad { vc in
guard let alert = vc as? AlertTarget else {
return
@ -141,7 +142,7 @@ class AlertVC: ListVC {
alert.config.customButton { button in
button.titleLabel?.font = .systemFont(ofSize: 15)
}
alert.vm.message = "本次消费需要你支付999软妹豆确认支付吗"
alert.vm?.message = "本次消费需要你支付999软妹豆确认支付吗"
alert.config.customActionStack { stack in
stack.spacing = 0
stack.axis = .vertical //
@ -187,15 +188,15 @@ class AlertVC: ListVC {
section.add(title: "只有一段文字 + 按钮") {
Alert { alert in
alert.vm.title = "只有一段文字"
alert.title = "只有一段文字"
alert.add(action: "取消", style: .gray)
alert.add(action: "默认按钮")
}
}
section.add(title: "标题 + 正文 + 按钮") {
Alert { alert in
alert.vm.title = "标题"
alert.vm.message = "这是一段正文,长度超出最大宽度时会自动换行"
alert.vm?.title = "标题"
alert.vm?.message = "这是一段正文,长度超出最大宽度时会自动换行"
alert.add(action: "取消", style: .gray)
alert.add(action: "删除", style: .destructive) { alert in
// pop
@ -229,8 +230,8 @@ class AlertVC: ListVC {
}
section.add(title: "确认删除") {
Alert(.delete) { alert in
alert.vm.title = "确认删除"
alert.vm.message = "此操作无法撤销"
alert.vm?.title = "确认删除"
alert.vm?.message = "此操作无法撤销"
alert.add(action: "取消", style: .gray)
alert.add(action: "删除", style: .destructive)
}
@ -239,7 +240,7 @@ class AlertVC: ListVC {
list.add(title: "控件管理") { section in
section.add(title: "按钮增删改查") {
Alert(.note) { alert in
alert.vm.message = "可以动态增加、删除按钮"
alert.vm?.message = "可以动态增加、删除按钮"
alert.add(action: "在底部增加按钮", style: .filled(color: .systemGreen)) { alert in
alert.add(action: "哈哈1", identifier: "haha1")
}
@ -265,36 +266,34 @@ class AlertVC: ListVC {
}
}
section.add(title: "更新文字") {
Alert(.note) { alert in
alert.vm.message = "可以动态增加、删除、更新文字"
Alert(.note.message("可以动态增加、删除、更新文字")) { alert in
alert.add(action: "增加标题") { alert in
alert.vm.title = "这是标题"
alert.vm?.title = "这是标题"
alert.reloadTextStack()
}
alert.add(action: "增加正文") { alert in
alert.vm.message = "可以动态增加、删除、更新文字"
alert.vm?.message = "可以动态增加、删除、更新文字"
alert.reloadTextStack()
}
alert.add(action: "删除标题", style: .destructive) { alert in
alert.vm.title = nil
alert.vm?.title = nil
alert.reloadTextStack()
}
alert.add(action: "删除正文", style: .destructive) { alert in
alert.vm.message = nil
alert.vm?.message = nil
alert.reloadTextStack()
}
alert.add(action: "取消", style: .gray)
}
}
section.add(title: "在弹出过程中增加元素") {
Alert(.loading) { alert in
alert.vm.title = "在弹出过程中增加元素"
Alert(.loading.title("在弹出过程中增加元素")) { alert in
alert.add(action: "OK", style: .gray)
alert.onViewWillAppear { vc in
guard let alert = vc as? AlertTarget else {
return
}
alert.vm.message = "这是一段后增加的文字\n动画效果会有细微差别"
alert.vm?.message = "这是一段后增加的文字\n动画效果会有细微差别"
alert.reloadTextStack()
}
}
@ -304,8 +303,8 @@ class AlertVC: ListVC {
section.add(title: "多层级弹窗") {
func f(i: Int) {
Alert { alert in
alert.vm.title = "\(i)次弹"
alert.vm.message = "每次都是一个新的实例覆盖在上一个弹窗上面,而背景不会叠加变深。"
alert.vm?.title = "\(i)次弹"
alert.vm?.message = "每次都是一个新的实例覆盖在上一个弹窗上面,而背景不会叠加变深。"
alert.add(action: "取消", style: .gray)
alert.add(action: "增加一个") { alert in
f(i: i + 1)
@ -357,7 +356,7 @@ class AlertVC: ListVC {
list.add(title: "自定义视图") { section in
section.add(title: "自定义控件") {
Alert { alert in
alert.vm.title = "自定义控件"
alert.title = "自定义控件"
//
let imgv = UIImageView(image: UIImage(named: "landscape"))
imgv.contentMode = .scaleAspectFill

View File

@ -17,12 +17,15 @@ class CapsuleVC: ListVC {
header.detailLabel.text = "状态胶囊控件,用于状态显示,一个主程序窗口每个位置(上中下)各自最多只有一个状态胶囊实例。"
CapsuleConfiguration.global { config in
config.defaultDuration = 3 //
// config.cardCornerRadius = .infinity //
}
list.add(title: "默认布局:纯文字") { section in
section.add(title: "一条简短的消息") {
// vmhandlerpushvm
Capsule(.message("一条简短消息"))
// Capsule(.message(""))
//
Capsule("一条简短消息")
}
section.add(title: "一条稍微长一点的消息") {
// vmhandlerpushhandler
@ -31,13 +34,15 @@ class CapsuleVC: ListVC {
capsule.vm = .message("一条稍微长一点的消息")
}
}
section.add(title: "(默认)状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。") {
section.add(title: "延迟显示") {
// push
let obj = Capsule().target
obj.vm = .message("状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。")
// ... push
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
obj.push()
}
}
section.add(title: "限制1行状态胶囊控件用于状态显示一个主程序窗口只有一个状态胶囊实例。") {
// vmhandler
Capsule(.message("状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。")) { capsule in
@ -146,7 +151,7 @@ class CapsuleVC: ListVC {
}
extension CapsuleTarget.ViewModel {
extension CapsuleViewModel {
static func info(_ text: String?) -> Self {
.init()

View File

@ -25,7 +25,7 @@ class SheetVC: ListVC {
sheet.add(spacing: 24)
sheet.add(action: "确认", style: .destructive) { sheet in
Alert(.confirm) { alert in
alert.vm.title = "处理点击事件"
alert.title = "处理点击事件"
alert.add(action: "我知道了")
}
}
@ -74,12 +74,11 @@ class SheetVC: ListVC {
sheet.add(action: "确认")
sheet.add(action: "取消", style: .gray)
sheet.onTappedBackground { sheet in
print("点击了背景")
Toast.lazyPush(identifier: "alert") { toast in
toast.vm = .error
toast.vm.title = "点击了背景"
toast.vm.message = "点击背景将不会dismiss必须在下方做出选择才能关掉"
toast.vm.duration = 2
.title("点击了背景")
.message("点击背景将不会dismiss必须在下方做出选择才能关掉")
.duration(2)
}
}
}

View File

@ -39,7 +39,10 @@ class TestToastTarget: ToastTarget {
}
}
typealias TestToast = HUDProvider<ToastViewModel, TestToastTarget>
//typealias TestToast = HUDProvider<ToastViewModel, TestToastTarget>
class TestToast: ToastProvider {
typealias Target = TestToastTarget
}
class ToastVC: ListVC {
@ -54,6 +57,7 @@ class ToastVC: ListVC {
header.detailLabel.text = message
ToastConfiguration.global { config in
config.defaultDuration = 5
config.contentViewMask { mask in
mask.backgroundColor = .clear
mask.effect = UIBlurEffect(style: .systemChromeMaterial)
@ -68,7 +72,9 @@ class ToastVC: ListVC {
TestToast(.title(title).message(message))
}
section.add(title: "一段长文本") {
Toast(.message(message))
// Toast(.message(message))
//
Toast(message)
}
section.add(title: "图标 + 标题 + 正文") {
let s1 = "笑容正在加载"
@ -80,8 +86,10 @@ class ToastVC: ListVC {
toast.update(progress: percent)
} completion: {
toast.update { toast in
toast.vm = .success(5).title("加载成功").message("这条通知5s后消失")
toast.vm.icon = UIImage(named: "twemoji")
toast.vm = .success(5)
.title("加载成功")
.message("这条通知5s后消失")
.icon(.init(named: "twemoji"))
}
}
}
@ -119,16 +127,15 @@ class ToastVC: ListVC {
section.add(title: "增加按钮") {
let title = "您收到了一条好友申请"
let message = "丹妮莉丝·坦格利安申请添加您为好友,是否同意?"
Toast(.title(title).message(message)) { toast in
Toast(.title(title).message(message).icon(.init(named: "avatar"))) { toast in
toast.isRemovable = false
toast.vm.icon = UIImage(named: "avatar")
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.vm = .message("Dracarys")
alert.vm.icon = UIImage(inProHUD: "prohud.windmill")
alert.vm.rotation = .init(repeatCount: .infinity)
.icon(UIImage(inProHUD: "prohud.windmill"))
.rotation(.init(repeatCount: .infinity))
alert.config.enableShadow = false
alert.config.contentViewMask { mask in
mask.effect = .none
@ -217,7 +224,7 @@ class ToastVC: ListVC {
}
section.add(title: "修改左右外边距") {
Toast(.message("这条toast的左右外边距经过自定义设置与其它的有所不同。")) { toast in
toast.config.windowEdgeInset = 8
toast.config.marginX = 32
toast.config.cardCornerRadius = 24
}
}
@ -306,7 +313,7 @@ class ToastVC: ListVC {
section.add(title: "卡片背景样式") {
Toast { toast in
toast.vm.title = "卡片背景样式"
toast.title = "卡片背景样式"
toast.add(action: "浅色毛玻璃") { toast in
toast.contentMaskView.effect = UIBlurEffect(style: .light)
toast.contentMaskView.backgroundColor = .clear
@ -327,7 +334,7 @@ class ToastVC: ListVC {
func foo() {
Toast { toast in
toast.title = "共享配置"
toast.vm.message = "建议在App启动后进行通用配置设置所有实例都会先拉取通用配置为默认值修改这些配置会影响到所有实例。"
toast.vm?.message = "建议在App启动后进行通用配置设置所有实例都会先拉取通用配置为默认值修改这些配置会影响到所有实例。"
toast.add(action: "默认", style: .gray) { toast in
ToastConfiguration.global { config in
config.customTitleLabel { titleLabel in
@ -359,7 +366,7 @@ class ToastVC: ListVC {
fileprivate func testAlert() {
Alert { alert in
alert.vm.title = "处理点击事件"
alert.title = "处理点击事件"
alert.add(action: "我知道了", style: .destructive)
}
}

View File

@ -17,7 +17,7 @@ extension AlertTarget: DefaultLayout {
if self.cfg.customReloadData?(self) == true {
return
}
view.tintColor = vm.tintColor ?? config.tintColor
view.tintColor = vm?.tintColor ?? config.tintColor
let isFirstLayout: Bool
if contentView.superview == nil {
isFirstLayout = animated
@ -126,7 +126,7 @@ extension AlertTarget: DefaultLayout {
func updateTimeoutDuration() {
//
vm.timeoutHandler = DispatchWorkItem(block: { [weak self] in
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
self?.pop()
})
}
@ -144,9 +144,9 @@ extension AlertTarget {
//
progressView?.removeFromSuperview()
if vm.icon != nil || vm.iconURL != nil {
imageView.image = vm.icon
if let iconURL = vm.iconURL {
if vm?.icon != nil || vm?.iconURL != nil {
imageView.image = vm?.icon
if let iconURL = vm?.iconURL {
config.customWebImage?(imageView, iconURL)
}
if imageView.superview == nil {
@ -159,7 +159,7 @@ extension AlertTarget {
mk.height.equalTo(config.iconSize.height)
}
}
if let rotation = vm.rotation {
if let rotation = vm?.rotation {
startRotate(rotation)
}
} else {
@ -171,8 +171,8 @@ extension AlertTarget {
}
func setupTextStack() {
let titleCount = vm.title?.count ?? 0
let bodyCount = vm.message?.count ?? 0
let titleCount = vm?.title?.count ?? 0
let bodyCount = vm?.message?.count ?? 0
if titleCount > 0 || bodyCount > 0 {
if textStack.superview != contentStack {
if let index = contentStack.arrangedSubviews.firstIndex(of: imageView) {
@ -189,7 +189,7 @@ extension AlertTarget {
}
}
if titleCount > 0 {
titleLabel.text = vm.title
titleLabel.text = vm?.title
if titleLabel.superview != textStack {
textStack.insertArrangedSubview(titleLabel, at: 0)
}
@ -209,7 +209,7 @@ extension AlertTarget {
titleLabel.removeFromSuperview()
}
if bodyCount > 0 {
bodyLabel.text = vm.message
bodyLabel.text = vm?.message
if bodyLabel.superview != textStack {
textStack.addArrangedSubview(bodyLabel)
}

View File

@ -8,13 +8,30 @@
import UIKit
open class AlertProvider: HUDProvider<AlertViewModel, AlertTarget> {
@discardableResult public required init(_ vm: ViewModel?, initializer: ((_ alert: Target) -> Void)?) {
super.init(vm, initializer: initializer)
public typealias ViewModel = AlertViewModel
public typealias Target = AlertTarget
@discardableResult @objc public required init(initializer: ((_ alert: Target) -> Void)?) {
super.init(initializer: initializer)
}
@discardableResult public required convenience init(initializer: ((_ alert: Target) -> Void)?) {
self.init(nil, initializer: initializer)
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ alert: Target) -> Void)?) {
self.init { alert in
alert.vm = vm
initializer?(alert)
}
}
/// ViewModelTarget
/// - Parameter vm:
@discardableResult public convenience init(_ vm: ViewModel) {
self.init(vm, initializer: nil)
}
@discardableResult @objc public convenience init(_ text: String, duration: TimeInterval = 3) {
self.init(.message(text).duration(duration), initializer: nil)
}
/// HUD
/// - Parameters:

View File

@ -74,11 +74,15 @@ open class AlertTarget: BaseController, HUDTargetType {
}()
///
@objc public var vm: AlertViewModel = .init()
@objc public var vm: AlertViewModel?
public override var title: String? {
didSet {
if let vm = vm {
vm.title = title
} else {
vm = .title(title)
}
}
}

View File

@ -19,6 +19,9 @@ public class CapsuleConfiguration: CommonConfiguration {
customGlobalConfig = callback
}
///
public var defaultDuration: TimeInterval = 3
override var cardCornerRadiusByDefault: CGFloat {
cardCornerRadius ?? 16
}

View File

@ -18,7 +18,7 @@ extension CapsuleTarget: DefaultLayout {
return
}
view.tintColor = vm.tintColor ?? config.tintColor
view.tintColor = vm?.tintColor ?? config.tintColor
// content
loadContentViewIfNeeded()
@ -29,7 +29,7 @@ extension CapsuleTarget: DefaultLayout {
// text
textLabel.removeFromSuperview()
var text = [vm.title ?? "", vm.message ?? ""].filter({ $0.count > 0 }).joined(separator: " ")
var text = [vm?.title ?? "", vm?.message ?? ""].filter({ $0.count > 0 }).joined(separator: " ")
if text.count > 0 {
contentStack.addArrangedSubview(textLabel)
textLabel.snp.makeConstraints { make in
@ -81,11 +81,11 @@ extension CapsuleTarget: DefaultLayout {
private func updateTimeoutDuration() {
// 使
if vm.duration == nil {
vm.duration = 3
if vm?.duration == nil {
vm?.duration = config.defaultDuration
}
//
vm.timeoutHandler = DispatchWorkItem(block: { [weak self] in
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
self?.pop()
})
}
@ -99,16 +99,16 @@ extension CapsuleTarget: DefaultLayout {
//
progressView?.removeFromSuperview()
if vm.icon == nil && vm.iconURL == nil {
if vm?.icon == nil && vm?.iconURL == nil {
contentStack.removeArrangedSubview(imageView)
} else {
contentStack.insertArrangedSubview(imageView, at: 0)
}
imageView.image = vm.icon
if let iconURL = vm.iconURL {
imageView.image = vm?.icon
if let iconURL = vm?.iconURL {
config.customWebImage?(imageView, iconURL)
}
if let rotation = vm.rotation {
if let rotation = vm?.rotation {
startRotate(rotation)
}

View File

@ -13,7 +13,7 @@ extension CapsuleTarget {
guard CapsuleConfiguration.isEnabled else { return }
let isNew: Bool
let window: CapsuleWindow
let position = vm.position
let position = vm?.position ?? .top
if let w = AppContext.current?.capsuleWindows[position] {
isNew = false
@ -30,10 +30,10 @@ extension CapsuleTarget {
// frame
let newFrame: CGRect
switch vm.position {
case .top:
let topLayoutMargins = AppContext.appWindow?.layoutMargins.top ?? 8
let y = max(topLayoutMargins - 8, 8)
switch vm?.position {
case .top, .none:
let topLayoutMargins = AppContext.appWindow?.safeAreaInsets.top ?? 8
let y = max(topLayoutMargins, 8)
newFrame = .init(x: (AppContext.appBounds.width - size.width) / 2, y: y, width: size.width, height: size.height)
case .middle:
newFrame = .init(x: (AppContext.appBounds.width - size.width) / 2, y: (AppContext.appBounds.height - size.height) / 2 - 20, width: size.width, height: size.height)
@ -60,6 +60,10 @@ extension CapsuleTarget {
AppContext.capsuleWindows[s]?[position] = window
}
navEvents[.onViewWillAppear]?(self)
// toast
ToastWindow.updateToastWindowsLayout()
if isNew {
window.isHidden = false
func completion() {
@ -116,8 +120,11 @@ extension CapsuleTarget {
@objc open func pop() {
guard let window = attachedWindow, let windowScene = windowScene else { return }
AppContext.capsuleWindows[windowScene]?[vm.position] = nil
AppContext.capsuleWindows[windowScene]?[vm?.position ?? .top] = nil
navEvents[.onViewWillDisappear]?(self)
// toast
ToastWindow.updateToastWindowsLayout()
func completion() {
window.isHidden = true
window.transform = .identity
@ -128,8 +135,8 @@ extension CapsuleTarget {
} else {
let duration = config.animateDurationForBuildOutByDefault
let oldFrame = window.frame
switch vm.position {
case .top:
switch vm?.position {
case .top, .none:
UIView.animateEaseOut(duration: duration) {
window.transform = .init(translationX: 0, y: -oldFrame.maxY - 20)
} completion: { done in

View File

@ -7,20 +7,37 @@
import UIKit
open class CapsuleProvider: HUDProvider<CapsuleTarget.ViewModel, CapsuleTarget> {
open class CapsuleProvider: HUDProvider<CapsuleViewModel, CapsuleTarget> {
public typealias ViewModel = CapsuleViewModel
public typealias Target = CapsuleTarget
@discardableResult @objc public required init(initializer: ((_ capsule: Target) -> Void)?) {
super.init(initializer: initializer)
}
/// ViewModelTarget
/// - Parameters:
/// - vm:
/// - initializer:
@discardableResult public required init(_ vm: ViewModel?, initializer: ((_ capsule: Target) -> Void)?) {
super.init(vm, initializer: initializer)
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ capsule: Target) -> Void)?) {
self.init { capsule in
capsule.vm = vm
initializer?(capsule)
}
}
@discardableResult public required convenience init(initializer: ((_ capsule: Target) -> Void)?) {
self.init(nil, initializer: initializer)
/// ViewModelTarget
/// - Parameter vm:
@discardableResult public convenience init(_ vm: ViewModel) {
self.init(vm, initializer: nil)
}
@discardableResult public convenience init(_ text: String) {
self.init(.message(text), initializer: nil)
}
/// HUD
/// - Parameters:
/// - identifier:

View File

@ -46,7 +46,17 @@ open class CapsuleTarget: BaseController, HUDTargetType {
return lb
}()
public var vm: CapsuleViewModel = .init()
public var vm: CapsuleViewModel?
public override var title: String? {
didSet {
if let vm = vm {
vm.title = title
} else {
vm = .title(title)
}
}
}
private var tapActionCallback: ((_ capsule: CapsuleTarget) -> Void)?

View File

@ -15,8 +15,8 @@ class CapsuleWindow: Window {
self.capsule = capsule
super.init(frame: .zero)
windowScene = AppContext.windowScene
switch capsule.vm.position {
case .top:
switch capsule.vm?.position {
case .top, .none:
// toast
windowLevel = .phCapsuleTop
case .middle:

View File

@ -12,11 +12,9 @@ public protocol HUDProviderType {
associatedtype ViewModel = HUDViewModelType
associatedtype Target = HUDTargetType
/// ViewModelTarget
/// - Parameters:
/// - vm:
/// - initializer:
@discardableResult init(_ vm: ViewModel?, initializer: ((_ target: Target) -> Void)?)
/// Target
/// - Parameter initializer:
@discardableResult init(initializer: ((_ target: Target) -> Void)?)
}
@ -25,39 +23,22 @@ open class HUDProvider<ViewModel: HUDViewModelType, Target: HUDTargetType>: HUDP
/// HUD
public var target: Target
/// ViewModelTarget
/// - Parameters:
/// - vm:
/// - initializer:
@discardableResult public required init(_ vm: ViewModel?, initializer: ((_ target: Target) -> Void)?) {
/// Target
/// - Parameter initializer:
@discardableResult public required init(initializer: ((_ target: Target) -> Void)?) {
var t = Target()
if let vm = vm as? Target.ViewModel {
t.vm = vm
}
initializer?(t)
self.target = t
if (vm == nil && initializer == nil) == false {
if (t.vm == nil && initializer == nil) == false {
DispatchQueue.main.async {
t.push()
}
}
}
/// Target
/// - Parameter initializer:
@discardableResult public convenience init(initializer: ((_ target: Target) -> Void)?) {
self.init(nil, initializer: initializer)
}
/// ViewModelTarget
/// - Parameter vm:
@discardableResult public convenience init(_ vm: ViewModel?) {
self.init(vm, initializer: nil)
}
/// target.push()
@discardableResult public convenience init() {
self.init(nil, initializer: nil)
self.init(initializer: nil)
}

View File

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

View File

@ -36,7 +36,7 @@ public struct AppContext {
static var toastWindows: [UIWindowScene: [ToastWindow]] = [:]
static var alertWindow: [UIWindowScene: AlertWindow] = [:]
static var sheetWindows: [UIWindowScene: [SheetWindow]] = [:]
static var capsuleWindows: [UIWindowScene: [CapsuleTarget.ViewModel.Position: CapsuleWindow]] = [:]
static var capsuleWindows: [UIWindowScene: [CapsuleViewModel.Position: CapsuleWindow]] = [:]
static var current: AppContext? {
guard let windowScene = windowScene else { return nil }
@ -120,7 +120,7 @@ extension AppContext {
var toastWindows: [ToastWindow] {
Self.toastWindows[windowScene] ?? []
}
var capsuleWindows: [CapsuleTarget.ViewModel.Position: CapsuleWindow] {
var capsuleWindows: [CapsuleViewModel.Position: CapsuleWindow] {
Self.capsuleWindows[windowScene] ?? [:]
}
}

View File

@ -17,7 +17,7 @@ extension SheetTarget: DefaultLayout {
if self.cfg.customReloadData?(self) == true {
return
}
view.tintColor = vm.tintColor ?? config.tintColor
view.tintColor = vm?.tintColor ?? config.tintColor
// background
if backgroundView.superview == nil {
view.insertSubview(backgroundView, at: 0)

View File

@ -8,12 +8,12 @@
import UIKit
open class SheetProvider: HUDProvider<SheetViewModel, SheetTarget> {
@discardableResult public required init(_ vm: ViewModel?, initializer: ((_ sheet: Target) -> Void)?) {
super.init(vm, initializer: initializer)
}
@discardableResult public required convenience init(initializer: ((_ sheet: Target) -> Void)?) {
self.init(nil, initializer: initializer)
public typealias ViewModel = SheetViewModel
public typealias Target = SheetTarget
@discardableResult @objc public required init(initializer: ((_ sheet: Target) -> Void)?) {
super.init(initializer: initializer)
}
/// HUD

View File

@ -41,7 +41,7 @@ open class SheetTarget: BaseController, HUDTargetType {
}
}
public var vm: SheetViewModel = .init()
public var vm: SheetViewModel? = nil
required public override init() {
super.init()

View File

@ -9,16 +9,6 @@ import UIKit
public class ToastConfiguration: CommonConfiguration {
///
public var margin = CGFloat(8)
var customInfoStack: ((_ stack: StackView) -> Void)?
public func customInfoStack(handler: @escaping (_ stack: StackView) -> Void) {
customInfoStack = handler
}
///
public var lineSpace = CGFloat(4)
static var customGlobalConfig: ((_ config: ToastConfiguration) -> Void)?
///
@ -27,11 +17,21 @@ public class ToastConfiguration: CommonConfiguration {
customGlobalConfig = callback
}
///
public var windowEdgeInset: CGFloat?
var windowEdgeInsetByDefault: CGFloat {
windowEdgeInset ?? 16
///
public var defaultDuration: TimeInterval = 10
///
public var marginX = CGFloat(8)
///
public var marginY = CGFloat(8)
var customInfoStack: ((_ stack: StackView) -> Void)?
public func customInfoStack(handler: @escaping (_ stack: StackView) -> Void) {
customInfoStack = handler
}
///
public var lineSpace = CGFloat(4)
override var cardMaxWidthByDefault: CGFloat {
cardMaxWidth ?? 500

View File

@ -17,7 +17,7 @@ extension ToastTarget: DefaultLayout {
if self.cfg.customReloadData?(self) == true {
return
}
view.tintColor = vm.tintColor ?? config.tintColor
view.tintColor = vm?.tintColor ?? config.tintColor
loadContentViewIfNeeded()
loadContentMaskViewIfNeeded()
guard customView == nil else {
@ -26,7 +26,7 @@ extension ToastTarget: DefaultLayout {
}
return
}
if vm.icon != nil || vm.iconURL != nil {
if vm?.icon != nil || vm?.iconURL != nil {
if imageView.superview == nil {
infoStack.insertArrangedSubview(imageView, at: 0)
imageView.snp.makeConstraints { make in
@ -42,8 +42,8 @@ extension ToastTarget: DefaultLayout {
if textStack.superview == nil {
infoStack.addArrangedSubview(textStack)
}
let titleCount = vm.title?.count ?? 0
let bodyCount = vm.message?.count ?? 0
let titleCount = vm?.title?.count ?? 0
let bodyCount = vm?.message?.count ?? 0
if titleCount > 0 {
textStack.insertArrangedSubview(titleLabel, at: 0)
if bodyCount > 0 {
@ -79,14 +79,12 @@ extension ToastTarget: DefaultLayout {
bodyLabel.removeFromSuperview()
}
//
titleLabel.text = vm.title
bodyLabel.text = vm.message
titleLabel.text = vm?.title
bodyLabel.text = vm?.message
view.layoutIfNeeded()
//
vm.timeoutHandler = DispatchWorkItem(block: { [weak self] in
self?.pop()
})
updateTimeoutDuration()
setupImageView()
@ -132,6 +130,17 @@ extension ToastTarget {
}
}
private func updateTimeoutDuration() {
// 使
if vm?.duration == nil {
vm?.duration = config.defaultDuration
}
//
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
self?.pop()
})
}
func setupImageView() {
//
stopRotate(animateLayer)
@ -141,11 +150,11 @@ extension ToastTarget {
//
progressView?.removeFromSuperview()
imageView.image = vm.icon
if let iconURL = vm.iconURL {
imageView.image = vm?.icon
if let iconURL = vm?.iconURL {
config.customWebImage?(imageView, iconURL)
}
if let rotation = vm.rotation {
if let rotation = vm?.rotation {
startRotate(rotation)
}

View File

@ -50,7 +50,7 @@ extension ToastTarget {
// frame
let cardEdgeInsets = config.cardEdgeInsetsByDefault
let width = CGFloat.minimum(AppContext.appBounds.width - config.windowEdgeInsetByDefault - config.windowEdgeInsetByDefault, config.cardMaxWidthByDefault)
let width = CGFloat.minimum(AppContext.appBounds.width - config.marginX - config.marginX, config.cardMaxWidthByDefault)
view.frame.size = CGSize(width: width, height: config.cardMaxHeightByDefault)
titleLabel.sizeToFit()
bodyLabel.sizeToFit()
@ -92,7 +92,7 @@ extension ToastTarget {
} else {
consolePrint("代码漏洞已经没有toast了")
}
vm.duration = nil
vm?.duration = nil
setContextWindows(windows)
UIView.animateEaseOut(duration: config.animateDurationForBuildOutByDefault) {
window.transform = .init(translationX: 0, y: 0-20-window.maxY)
@ -124,21 +124,34 @@ fileprivate var updateToastsLayoutWorkItem: DispatchWorkItem?
fileprivate extension ToastWindow {
static func setToastWindowsLayout(windows: [ToastWindow]) {
var windows: [Window] = windows
if let win = AppContext.current?.capsuleWindows[.top] {
windows.insert(win, at: 0)
}
for (i, window) in windows.enumerated() {
let config = window.toast.config
let margin: CGFloat
if let window = window as? ToastWindow {
margin = window.toast.config.marginY
} else if let window = window as? CapsuleWindow {
margin = window.safeAreaInsets.top
} else {
margin = 8
}
var y = window.frame.origin.y
if i == 0 {
let topLayoutMargins = AppContext.appWindow?.layoutMargins.top ?? config.margin
y = max(topLayoutMargins - config.margin, config.margin)
let topLayoutMargins = AppContext.appWindow?.safeAreaInsets.top ?? margin
y = max(topLayoutMargins, margin)
} else {
if i - 1 < windows.count && i > 0 {
y = config.margin + windows[i-1].frame.maxY
y = margin + windows[i-1].frame.maxY
} else {
y = config.margin
y = margin
}
}
if let window = window as? ToastWindow {
window.maxY = y + window.frame.size.height
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
}
UIView.animateEaseOut(duration: 0.68) {
window.frame.origin.y = y
}
}
@ -154,3 +167,10 @@ fileprivate extension ToastWindow {
}
}
extension ToastWindow {
static func updateToastWindowsLayout() {
let wins = AppContext.current?.toastWindows ?? []
updateToastWindowsLayout(windows: wins)
}
}

View File

@ -8,19 +8,37 @@
import UIKit
open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> {
@discardableResult public required init(_ vm: ViewModel?, initializer: ((_ toast: Target) -> Void)?) {
super.init(vm, initializer: initializer)
public typealias ViewModel = ToastViewModel
public typealias Target = ToastTarget
@discardableResult @objc public required init(initializer: ((_ toast: Target) -> Void)?) {
super.init(initializer: initializer)
}
@discardableResult public required convenience init(initializer: ((_ toast: Target) -> Void)?) {
self.init(nil, initializer: initializer)
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ toast: Target) -> Void)?) {
self.init { toast in
toast.vm = vm
initializer?(toast)
}
}
/// ViewModelTarget
/// - Parameter vm:
@discardableResult public convenience init(_ vm: ViewModel) {
self.init(vm, initializer: nil)
}
@discardableResult @objc public convenience init(_ text: String) {
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 (_ toast: ToastTarget) -> Void, onExists: ((_ toast: ToastTarget) -> Void)? = nil) {
@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)
@ -35,7 +53,7 @@ open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> {
/// HUD
/// - Parameter identifier:
/// - Returns: HUD
@discardableResult public static func find(identifier: String, update handler: ((_ toast: ToastTarget) -> Void)? = nil) -> [ToastTarget] {
@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) })

View File

@ -84,14 +84,17 @@ open class ToastTarget: BaseController, HUDTargetType {
public var isRemovable = true
///
@objc public var vm = ToastViewModel()
@objc public var vm: ToastViewModel?
private var tapActionCallback: ((_ toast: ToastTarget) -> Void)?
public override var title: String? {
didSet {
if let vm = vm {
vm.title = title
} else {
vm = .title(title)
}
}
}
@ -135,7 +138,7 @@ fileprivate extension ToastTarget {
///
/// - Parameter sender:
@objc func _onPanGesture(_ sender: UIPanGestureRecognizer) {
vm.timeoutTimer?.invalidate()
vm?.timeoutTimer?.invalidate()
let point = sender.translation(in: sender.view)
window?.transform = .init(translationX: 0, y: point.y)
if sender.state == .recognized {
@ -150,8 +153,8 @@ fileprivate extension ToastTarget {
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
self.window?.transform = .identity
} completion: { done in
let d = self.vm.duration
self.vm.duration = d
let d = self.vm?.duration
self.vm?.duration = d
}
}
}