mirror of https://github.com/xaoxuu/ProHUD
代码重构
This commit is contained in:
parent
32f9af1ae6
commit
0574844a7f
|
@ -29,14 +29,15 @@ class AlertVC: ListVC {
|
||||||
|
|
||||||
list.add(title: "纯文字") { section in
|
list.add(title: "纯文字") { section in
|
||||||
section.add(title: "只有一句话") {
|
section.add(title: "只有一句话") {
|
||||||
Alert(.message("只有一句话").duration(2))
|
// Alert(.message("只有一句话").duration(2))
|
||||||
|
// 可以简写成这样:
|
||||||
|
Alert("只有一句话")
|
||||||
}
|
}
|
||||||
section.add(title: "标题 + 正文") {
|
section.add(title: "标题 + 正文") {
|
||||||
let title = "这是标题"
|
let title = "这是标题"
|
||||||
let message = "这是正文,文字支持自动换行,可设置最小宽度和最大宽度。这个弹窗将会持续4秒。"
|
let message = "这是正文,文字支持自动换行,可设置最小宽度和最大宽度。这个弹窗将会持续4秒。"
|
||||||
Alert { alert in
|
Alert { alert in
|
||||||
alert.vm = .title(title).message(message)
|
alert.vm = .title(title).message(message).duration(4)
|
||||||
alert.vm.duration = 4
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,9 +63,9 @@ class AlertVC: ListVC {
|
||||||
}
|
}
|
||||||
section.add(title: "图标 + 标题 + 正文") {
|
section.add(title: "图标 + 标题 + 正文") {
|
||||||
Alert(.error) { alert in
|
Alert(.error) { alert in
|
||||||
alert.vm.title = "加载失败"
|
alert.vm?.title = "加载失败"
|
||||||
alert.vm.message = "请稍后重试"
|
alert.vm?.message = "请稍后重试"
|
||||||
alert.vm.duration = 3
|
alert.vm?.duration = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +82,7 @@ class AlertVC: ListVC {
|
||||||
alert.config.customButton { button in
|
alert.config.customButton { button in
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 15)
|
button.titleLabel?.font = .systemFont(ofSize: 15)
|
||||||
}
|
}
|
||||||
alert.vm.title = "你正在使用移动网络观看"
|
alert.title = "你正在使用移动网络观看"
|
||||||
alert.onViewDidLoad { vc in
|
alert.onViewDidLoad { vc in
|
||||||
guard let alert = vc as? AlertTarget else {
|
guard let alert = vc as? AlertTarget else {
|
||||||
return
|
return
|
||||||
|
@ -111,7 +112,7 @@ class AlertVC: ListVC {
|
||||||
alert.config.customButton { button in
|
alert.config.customButton { button in
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 15)
|
button.titleLabel?.font = .systemFont(ofSize: 15)
|
||||||
}
|
}
|
||||||
alert.vm.message = "为了维护社区氛围,上麦用户需进行主播认证"
|
alert.vm?.message = "为了维护社区氛围,上麦用户需进行主播认证"
|
||||||
alert.onViewDidLoad { vc in
|
alert.onViewDidLoad { vc in
|
||||||
guard let alert = vc as? AlertTarget else {
|
guard let alert = vc as? AlertTarget else {
|
||||||
return
|
return
|
||||||
|
@ -141,7 +142,7 @@ class AlertVC: ListVC {
|
||||||
alert.config.customButton { button in
|
alert.config.customButton { button in
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 15)
|
button.titleLabel?.font = .systemFont(ofSize: 15)
|
||||||
}
|
}
|
||||||
alert.vm.message = "本次消费需要你支付999软妹豆,确认支付吗?"
|
alert.vm?.message = "本次消费需要你支付999软妹豆,确认支付吗?"
|
||||||
alert.config.customActionStack { stack in
|
alert.config.customActionStack { stack in
|
||||||
stack.spacing = 0
|
stack.spacing = 0
|
||||||
stack.axis = .vertical // 竖排按钮
|
stack.axis = .vertical // 竖排按钮
|
||||||
|
@ -187,15 +188,15 @@ class AlertVC: ListVC {
|
||||||
|
|
||||||
section.add(title: "只有一段文字 + 按钮") {
|
section.add(title: "只有一段文字 + 按钮") {
|
||||||
Alert { alert in
|
Alert { alert in
|
||||||
alert.vm.title = "只有一段文字"
|
alert.title = "只有一段文字"
|
||||||
alert.add(action: "取消", style: .gray)
|
alert.add(action: "取消", style: .gray)
|
||||||
alert.add(action: "默认按钮")
|
alert.add(action: "默认按钮")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section.add(title: "标题 + 正文 + 按钮") {
|
section.add(title: "标题 + 正文 + 按钮") {
|
||||||
Alert { alert in
|
Alert { alert in
|
||||||
alert.vm.title = "标题"
|
alert.vm?.title = "标题"
|
||||||
alert.vm.message = "这是一段正文,长度超出最大宽度时会自动换行"
|
alert.vm?.message = "这是一段正文,长度超出最大宽度时会自动换行"
|
||||||
alert.add(action: "取消", style: .gray)
|
alert.add(action: "取消", style: .gray)
|
||||||
alert.add(action: "删除", style: .destructive) { alert in
|
alert.add(action: "删除", style: .destructive) { alert in
|
||||||
// 自定义了按钮事件之后,需要手动pop弹窗
|
// 自定义了按钮事件之后,需要手动pop弹窗
|
||||||
|
@ -229,8 +230,8 @@ class AlertVC: ListVC {
|
||||||
}
|
}
|
||||||
section.add(title: "确认删除") {
|
section.add(title: "确认删除") {
|
||||||
Alert(.delete) { alert in
|
Alert(.delete) { alert in
|
||||||
alert.vm.title = "确认删除"
|
alert.vm?.title = "确认删除"
|
||||||
alert.vm.message = "此操作无法撤销"
|
alert.vm?.message = "此操作无法撤销"
|
||||||
alert.add(action: "取消", style: .gray)
|
alert.add(action: "取消", style: .gray)
|
||||||
alert.add(action: "删除", style: .destructive)
|
alert.add(action: "删除", style: .destructive)
|
||||||
}
|
}
|
||||||
|
@ -239,7 +240,7 @@ class AlertVC: ListVC {
|
||||||
list.add(title: "控件管理") { section in
|
list.add(title: "控件管理") { section in
|
||||||
section.add(title: "按钮增删改查") {
|
section.add(title: "按钮增删改查") {
|
||||||
Alert(.note) { alert in
|
Alert(.note) { alert in
|
||||||
alert.vm.message = "可以动态增加、删除按钮"
|
alert.vm?.message = "可以动态增加、删除按钮"
|
||||||
alert.add(action: "在底部增加按钮", style: .filled(color: .systemGreen)) { alert in
|
alert.add(action: "在底部增加按钮", style: .filled(color: .systemGreen)) { alert in
|
||||||
alert.add(action: "哈哈1", identifier: "haha1")
|
alert.add(action: "哈哈1", identifier: "haha1")
|
||||||
}
|
}
|
||||||
|
@ -265,36 +266,34 @@ class AlertVC: ListVC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section.add(title: "更新文字") {
|
section.add(title: "更新文字") {
|
||||||
Alert(.note) { alert in
|
Alert(.note.message("可以动态增加、删除、更新文字")) { alert in
|
||||||
alert.vm.message = "可以动态增加、删除、更新文字"
|
|
||||||
alert.add(action: "增加标题") { alert in
|
alert.add(action: "增加标题") { alert in
|
||||||
alert.vm.title = "这是标题"
|
alert.vm?.title = "这是标题"
|
||||||
alert.reloadTextStack()
|
alert.reloadTextStack()
|
||||||
}
|
}
|
||||||
alert.add(action: "增加正文") { alert in
|
alert.add(action: "增加正文") { alert in
|
||||||
alert.vm.message = "可以动态增加、删除、更新文字"
|
alert.vm?.message = "可以动态增加、删除、更新文字"
|
||||||
alert.reloadTextStack()
|
alert.reloadTextStack()
|
||||||
}
|
}
|
||||||
alert.add(action: "删除标题", style: .destructive) { alert in
|
alert.add(action: "删除标题", style: .destructive) { alert in
|
||||||
alert.vm.title = nil
|
alert.vm?.title = nil
|
||||||
alert.reloadTextStack()
|
alert.reloadTextStack()
|
||||||
}
|
}
|
||||||
alert.add(action: "删除正文", style: .destructive) { alert in
|
alert.add(action: "删除正文", style: .destructive) { alert in
|
||||||
alert.vm.message = nil
|
alert.vm?.message = nil
|
||||||
alert.reloadTextStack()
|
alert.reloadTextStack()
|
||||||
}
|
}
|
||||||
alert.add(action: "取消", style: .gray)
|
alert.add(action: "取消", style: .gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section.add(title: "在弹出过程中增加元素") {
|
section.add(title: "在弹出过程中增加元素") {
|
||||||
Alert(.loading) { alert in
|
Alert(.loading.title("在弹出过程中增加元素")) { alert in
|
||||||
alert.vm.title = "在弹出过程中增加元素"
|
|
||||||
alert.add(action: "OK", style: .gray)
|
alert.add(action: "OK", style: .gray)
|
||||||
alert.onViewWillAppear { vc in
|
alert.onViewWillAppear { vc in
|
||||||
guard let alert = vc as? AlertTarget else {
|
guard let alert = vc as? AlertTarget else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
alert.vm.message = "这是一段后增加的文字\n动画效果会有细微差别"
|
alert.vm?.message = "这是一段后增加的文字\n动画效果会有细微差别"
|
||||||
alert.reloadTextStack()
|
alert.reloadTextStack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,8 +303,8 @@ class AlertVC: ListVC {
|
||||||
section.add(title: "多层级弹窗") {
|
section.add(title: "多层级弹窗") {
|
||||||
func f(i: Int) {
|
func f(i: Int) {
|
||||||
Alert { alert in
|
Alert { alert in
|
||||||
alert.vm.title = "第\(i)次弹"
|
alert.vm?.title = "第\(i)次弹"
|
||||||
alert.vm.message = "每次都是一个新的实例覆盖在上一个弹窗上面,而背景不会叠加变深。"
|
alert.vm?.message = "每次都是一个新的实例覆盖在上一个弹窗上面,而背景不会叠加变深。"
|
||||||
alert.add(action: "取消", style: .gray)
|
alert.add(action: "取消", style: .gray)
|
||||||
alert.add(action: "增加一个") { alert in
|
alert.add(action: "增加一个") { alert in
|
||||||
f(i: i + 1)
|
f(i: i + 1)
|
||||||
|
@ -357,7 +356,7 @@ class AlertVC: ListVC {
|
||||||
list.add(title: "自定义视图") { section in
|
list.add(title: "自定义视图") { section in
|
||||||
section.add(title: "自定义控件") {
|
section.add(title: "自定义控件") {
|
||||||
Alert { alert in
|
Alert { alert in
|
||||||
alert.vm.title = "自定义控件"
|
alert.title = "自定义控件"
|
||||||
// 图片
|
// 图片
|
||||||
let imgv = UIImageView(image: UIImage(named: "landscape"))
|
let imgv = UIImageView(image: UIImage(named: "landscape"))
|
||||||
imgv.contentMode = .scaleAspectFill
|
imgv.contentMode = .scaleAspectFill
|
||||||
|
|
|
@ -17,12 +17,15 @@ class CapsuleVC: ListVC {
|
||||||
header.detailLabel.text = "状态胶囊控件,用于状态显示,一个主程序窗口每个位置(上中下)各自最多只有一个状态胶囊实例。"
|
header.detailLabel.text = "状态胶囊控件,用于状态显示,一个主程序窗口每个位置(上中下)各自最多只有一个状态胶囊实例。"
|
||||||
|
|
||||||
CapsuleConfiguration.global { config in
|
CapsuleConfiguration.global { config in
|
||||||
|
config.defaultDuration = 3 // 默认的持续时间
|
||||||
// config.cardCornerRadius = .infinity // 设置一个较大的数字就会变成胶囊形状
|
// config.cardCornerRadius = .infinity // 设置一个较大的数字就会变成胶囊形状
|
||||||
}
|
}
|
||||||
list.add(title: "默认布局:纯文字") { section in
|
list.add(title: "默认布局:纯文字") { section in
|
||||||
section.add(title: "一条简短的消息") {
|
section.add(title: "一条简短的消息") {
|
||||||
// 设置vm或者handler都会自动push,这里测试传入vm:
|
// 设置vm或者handler都会自动push,这里测试传入vm:
|
||||||
Capsule(.message("一条简短消息"))
|
// Capsule(.message("一条简短消息"))
|
||||||
|
// 如果只有一条文字信息,可以直接传字符串:
|
||||||
|
Capsule("一条简短消息")
|
||||||
}
|
}
|
||||||
section.add(title: "一条稍微长一点的消息") {
|
section.add(title: "一条稍微长一点的消息") {
|
||||||
// 设置vm或者handler都会自动push,这里测试传入handler:
|
// 设置vm或者handler都会自动push,这里测试传入handler:
|
||||||
|
@ -31,13 +34,15 @@ class CapsuleVC: ListVC {
|
||||||
capsule.vm = .message("一条稍微长一点的消息")
|
capsule.vm = .message("一条稍微长一点的消息")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section.add(title: "(默认)状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。") {
|
section.add(title: "延迟显示") {
|
||||||
// 也可以创建一个空白实例,在需要的时候再push
|
// 也可以创建一个空白实例,在需要的时候再push
|
||||||
let obj = Capsule().target
|
let obj = Capsule().target
|
||||||
obj.vm = .message("状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。")
|
obj.vm = .message("状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。")
|
||||||
// ... 在需要的时候手动push
|
// ... 在需要的时候手动push
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||||
obj.push()
|
obj.push()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
section.add(title: "(限制1行)状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。") {
|
section.add(title: "(限制1行)状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。") {
|
||||||
// 同时设置vm和handler也可以
|
// 同时设置vm和handler也可以
|
||||||
Capsule(.message("状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。")) { capsule in
|
Capsule(.message("状态胶囊控件,用于状态显示,一个主程序窗口只有一个状态胶囊实例。")) { capsule in
|
||||||
|
@ -146,7 +151,7 @@ class CapsuleVC: ListVC {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CapsuleTarget.ViewModel {
|
extension CapsuleViewModel {
|
||||||
|
|
||||||
static func info(_ text: String?) -> Self {
|
static func info(_ text: String?) -> Self {
|
||||||
.init()
|
.init()
|
||||||
|
|
|
@ -25,7 +25,7 @@ class SheetVC: ListVC {
|
||||||
sheet.add(spacing: 24)
|
sheet.add(spacing: 24)
|
||||||
sheet.add(action: "确认", style: .destructive) { sheet in
|
sheet.add(action: "确认", style: .destructive) { sheet in
|
||||||
Alert(.confirm) { alert in
|
Alert(.confirm) { alert in
|
||||||
alert.vm.title = "处理点击事件"
|
alert.title = "处理点击事件"
|
||||||
alert.add(action: "我知道了")
|
alert.add(action: "我知道了")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,12 +74,11 @@ class SheetVC: ListVC {
|
||||||
sheet.add(action: "确认")
|
sheet.add(action: "确认")
|
||||||
sheet.add(action: "取消", style: .gray)
|
sheet.add(action: "取消", style: .gray)
|
||||||
sheet.onTappedBackground { sheet in
|
sheet.onTappedBackground { sheet in
|
||||||
print("点击了背景")
|
|
||||||
Toast.lazyPush(identifier: "alert") { toast in
|
Toast.lazyPush(identifier: "alert") { toast in
|
||||||
toast.vm = .error
|
toast.vm = .error
|
||||||
toast.vm.title = "点击了背景"
|
.title("点击了背景")
|
||||||
toast.vm.message = "点击背景将不会dismiss,必须在下方做出选择才能关掉"
|
.message("点击背景将不会dismiss,必须在下方做出选择才能关掉")
|
||||||
toast.vm.duration = 2
|
.duration(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
class ToastVC: ListVC {
|
||||||
|
|
||||||
|
@ -54,6 +57,7 @@ class ToastVC: ListVC {
|
||||||
header.detailLabel.text = message
|
header.detailLabel.text = message
|
||||||
|
|
||||||
ToastConfiguration.global { config in
|
ToastConfiguration.global { config in
|
||||||
|
config.defaultDuration = 5
|
||||||
config.contentViewMask { mask in
|
config.contentViewMask { mask in
|
||||||
mask.backgroundColor = .clear
|
mask.backgroundColor = .clear
|
||||||
mask.effect = UIBlurEffect(style: .systemChromeMaterial)
|
mask.effect = UIBlurEffect(style: .systemChromeMaterial)
|
||||||
|
@ -68,7 +72,9 @@ class ToastVC: ListVC {
|
||||||
TestToast(.title(title).message(message))
|
TestToast(.title(title).message(message))
|
||||||
}
|
}
|
||||||
section.add(title: "一段长文本") {
|
section.add(title: "一段长文本") {
|
||||||
Toast(.message(message))
|
// Toast(.message(message))
|
||||||
|
// 可以简写成这样:
|
||||||
|
Toast(message)
|
||||||
}
|
}
|
||||||
section.add(title: "图标 + 标题 + 正文") {
|
section.add(title: "图标 + 标题 + 正文") {
|
||||||
let s1 = "笑容正在加载"
|
let s1 = "笑容正在加载"
|
||||||
|
@ -80,8 +86,10 @@ class ToastVC: ListVC {
|
||||||
toast.update(progress: percent)
|
toast.update(progress: percent)
|
||||||
} completion: {
|
} completion: {
|
||||||
toast.update { toast in
|
toast.update { toast in
|
||||||
toast.vm = .success(5).title("加载成功").message("这条通知5s后消失")
|
toast.vm = .success(5)
|
||||||
toast.vm.icon = UIImage(named: "twemoji")
|
.title("加载成功")
|
||||||
|
.message("这条通知5s后消失")
|
||||||
|
.icon(.init(named: "twemoji"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,16 +127,15 @@ class ToastVC: ListVC {
|
||||||
section.add(title: "增加按钮") {
|
section.add(title: "增加按钮") {
|
||||||
let title = "您收到了一条好友申请"
|
let title = "您收到了一条好友申请"
|
||||||
let message = "丹妮莉丝·坦格利安申请添加您为好友,是否同意?"
|
let message = "丹妮莉丝·坦格利安申请添加您为好友,是否同意?"
|
||||||
Toast(.title(title).message(message)) { toast in
|
Toast(.title(title).message(message).icon(.init(named: "avatar"))) { toast in
|
||||||
toast.isRemovable = false
|
toast.isRemovable = false
|
||||||
toast.vm.icon = UIImage(named: "avatar")
|
|
||||||
toast.imageView.layer.masksToBounds = true
|
toast.imageView.layer.masksToBounds = true
|
||||||
toast.imageView.layer.cornerRadius = toast.config.iconSize.width / 2
|
toast.imageView.layer.cornerRadius = toast.config.iconSize.width / 2
|
||||||
toast.add(action: "拒绝", style: .destructive) { toast in
|
toast.add(action: "拒绝", style: .destructive) { toast in
|
||||||
Alert.lazyPush(identifier: "Dracarys") { alert in
|
Alert.lazyPush(identifier: "Dracarys") { alert in
|
||||||
alert.vm = .message("Dracarys")
|
alert.vm = .message("Dracarys")
|
||||||
alert.vm.icon = UIImage(inProHUD: "prohud.windmill")
|
.icon(UIImage(inProHUD: "prohud.windmill"))
|
||||||
alert.vm.rotation = .init(repeatCount: .infinity)
|
.rotation(.init(repeatCount: .infinity))
|
||||||
alert.config.enableShadow = false
|
alert.config.enableShadow = false
|
||||||
alert.config.contentViewMask { mask in
|
alert.config.contentViewMask { mask in
|
||||||
mask.effect = .none
|
mask.effect = .none
|
||||||
|
@ -217,7 +224,7 @@ class ToastVC: ListVC {
|
||||||
}
|
}
|
||||||
section.add(title: "修改左右外边距") {
|
section.add(title: "修改左右外边距") {
|
||||||
Toast(.message("这条toast的左右外边距经过自定义设置,与其它的有所不同。")) { toast in
|
Toast(.message("这条toast的左右外边距经过自定义设置,与其它的有所不同。")) { toast in
|
||||||
toast.config.windowEdgeInset = 8
|
toast.config.marginX = 32
|
||||||
toast.config.cardCornerRadius = 24
|
toast.config.cardCornerRadius = 24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,7 +313,7 @@ class ToastVC: ListVC {
|
||||||
|
|
||||||
section.add(title: "卡片背景样式") {
|
section.add(title: "卡片背景样式") {
|
||||||
Toast { toast in
|
Toast { toast in
|
||||||
toast.vm.title = "卡片背景样式"
|
toast.title = "卡片背景样式"
|
||||||
toast.add(action: "浅色毛玻璃") { toast in
|
toast.add(action: "浅色毛玻璃") { toast in
|
||||||
toast.contentMaskView.effect = UIBlurEffect(style: .light)
|
toast.contentMaskView.effect = UIBlurEffect(style: .light)
|
||||||
toast.contentMaskView.backgroundColor = .clear
|
toast.contentMaskView.backgroundColor = .clear
|
||||||
|
@ -327,7 +334,7 @@ class ToastVC: ListVC {
|
||||||
func foo() {
|
func foo() {
|
||||||
Toast { toast in
|
Toast { toast in
|
||||||
toast.title = "共享配置"
|
toast.title = "共享配置"
|
||||||
toast.vm.message = "建议在App启动后进行通用配置设置,所有实例都会先拉取通用配置为默认值,修改这些配置会影响到所有实例。"
|
toast.vm?.message = "建议在App启动后进行通用配置设置,所有实例都会先拉取通用配置为默认值,修改这些配置会影响到所有实例。"
|
||||||
toast.add(action: "默认", style: .gray) { toast in
|
toast.add(action: "默认", style: .gray) { toast in
|
||||||
ToastConfiguration.global { config in
|
ToastConfiguration.global { config in
|
||||||
config.customTitleLabel { titleLabel in
|
config.customTitleLabel { titleLabel in
|
||||||
|
@ -359,7 +366,7 @@ class ToastVC: ListVC {
|
||||||
|
|
||||||
fileprivate func testAlert() {
|
fileprivate func testAlert() {
|
||||||
Alert { alert in
|
Alert { alert in
|
||||||
alert.vm.title = "处理点击事件"
|
alert.title = "处理点击事件"
|
||||||
alert.add(action: "我知道了", style: .destructive)
|
alert.add(action: "我知道了", style: .destructive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ extension AlertTarget: DefaultLayout {
|
||||||
if self.cfg.customReloadData?(self) == true {
|
if self.cfg.customReloadData?(self) == true {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view.tintColor = vm.tintColor ?? config.tintColor
|
view.tintColor = vm?.tintColor ?? config.tintColor
|
||||||
let isFirstLayout: Bool
|
let isFirstLayout: Bool
|
||||||
if contentView.superview == nil {
|
if contentView.superview == nil {
|
||||||
isFirstLayout = animated
|
isFirstLayout = animated
|
||||||
|
@ -126,7 +126,7 @@ extension AlertTarget: DefaultLayout {
|
||||||
|
|
||||||
func updateTimeoutDuration() {
|
func updateTimeoutDuration() {
|
||||||
// 设置持续时间
|
// 设置持续时间
|
||||||
vm.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||||
self?.pop()
|
self?.pop()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -144,9 +144,9 @@ extension AlertTarget {
|
||||||
// 移除进度
|
// 移除进度
|
||||||
progressView?.removeFromSuperview()
|
progressView?.removeFromSuperview()
|
||||||
|
|
||||||
if vm.icon != nil || vm.iconURL != nil {
|
if vm?.icon != nil || vm?.iconURL != nil {
|
||||||
imageView.image = vm.icon
|
imageView.image = vm?.icon
|
||||||
if let iconURL = vm.iconURL {
|
if let iconURL = vm?.iconURL {
|
||||||
config.customWebImage?(imageView, iconURL)
|
config.customWebImage?(imageView, iconURL)
|
||||||
}
|
}
|
||||||
if imageView.superview == nil {
|
if imageView.superview == nil {
|
||||||
|
@ -159,7 +159,7 @@ extension AlertTarget {
|
||||||
mk.height.equalTo(config.iconSize.height)
|
mk.height.equalTo(config.iconSize.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let rotation = vm.rotation {
|
if let rotation = vm?.rotation {
|
||||||
startRotate(rotation)
|
startRotate(rotation)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -171,8 +171,8 @@ extension AlertTarget {
|
||||||
|
|
||||||
}
|
}
|
||||||
func setupTextStack() {
|
func setupTextStack() {
|
||||||
let titleCount = vm.title?.count ?? 0
|
let titleCount = vm?.title?.count ?? 0
|
||||||
let bodyCount = vm.message?.count ?? 0
|
let bodyCount = vm?.message?.count ?? 0
|
||||||
if titleCount > 0 || bodyCount > 0 {
|
if titleCount > 0 || bodyCount > 0 {
|
||||||
if textStack.superview != contentStack {
|
if textStack.superview != contentStack {
|
||||||
if let index = contentStack.arrangedSubviews.firstIndex(of: imageView) {
|
if let index = contentStack.arrangedSubviews.firstIndex(of: imageView) {
|
||||||
|
@ -189,7 +189,7 @@ extension AlertTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if titleCount > 0 {
|
if titleCount > 0 {
|
||||||
titleLabel.text = vm.title
|
titleLabel.text = vm?.title
|
||||||
if titleLabel.superview != textStack {
|
if titleLabel.superview != textStack {
|
||||||
textStack.insertArrangedSubview(titleLabel, at: 0)
|
textStack.insertArrangedSubview(titleLabel, at: 0)
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ extension AlertTarget {
|
||||||
titleLabel.removeFromSuperview()
|
titleLabel.removeFromSuperview()
|
||||||
}
|
}
|
||||||
if bodyCount > 0 {
|
if bodyCount > 0 {
|
||||||
bodyLabel.text = vm.message
|
bodyLabel.text = vm?.message
|
||||||
if bodyLabel.superview != textStack {
|
if bodyLabel.superview != textStack {
|
||||||
textStack.addArrangedSubview(bodyLabel)
|
textStack.addArrangedSubview(bodyLabel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,30 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
open class AlertProvider: HUDProvider<AlertViewModel, AlertTarget> {
|
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)?) {
|
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ alert: Target) -> Void)?) {
|
||||||
self.init(nil, initializer: initializer)
|
self.init { alert in
|
||||||
|
alert.vm = vm
|
||||||
|
initializer?(alert)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
/// 根据ViewModel创建一个Target并显示
|
||||||
|
/// - 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实例,如果存在就更新实例
|
/// 如果不存在就创建并弹出一个HUD实例,如果存在就更新实例
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
|
|
|
@ -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? {
|
public override var title: String? {
|
||||||
didSet {
|
didSet {
|
||||||
|
if let vm = vm {
|
||||||
vm.title = title
|
vm.title = title
|
||||||
|
} else {
|
||||||
|
vm = .title(title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ public class CapsuleConfiguration: CommonConfiguration {
|
||||||
customGlobalConfig = callback
|
customGlobalConfig = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 默认的持续时间
|
||||||
|
public var defaultDuration: TimeInterval = 3
|
||||||
|
|
||||||
override var cardCornerRadiusByDefault: CGFloat {
|
override var cardCornerRadiusByDefault: CGFloat {
|
||||||
cardCornerRadius ?? 16
|
cardCornerRadius ?? 16
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ extension CapsuleTarget: DefaultLayout {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
view.tintColor = vm.tintColor ?? config.tintColor
|
view.tintColor = vm?.tintColor ?? config.tintColor
|
||||||
|
|
||||||
// content
|
// content
|
||||||
loadContentViewIfNeeded()
|
loadContentViewIfNeeded()
|
||||||
|
@ -29,7 +29,7 @@ extension CapsuleTarget: DefaultLayout {
|
||||||
|
|
||||||
// text
|
// text
|
||||||
textLabel.removeFromSuperview()
|
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 {
|
if text.count > 0 {
|
||||||
contentStack.addArrangedSubview(textLabel)
|
contentStack.addArrangedSubview(textLabel)
|
||||||
textLabel.snp.makeConstraints { make in
|
textLabel.snp.makeConstraints { make in
|
||||||
|
@ -81,11 +81,11 @@ extension CapsuleTarget: DefaultLayout {
|
||||||
|
|
||||||
private func updateTimeoutDuration() {
|
private func updateTimeoutDuration() {
|
||||||
// 为空时使用默认规则
|
// 为空时使用默认规则
|
||||||
if vm.duration == nil {
|
if vm?.duration == nil {
|
||||||
vm.duration = 3
|
vm?.duration = config.defaultDuration
|
||||||
}
|
}
|
||||||
// 设置持续时间
|
// 设置持续时间
|
||||||
vm.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||||
self?.pop()
|
self?.pop()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -99,16 +99,16 @@ extension CapsuleTarget: DefaultLayout {
|
||||||
// 移除进度
|
// 移除进度
|
||||||
progressView?.removeFromSuperview()
|
progressView?.removeFromSuperview()
|
||||||
|
|
||||||
if vm.icon == nil && vm.iconURL == nil {
|
if vm?.icon == nil && vm?.iconURL == nil {
|
||||||
contentStack.removeArrangedSubview(imageView)
|
contentStack.removeArrangedSubview(imageView)
|
||||||
} else {
|
} else {
|
||||||
contentStack.insertArrangedSubview(imageView, at: 0)
|
contentStack.insertArrangedSubview(imageView, at: 0)
|
||||||
}
|
}
|
||||||
imageView.image = vm.icon
|
imageView.image = vm?.icon
|
||||||
if let iconURL = vm.iconURL {
|
if let iconURL = vm?.iconURL {
|
||||||
config.customWebImage?(imageView, iconURL)
|
config.customWebImage?(imageView, iconURL)
|
||||||
}
|
}
|
||||||
if let rotation = vm.rotation {
|
if let rotation = vm?.rotation {
|
||||||
startRotate(rotation)
|
startRotate(rotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ extension CapsuleTarget {
|
||||||
guard CapsuleConfiguration.isEnabled else { return }
|
guard CapsuleConfiguration.isEnabled else { return }
|
||||||
let isNew: Bool
|
let isNew: Bool
|
||||||
let window: CapsuleWindow
|
let window: CapsuleWindow
|
||||||
let position = vm.position
|
let position = vm?.position ?? .top
|
||||||
|
|
||||||
if let w = AppContext.current?.capsuleWindows[position] {
|
if let w = AppContext.current?.capsuleWindows[position] {
|
||||||
isNew = false
|
isNew = false
|
||||||
|
@ -30,10 +30,10 @@ extension CapsuleTarget {
|
||||||
|
|
||||||
// 应用到frame
|
// 应用到frame
|
||||||
let newFrame: CGRect
|
let newFrame: CGRect
|
||||||
switch vm.position {
|
switch vm?.position {
|
||||||
case .top:
|
case .top, .none:
|
||||||
let topLayoutMargins = AppContext.appWindow?.layoutMargins.top ?? 8
|
let topLayoutMargins = AppContext.appWindow?.safeAreaInsets.top ?? 8
|
||||||
let y = max(topLayoutMargins - 8, 8)
|
let y = max(topLayoutMargins, 8)
|
||||||
newFrame = .init(x: (AppContext.appBounds.width - size.width) / 2, y: y, width: size.width, height: size.height)
|
newFrame = .init(x: (AppContext.appBounds.width - size.width) / 2, y: y, width: size.width, height: size.height)
|
||||||
case .middle:
|
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)
|
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
|
AppContext.capsuleWindows[s]?[position] = window
|
||||||
}
|
}
|
||||||
navEvents[.onViewWillAppear]?(self)
|
navEvents[.onViewWillAppear]?(self)
|
||||||
|
|
||||||
|
// 更新toast防止重叠
|
||||||
|
ToastWindow.updateToastWindowsLayout()
|
||||||
|
|
||||||
if isNew {
|
if isNew {
|
||||||
window.isHidden = false
|
window.isHidden = false
|
||||||
func completion() {
|
func completion() {
|
||||||
|
@ -116,8 +120,11 @@ extension CapsuleTarget {
|
||||||
|
|
||||||
@objc open func pop() {
|
@objc open func pop() {
|
||||||
guard let window = attachedWindow, let windowScene = windowScene else { return }
|
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)
|
navEvents[.onViewWillDisappear]?(self)
|
||||||
|
// 更新toast防止重叠
|
||||||
|
ToastWindow.updateToastWindowsLayout()
|
||||||
|
|
||||||
func completion() {
|
func completion() {
|
||||||
window.isHidden = true
|
window.isHidden = true
|
||||||
window.transform = .identity
|
window.transform = .identity
|
||||||
|
@ -128,8 +135,8 @@ extension CapsuleTarget {
|
||||||
} else {
|
} else {
|
||||||
let duration = config.animateDurationForBuildOutByDefault
|
let duration = config.animateDurationForBuildOutByDefault
|
||||||
let oldFrame = window.frame
|
let oldFrame = window.frame
|
||||||
switch vm.position {
|
switch vm?.position {
|
||||||
case .top:
|
case .top, .none:
|
||||||
UIView.animateEaseOut(duration: duration) {
|
UIView.animateEaseOut(duration: duration) {
|
||||||
window.transform = .init(translationX: 0, y: -oldFrame.maxY - 20)
|
window.transform = .init(translationX: 0, y: -oldFrame.maxY - 20)
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
|
|
|
@ -7,20 +7,37 @@
|
||||||
|
|
||||||
import UIKit
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - vm: 数据模型
|
/// - vm: 数据模型
|
||||||
/// - initializer: 初始化代码
|
/// - initializer: 初始化代码
|
||||||
@discardableResult public required init(_ vm: ViewModel?, initializer: ((_ capsule: Target) -> Void)?) {
|
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ capsule: Target) -> Void)?) {
|
||||||
super.init(vm, initializer: initializer)
|
self.init { capsule in
|
||||||
|
capsule.vm = vm
|
||||||
|
initializer?(capsule)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult public required convenience init(initializer: ((_ capsule: Target) -> Void)?) {
|
/// 根据ViewModel创建一个Target并显示
|
||||||
self.init(nil, initializer: initializer)
|
/// - 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实例,如果存在就更新实例
|
/// 如果不存在就创建并弹出一个HUD实例,如果存在就更新实例
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - identifier: 实例唯一标识符(如果为空,则以代码位置为唯一标识符)
|
/// - identifier: 实例唯一标识符(如果为空,则以代码位置为唯一标识符)
|
||||||
|
|
|
@ -46,7 +46,17 @@ open class CapsuleTarget: BaseController, HUDTargetType {
|
||||||
return lb
|
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)?
|
private var tapActionCallback: ((_ capsule: CapsuleTarget) -> Void)?
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ class CapsuleWindow: Window {
|
||||||
self.capsule = capsule
|
self.capsule = capsule
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
windowScene = AppContext.windowScene
|
windowScene = AppContext.windowScene
|
||||||
switch capsule.vm.position {
|
switch capsule.vm?.position {
|
||||||
case .top:
|
case .top, .none:
|
||||||
// 略高于toast
|
// 略高于toast
|
||||||
windowLevel = .phCapsuleTop
|
windowLevel = .phCapsuleTop
|
||||||
case .middle:
|
case .middle:
|
||||||
|
|
|
@ -12,11 +12,9 @@ public protocol HUDProviderType {
|
||||||
associatedtype ViewModel = HUDViewModelType
|
associatedtype ViewModel = HUDViewModelType
|
||||||
associatedtype Target = HUDTargetType
|
associatedtype Target = HUDTargetType
|
||||||
|
|
||||||
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
/// 根据自定义的初始化代码创建一个Target并显示
|
||||||
/// - Parameters:
|
/// - Parameter initializer: 初始化代码
|
||||||
/// - vm: 数据模型
|
@discardableResult init(initializer: ((_ target: Target) -> Void)?)
|
||||||
/// - initializer: 初始化代码
|
|
||||||
@discardableResult init(_ vm: ViewModel?, initializer: ((_ target: Target) -> Void)?)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,39 +23,22 @@ open class HUDProvider<ViewModel: HUDViewModelType, Target: HUDTargetType>: HUDP
|
||||||
/// HUD实例
|
/// HUD实例
|
||||||
public var target: Target
|
public var target: Target
|
||||||
|
|
||||||
/// 根据ViewModel和自定义的初始化代码创建一个Target并显示
|
/// 根据自定义的初始化代码创建一个Target并显示
|
||||||
/// - Parameters:
|
/// - Parameter initializer: 初始化代码
|
||||||
/// - vm: 数据模型
|
@discardableResult public required init(initializer: ((_ target: Target) -> Void)?) {
|
||||||
/// - initializer: 初始化代码
|
|
||||||
@discardableResult public required init(_ vm: ViewModel?, initializer: ((_ target: Target) -> Void)?) {
|
|
||||||
var t = Target()
|
var t = Target()
|
||||||
if let vm = vm as? Target.ViewModel {
|
|
||||||
t.vm = vm
|
|
||||||
}
|
|
||||||
initializer?(t)
|
initializer?(t)
|
||||||
self.target = t
|
self.target = t
|
||||||
if (vm == nil && initializer == nil) == false {
|
if (t.vm == nil && initializer == nil) == false {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
t.push()
|
t.push()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据自定义的初始化代码创建一个Target并显示
|
|
||||||
/// - Parameter initializer: 初始化代码
|
|
||||||
@discardableResult public convenience init(initializer: ((_ target: Target) -> Void)?) {
|
|
||||||
self.init(nil, initializer: initializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 根据ViewModel创建一个Target并显示
|
|
||||||
/// - Parameter vm: 数据模型
|
|
||||||
@discardableResult public convenience init(_ vm: ViewModel?) {
|
|
||||||
self.init(vm, initializer: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 创建一个空白的实例,不立即显示,需要手动调用target.push()来显示
|
/// 创建一个空白的实例,不立即显示,需要手动调用target.push()来显示
|
||||||
@discardableResult public convenience init() {
|
@discardableResult public convenience init() {
|
||||||
self.init(nil, initializer: nil)
|
self.init(initializer: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,6 @@ import UIKit
|
||||||
|
|
||||||
public protocol HUDTargetType: HUDControllerType {
|
public protocol HUDTargetType: HUDControllerType {
|
||||||
associatedtype ViewModel = HUDViewModelType
|
associatedtype ViewModel = HUDViewModelType
|
||||||
var vm: ViewModel { get set }
|
var vm: ViewModel? { get set }
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ public struct AppContext {
|
||||||
static var toastWindows: [UIWindowScene: [ToastWindow]] = [:]
|
static var toastWindows: [UIWindowScene: [ToastWindow]] = [:]
|
||||||
static var alertWindow: [UIWindowScene: AlertWindow] = [:]
|
static var alertWindow: [UIWindowScene: AlertWindow] = [:]
|
||||||
static var sheetWindows: [UIWindowScene: [SheetWindow]] = [:]
|
static var sheetWindows: [UIWindowScene: [SheetWindow]] = [:]
|
||||||
static var capsuleWindows: [UIWindowScene: [CapsuleTarget.ViewModel.Position: CapsuleWindow]] = [:]
|
static var capsuleWindows: [UIWindowScene: [CapsuleViewModel.Position: CapsuleWindow]] = [:]
|
||||||
|
|
||||||
static var current: AppContext? {
|
static var current: AppContext? {
|
||||||
guard let windowScene = windowScene else { return nil }
|
guard let windowScene = windowScene else { return nil }
|
||||||
|
@ -120,7 +120,7 @@ extension AppContext {
|
||||||
var toastWindows: [ToastWindow] {
|
var toastWindows: [ToastWindow] {
|
||||||
Self.toastWindows[windowScene] ?? []
|
Self.toastWindows[windowScene] ?? []
|
||||||
}
|
}
|
||||||
var capsuleWindows: [CapsuleTarget.ViewModel.Position: CapsuleWindow] {
|
var capsuleWindows: [CapsuleViewModel.Position: CapsuleWindow] {
|
||||||
Self.capsuleWindows[windowScene] ?? [:]
|
Self.capsuleWindows[windowScene] ?? [:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ extension SheetTarget: DefaultLayout {
|
||||||
if self.cfg.customReloadData?(self) == true {
|
if self.cfg.customReloadData?(self) == true {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view.tintColor = vm.tintColor ?? config.tintColor
|
view.tintColor = vm?.tintColor ?? config.tintColor
|
||||||
// background
|
// background
|
||||||
if backgroundView.superview == nil {
|
if backgroundView.superview == nil {
|
||||||
view.insertSubview(backgroundView, at: 0)
|
view.insertSubview(backgroundView, at: 0)
|
||||||
|
|
|
@ -8,12 +8,12 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
open class SheetProvider: HUDProvider<SheetViewModel, SheetTarget> {
|
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)?) {
|
public typealias ViewModel = SheetViewModel
|
||||||
self.init(nil, initializer: initializer)
|
public typealias Target = SheetTarget
|
||||||
|
|
||||||
|
@discardableResult @objc public required init(initializer: ((_ sheet: Target) -> Void)?) {
|
||||||
|
super.init(initializer: initializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 如果不存在就创建并弹出一个HUD实例,如果存在就更新实例
|
/// 如果不存在就创建并弹出一个HUD实例,如果存在就更新实例
|
||||||
|
|
|
@ -41,7 +41,7 @@ open class SheetTarget: BaseController, HUDTargetType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var vm: SheetViewModel = .init()
|
public var vm: SheetViewModel? = nil
|
||||||
|
|
||||||
required public override init() {
|
required public override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
|
@ -9,16 +9,6 @@ import UIKit
|
||||||
|
|
||||||
public class ToastConfiguration: CommonConfiguration {
|
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)?
|
static var customGlobalConfig: ((_ config: ToastConfiguration) -> Void)?
|
||||||
|
|
||||||
/// 全局共享配置(只能设置一次,影响所有实例)
|
/// 全局共享配置(只能设置一次,影响所有实例)
|
||||||
|
@ -27,11 +17,21 @@ public class ToastConfiguration: CommonConfiguration {
|
||||||
customGlobalConfig = callback
|
customGlobalConfig = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 距离窗口左右的间距
|
/// 默认的持续时间
|
||||||
public var windowEdgeInset: CGFloat?
|
public var defaultDuration: TimeInterval = 10
|
||||||
var windowEdgeInsetByDefault: CGFloat {
|
|
||||||
windowEdgeInset ?? 16
|
/// 元素与左右屏幕之间的距离(在没有达到最大宽度的情况下)
|
||||||
|
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 {
|
override var cardMaxWidthByDefault: CGFloat {
|
||||||
cardMaxWidth ?? 500
|
cardMaxWidth ?? 500
|
||||||
|
|
|
@ -17,7 +17,7 @@ extension ToastTarget: DefaultLayout {
|
||||||
if self.cfg.customReloadData?(self) == true {
|
if self.cfg.customReloadData?(self) == true {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view.tintColor = vm.tintColor ?? config.tintColor
|
view.tintColor = vm?.tintColor ?? config.tintColor
|
||||||
loadContentViewIfNeeded()
|
loadContentViewIfNeeded()
|
||||||
loadContentMaskViewIfNeeded()
|
loadContentMaskViewIfNeeded()
|
||||||
guard customView == nil else {
|
guard customView == nil else {
|
||||||
|
@ -26,7 +26,7 @@ extension ToastTarget: DefaultLayout {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if vm.icon != nil || vm.iconURL != nil {
|
if vm?.icon != nil || vm?.iconURL != nil {
|
||||||
if imageView.superview == nil {
|
if imageView.superview == nil {
|
||||||
infoStack.insertArrangedSubview(imageView, at: 0)
|
infoStack.insertArrangedSubview(imageView, at: 0)
|
||||||
imageView.snp.makeConstraints { make in
|
imageView.snp.makeConstraints { make in
|
||||||
|
@ -42,8 +42,8 @@ extension ToastTarget: DefaultLayout {
|
||||||
if textStack.superview == nil {
|
if textStack.superview == nil {
|
||||||
infoStack.addArrangedSubview(textStack)
|
infoStack.addArrangedSubview(textStack)
|
||||||
}
|
}
|
||||||
let titleCount = vm.title?.count ?? 0
|
let titleCount = vm?.title?.count ?? 0
|
||||||
let bodyCount = vm.message?.count ?? 0
|
let bodyCount = vm?.message?.count ?? 0
|
||||||
if titleCount > 0 {
|
if titleCount > 0 {
|
||||||
textStack.insertArrangedSubview(titleLabel, at: 0)
|
textStack.insertArrangedSubview(titleLabel, at: 0)
|
||||||
if bodyCount > 0 {
|
if bodyCount > 0 {
|
||||||
|
@ -79,14 +79,12 @@ extension ToastTarget: DefaultLayout {
|
||||||
bodyLabel.removeFromSuperview()
|
bodyLabel.removeFromSuperview()
|
||||||
}
|
}
|
||||||
// 设置数据
|
// 设置数据
|
||||||
titleLabel.text = vm.title
|
titleLabel.text = vm?.title
|
||||||
bodyLabel.text = vm.message
|
bodyLabel.text = vm?.message
|
||||||
view.layoutIfNeeded()
|
view.layoutIfNeeded()
|
||||||
|
|
||||||
// 设置持续时间
|
// 设置持续时间
|
||||||
vm.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
updateTimeoutDuration()
|
||||||
self?.pop()
|
|
||||||
})
|
|
||||||
|
|
||||||
setupImageView()
|
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() {
|
func setupImageView() {
|
||||||
// 移除动画
|
// 移除动画
|
||||||
stopRotate(animateLayer)
|
stopRotate(animateLayer)
|
||||||
|
@ -141,11 +150,11 @@ extension ToastTarget {
|
||||||
// 移除进度
|
// 移除进度
|
||||||
progressView?.removeFromSuperview()
|
progressView?.removeFromSuperview()
|
||||||
|
|
||||||
imageView.image = vm.icon
|
imageView.image = vm?.icon
|
||||||
if let iconURL = vm.iconURL {
|
if let iconURL = vm?.iconURL {
|
||||||
config.customWebImage?(imageView, iconURL)
|
config.customWebImage?(imageView, iconURL)
|
||||||
}
|
}
|
||||||
if let rotation = vm.rotation {
|
if let rotation = vm?.rotation {
|
||||||
startRotate(rotation)
|
startRotate(rotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ extension ToastTarget {
|
||||||
|
|
||||||
// frame
|
// frame
|
||||||
let cardEdgeInsets = config.cardEdgeInsetsByDefault
|
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)
|
view.frame.size = CGSize(width: width, height: config.cardMaxHeightByDefault)
|
||||||
titleLabel.sizeToFit()
|
titleLabel.sizeToFit()
|
||||||
bodyLabel.sizeToFit()
|
bodyLabel.sizeToFit()
|
||||||
|
@ -92,7 +92,7 @@ extension ToastTarget {
|
||||||
} else {
|
} else {
|
||||||
consolePrint("‼️代码漏洞:已经没有toast了")
|
consolePrint("‼️代码漏洞:已经没有toast了")
|
||||||
}
|
}
|
||||||
vm.duration = nil
|
vm?.duration = nil
|
||||||
setContextWindows(windows)
|
setContextWindows(windows)
|
||||||
UIView.animateEaseOut(duration: config.animateDurationForBuildOutByDefault) {
|
UIView.animateEaseOut(duration: config.animateDurationForBuildOutByDefault) {
|
||||||
window.transform = .init(translationX: 0, y: 0-20-window.maxY)
|
window.transform = .init(translationX: 0, y: 0-20-window.maxY)
|
||||||
|
@ -124,21 +124,34 @@ fileprivate var updateToastsLayoutWorkItem: DispatchWorkItem?
|
||||||
fileprivate extension ToastWindow {
|
fileprivate extension ToastWindow {
|
||||||
|
|
||||||
static func setToastWindowsLayout(windows: [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() {
|
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
|
var y = window.frame.origin.y
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
let topLayoutMargins = AppContext.appWindow?.layoutMargins.top ?? config.margin
|
let topLayoutMargins = AppContext.appWindow?.safeAreaInsets.top ?? margin
|
||||||
y = max(topLayoutMargins - config.margin, config.margin)
|
y = max(topLayoutMargins, margin)
|
||||||
} else {
|
} else {
|
||||||
if i - 1 < windows.count && i > 0 {
|
if i - 1 < windows.count && i > 0 {
|
||||||
y = config.margin + windows[i-1].frame.maxY
|
y = margin + windows[i-1].frame.maxY
|
||||||
} else {
|
} else {
|
||||||
y = config.margin
|
y = margin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let window = window as? ToastWindow {
|
||||||
window.maxY = y + window.frame.size.height
|
window.maxY = y + window.frame.size.height
|
||||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
}
|
||||||
|
UIView.animateEaseOut(duration: 0.68) {
|
||||||
window.frame.origin.y = y
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,19 +8,37 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> {
|
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)?) {
|
@discardableResult public convenience init(_ vm: ViewModel, initializer: ((_ toast: Target) -> Void)?) {
|
||||||
self.init(nil, initializer: initializer)
|
self.init { toast in
|
||||||
|
toast.vm = vm
|
||||||
|
initializer?(toast)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据ViewModel创建一个Target并显示
|
||||||
|
/// - 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实例,如果存在就更新实例
|
/// 如果不存在就创建并弹出一个HUD实例,如果存在就更新实例
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - identifier: 实例唯一标识符(如果为空,则以代码位置为唯一标识符)
|
/// - identifier: 实例唯一标识符(如果为空,则以代码位置为唯一标识符)
|
||||||
/// - handler: 实例创建代码
|
/// - 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)")
|
let id = identifier ?? (file + "#\(line)")
|
||||||
if let vc = find(identifier: id).last {
|
if let vc = find(identifier: id).last {
|
||||||
vc.update(handler: onExists ?? handler)
|
vc.update(handler: onExists ?? handler)
|
||||||
|
@ -35,7 +53,7 @@ open class ToastProvider: HUDProvider<ToastViewModel, ToastTarget> {
|
||||||
/// 查找HUD实例
|
/// 查找HUD实例
|
||||||
/// - Parameter identifier: 唯一标识符
|
/// - Parameter identifier: 唯一标识符
|
||||||
/// - Returns: HUD实例
|
/// - 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 })
|
let arr = AppContext.toastWindows.values.flatMap({ $0 }).compactMap({ $0.toast }).filter({ $0.identifier == identifier })
|
||||||
if let handler = handler {
|
if let handler = handler {
|
||||||
arr.forEach({ $0.update(handler: handler) })
|
arr.forEach({ $0.update(handler: handler) })
|
||||||
|
|
|
@ -84,14 +84,17 @@ open class ToastTarget: BaseController, HUDTargetType {
|
||||||
public var isRemovable = true
|
public var isRemovable = true
|
||||||
|
|
||||||
/// 视图模型
|
/// 视图模型
|
||||||
@objc public var vm = ToastViewModel()
|
@objc public var vm: ToastViewModel?
|
||||||
|
|
||||||
private var tapActionCallback: ((_ toast: ToastTarget) -> Void)?
|
private var tapActionCallback: ((_ toast: ToastTarget) -> Void)?
|
||||||
|
|
||||||
|
|
||||||
public override var title: String? {
|
public override var title: String? {
|
||||||
didSet {
|
didSet {
|
||||||
|
if let vm = vm {
|
||||||
vm.title = title
|
vm.title = title
|
||||||
|
} else {
|
||||||
|
vm = .title(title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +138,7 @@ fileprivate extension ToastTarget {
|
||||||
/// 拖拽事件
|
/// 拖拽事件
|
||||||
/// - Parameter sender: 手势
|
/// - Parameter sender: 手势
|
||||||
@objc func _onPanGesture(_ sender: UIPanGestureRecognizer) {
|
@objc func _onPanGesture(_ sender: UIPanGestureRecognizer) {
|
||||||
vm.timeoutTimer?.invalidate()
|
vm?.timeoutTimer?.invalidate()
|
||||||
let point = sender.translation(in: sender.view)
|
let point = sender.translation(in: sender.view)
|
||||||
window?.transform = .init(translationX: 0, y: point.y)
|
window?.transform = .init(translationX: 0, y: point.y)
|
||||||
if sender.state == .recognized {
|
if sender.state == .recognized {
|
||||||
|
@ -150,8 +153,8 @@ fileprivate extension ToastTarget {
|
||||||
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
UIView.animateEaseOut(duration: config.animateDurationForReloadByDefault) {
|
||||||
self.window?.transform = .identity
|
self.window?.transform = .identity
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
let d = self.vm.duration
|
let d = self.vm?.duration
|
||||||
self.vm.duration = d
|
self.vm?.duration = d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue