update readme

This commit is contained in:
xaoxuu 2022-09-12 19:00:54 +08:00
parent ebfc53502e
commit 410b25955f
5 changed files with 282 additions and 267 deletions

View File

@ -35,7 +35,7 @@ class AlertVC: ListVC {
let title = "这是标题" let title = "这是标题"
let message = "这是正文文字支持自动换行可设置最小宽度和最大宽度。这个弹窗将会持续4秒。" let message = "这是正文文字支持自动换行可设置最小宽度和最大宽度。这个弹窗将会持续4秒。"
Alert { alert in Alert { alert in
alert.vm = .text(title: title, message: message) alert.vm = .title(title).message(message)
alert.vm.duration = 4 alert.vm.duration = 4
} }
} }

View File

@ -66,7 +66,7 @@ class SheetVC: ListVC {
} }
list.add(title: "事件管理") { section in list.add(title: "事件管理") { section in
section.add(title: "拦截点击背景事件") { section.add(title: "拦截背景点击事件") {
Sheet { sheet in Sheet { sheet in
sheet.add(title: "ProHUD") sheet.add(title: "ProHUD")
sheet.add(message: "点击背景将不会dismiss必须在下方做出选择才能关掉") sheet.add(message: "点击背景将不会dismiss必须在下方做出选择才能关掉")

View File

@ -25,7 +25,7 @@ class ToastVC: ListVC {
} }
} }
let vm: ViewModel = .loading
list.add(title: "默认布局") { section in list.add(title: "默认布局") { section in
section.add(title: "标题 + 正文") { section.add(title: "标题 + 正文") {
Toast(.title(title).message(message)).push() Toast(.title(title).message(message)).push()
@ -100,6 +100,9 @@ class ToastVC: ListVC {
toast.pop() toast.pop()
Alert(.success(1).message("Good choice!")).push() Alert(.success(1).message("Good choice!")).push()
} }
Toast.find(identifier: "loading") { toast in
toast.vm = .success(2).message("加载成功")
}
} }
} }

513
README.md
View File

@ -1,352 +1,371 @@
# ProHUD
<br> <br>
<img src="https://img.vim-cn.com/92/807ffd8bab40497971172514294020b6501074.png" height="80px"> **一个易于上手又完全可定制化的专业HUD库**内含Toast、Alert、Sheet三件套
**简单易用完全可定制化的HUD** ProHUD = Toast + Alert + ActionSheet
文档:<https://xaoxuu.com/wiki/prohud/> 文档:<https://xaoxuu.com/wiki/prohud/>
<br> <br>
| | | | | |
| ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------ |
| ![1.PNG](https://i.loli.net/2019/08/20/sgultOmRLXrwfA3.png) | ![2.PNG](https://i.loli.net/2019/08/20/a2mCq871PwfbZEG.png) | ![3.PNG](https://i.loli.net/2019/08/20/Zdz2cTphOlu3XKf.png) | ![4.PNG](https://i.loli.net/2019/08/20/87UdSGaMuevV1iF.png) | ![5.PNG](https://i.loli.net/2019/08/20/HEusSLBgG3XC1nN.png) |
| ![6.PNG](https://i.loli.net/2019/08/20/B178IvGZgbzjiuk.png) | ![7.PNG](https://i.loli.net/2019/08/20/YSNEX3fmdtiarjZ.png) | ![8.PNG](https://i.loli.net/2019/08/20/zlDXtWKfR3pLkji.png) | ![9.PNG](https://i.loli.net/2019/08/20/NEewmBV27fhW4yI.png) | ![10.PNG](https://i.loli.net/2019/08/20/XYvCIow2faRtn9P.png) |
| | | |
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| ![11.PNG](https://i.loli.net/2019/08/20/nHqKmNOEejgxbrf.png) | ![12.PNG](https://i.loli.net/2019/08/20/kScIodEnmbpaT5Y.png) | ![13.PNG](https://i.loli.net/2019/08/20/2RomGEC1KfSvIP9.png) |
## 特性 ## 特性
#### 使用简单 **易于上手**
- 用相似的接口调用**Toast**、**Alert**、**Guard**。 - 用极少的参数就可以创建并显示一个实例。
- 用相似的接口调用**Toast**、**Alert**、**Sheet**。
#### 功能丰富 **功能丰富**
- 用简便的方法拿到已发布的实例,避免重复发布实例 - 具有完善的实例管理(多实例共存方案、查找与更新方案)
- 可对已发布的实例进行数据更新。 - 可对已发布的实例进行数据更新。
- 横竖屏和iPad布局优化。 - 横竖屏和iPad布局优化。
- 可对所有实例设置监听事件。
- 对多实例并存堆叠的极端情况做了优化。
#### 完全可定制化 **完全可定制化**
- 字体、颜色、边距等可配置。 - 支持只使用ProHUD的容器而容器内容可完全自定义。
- 可扩展场景。 - 程序初始化时配置自定义UI样式调用的时候只需要关注数据。
- 程序初始化时配置自定义UI样式快速调用。 - 易于扩展,可以很方便的添加任意控件。
- 易于扩展,可以很方便的添加任意控件,并处理好布局。
### Toast顶部通知横幅
- 多个Toast并存策略平铺
- 只接收一个点击事件。
- 可以预先对不同的场景配置不同的默认值(图标、持续时间)。
## Toast顶部通知横幅
### Alert页面中心弹窗 通知条控件,用于非阻塞性事件通知。显示效果如同原生通知,默认会自动消失,可以支持手势移除,有多条通知可以平铺并列显示。
- 多个Alert并存策略具有景深效果的堆叠 ### 方式一传入ViewModel生成实例
- 可以预先对不同的场景配置不同的默认值(图标、持续时间)。
- 可快速创建具有预先配置的默认样式Default、Destructive、Cancel的按钮。
- 对已发布的实例进行文本和按钮的更新,包括新增、修改、删除文本和按钮。
- 强制退出按钮(防止超时导致页面卡死)。
这种方式创建的实例在调用`push()`之后才会显示出来,结构为:
### Guard底部操作表
- 快速创建具有预先配置的默认样式的文本元素(标题、副标题、正文)。
- 可快速创建具有预先配置的默认样式Default、Destructive、Cancel的按钮。
## 基本使用
以下示例中scene、title、message等参数都是非必填项如果不需要可以省略。
### Toast 横幅
默认提供的场景有:`default, loading, success, warning, error`。
示例1发布一个警告
```swift ```swift
Toast.push(scene: .warning, title: "设备电量过低", message: "请及时对设备进行充电,以免影响使用。") let 实例 = Toast(视图模型)
实例.push()
``` ```
示例2发布一个警告并设置其他属性 也可以连在一起写,例如:
```swift ```swift
Toast.push(scene: .warning, title: "设备电量过低", message: "请及时对设备进行充电,以免影响使用。") { (toast) in Toast(.message("要显示的消息内容")).push()
// 设置identifier ```
toast.identifier = "这是唯一标识"
// 禁止通过手势将其移出屏幕 #### 如何创建ViewModel
ViewModel有多种创建方式也可以自行扩展更多常用场景例如
```swift
// 纯文本
let vm = .message("要显示的消息内容")
// 持续2s的文本
let vm = .message("要显示的消息内容").duration(2)
// 标题 + 正文
let vm = .title("标题").message("正文")
```
内置了几种常见的场景扩展,例如正在加载的场景:
```swift
static var loading: ViewModel {
let obj = ViewModel(icon: UIImage(inProHUD: "prohud.windmill"))
obj.rotation = .init(repeatCount: .infinity)
return obj
}
static func loading(_ seconds: TimeInterval) -> ViewModel {
let obj = ViewModel(icon: UIImage(inProHUD: "prohud.windmill"), duration: seconds)
obj.rotation = .init(repeatCount: .infinity)
return obj
}
```
使用的时候可以:
```swift
// 无限持续时间
let vm = .loading
// 无限持续时间, 带有文字
let vm = .loading.message("正在加载")
// 持续10s
let vm = .loading(10)
// 持续10s, 带有文字
let vm = .loading(10).message("正在加载")
```
### 方式二:以闭包形式创建并显示实例
对于复杂实例,建议以这种方式使用,例如给实例增加事件响应:
```swift
let title = "您收到了一条消息"
let message = "点击通知横幅任意处即可回复"
Toast { toast in
toast.vm = .msg.title(title).message(message)
toast.onTapped { toast in
toast.pop()
Alert(.success(1).message("操作成功")).push()
}
}
```
也可以增加多个按钮,横向平铺,在这个例子中,左侧图标位置自定义为头像:
```swift
let title = "您收到了一条好友申请"
let message = "丹妮莉丝·坦格利安申请添加您为好友,是否同意?"
Toast(.title(title).message(message)) { toast in
toast.isRemovable = false toast.isRemovable = false
// 监听点击事件 toast.vm.icon = UIImage(named: "avatar")
toast.didTapped { toast.imageView.layer.masksToBounds = true
print("点击了这条横幅") toast.imageView.layer.cornerRadius = toast.config.iconSize.width / 2
toast.add(action: "拒绝", style: .destructive) { toast in
// 按钮点击事件回调
...
}
toast.add(action: "同意") { toast in
// 按钮点击事件回调
toast.pop()
Alert(.success(1).message("Good choice!")).push()
} }
} }
``` ```
### 如果存在就更新,不存在就创建新的实例
例如弹出一个loading有多个地方需要更新这个loading为了避免重复弹出多个实例可以使用 `lazyPush` 方法:
### Alert 弹窗
示例1发布一个Loading
```swift ```swift
// 写法1最简 Toast.lazyPush(identifier: "loading") { toast in
let a = Alert.push(scene: .loading, title: "正在加载", message: "请稍等片刻").rotate() toast.vm = .loading.title("正在加载\(i)").message("这条消息不会重复显示多条")
// 写法2标准
Alert.push() { (a) in
a.identifier = "loading"
a.rotate()
a.update { (vm) in
vm.scene = .loading
vm.title = "正在同步"
vm.message = "请稍等片刻"
}
}
// 写法3飞入效果
let a = Alert.push() { (a) in
a.identifier = "loading"
}
a.rotate()
a.update { (vm) in
vm.scene = .loading
vm.title = "正在同步"
vm.message = "请稍等片刻"
} }
``` ```
示例2发布一个可交互弹窗 ### 如果存在就更新,不存在就忽略指令
如果要对一个已经存在的实例进行更新,假如实例已经结束显示了,那就不进行任何操作,这时候可以使用 `find` 方法:
```swift ```swift
Alert.push() { (a) in Toast.find(identifier: "loading") { toast in
a.identifier = "error" toast.vm = .success(2).message("加载成功")
a.update { (vm) in
vm.scene = .error
vm.title = "同步失败"
vm.message = "请检查网络是否连接"
vm.add(action: .default, title: "重试") {
// do something
}
vm.add(action: .cancel, title: "取消", handler: nil)
}
} }
``` ```
## Alert页面中心弹窗
### Guard 操作表 弹窗控件,用于强阻塞性交互,用户必须做出选择或者等待结果才能进入下一步,当多个实例出现时,会以堆叠的形式显示,新的实例会在覆盖旧的实例上层。
`Guard`控件使用更加灵活: Alert和Toast一样有两种创建方法不再赘述。
### 修改实例内容
在实例弹出后仍然可以修改实例内容:
```swift ```swift
Guard.push { (g) in // 持有实例的情况下:
g.update { (vm) in Alert(.note) { alert in
vm.add(title: "大标题") alert.vm.message = "可以动态增加、删除、更新文字"
vm.add(subTitle: "副标题") alert.add(action: "增加标题") { alert in
vm.add(message: "正文") alert.vm.title = "这是标题"
vm.add(action: .default, title: "确定") { alert.reloadTextStack()
// do something
} }
vm.add(action: .destructive, title: "删除") { alert.add(action: "增加正文") { alert in
// do something alert.vm.message = "可以动态增加、删除、更新文字"
alert.reloadTextStack()
} }
vm.add(action: .cancel, title: "取消") { alert.add(action: "删除标题", style: .destructive) { alert in
alert.vm.title = nil
alert.reloadTextStack()
} }
alert.add(action: "删除正文", style: .destructive) { alert in
alert.vm.message = nil
alert.reloadTextStack()
} }
alert.add(action: "取消", style: .gray)
}
// 未持有实例时,可通过 identifier 查找并更新:
Alert.find(identifier: "my-alert") { alert in
alert.vm.title = "这是标题"
alert.reloadTextStack()
} }
``` ```
示例1弹出一个删除的操作表 ### 按钮的增删改查
```swift ```swift
Guard.push() { (g) in Alert(.note) { alert in
g.update { (vm) in alert.vm.message = "可以动态增加、删除按钮"
// 添加一个删除按钮 alert.add(action: "在底部增加按钮", style: .filled(color: .systemGreen)) { alert in
vm.add(action: .destructive, title: "删除") { [weak g] in alert.add(action: "哈哈1", identifier: "haha1")
// 确认弹窗
Alert.push(scene: .delete, title: "确认删除", message: "此操作不可撤销") { (a) in
a.update { (vm) in
vm.add(action: .destructive, title: "删除") { [weak a] in
// 删除操作
a?.pop()
} }
vm.add(action: .cancel, title: "取消", handler: nil) alert.add(action: "在当前按钮下方增加", style: .filled(color: .systemIndigo), identifier: "add") { alert in
alert.insert(action: .init(identifier: "haha2", style: .light(color: .systemOrange), title: "哈哈2", handler: nil), after: "add")
}
alert.add(action: "修改当前按钮文字", identifier: "edit") { alert in
alert.update(action: "已修改", for: "edit")
}
alert.add(action: "删除「哈哈1」", style: .destructive) { alert in
alert.remove(actions: .identifiers("haha1"))
}
alert.add(action: "删除「哈哈1」和「哈哈2」", style: .destructive) { alert in
alert.remove(actions: .identifiers("haha1", "haha2"))
}
alert.add(action: "删除全部按钮", style: .destructive) { alert in
alert.remove(actions: .all)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
alert.pop()
} }
} }
g?.pop() alert.add(action: "取消", style: .gray)
}
// 添加一个取消按钮
vm.add(action: .cancel, title: "取消", handler: nil)
}
} }
``` ```
示例2弹出一个全屏的隐私政策页面 ### 添加自定义控件
```swift ```swift
Guard.push() { (vc) in Alert { alert in
vc.isFullScreen = true alert.vm.title = "自定义控件"
vc.update { (vm) in // 图片
let titleLabel = vm.add(title: "隐私协议") let imgv = UIImageView(image: UIImage(named: "landscape"))
titleLabel.snp.makeConstraints { (mk) in imgv.contentMode = .scaleAspectFill
mk.height.equalTo(44) imgv.clipsToBounds = true
} imgv.layer.cornerRadiusWithContinuous = 12
let tv = UITextView() alert.add(subview: imgv).snp.makeConstraints { make in
tv.backgroundColor = .white make.height.equalTo(120)
tv.isEditable = false
vc.textStack.addArrangedSubview(tv)
tv.text = "这里可以插入一个webView"
vm.add(message: "请认真阅读以上内容,当您阅读完毕并同意协议内容时点击接受按钮。")
vm.add(action: .default, title: "接受") { [weak vc] in
vc?.pop()
} }
// seg
let seg = UISegmentedControl(items: ["开发", "测试", "预发", "生产"])
seg.selectedSegmentIndex = 0
alert.add(subview: seg).snp.makeConstraints { make in
make.height.equalTo(40)
make.width.equalTo(400)
} }
// slider
let slider = UISlider()
slider.minimumValue = 0
slider.maximumValue = 100
slider.value = 50
alert.add(subview: slider)
alert.add(spacing: 24)
alert.add(action: "取消", style: .gray)
} }
``` ```
## 高级用法
### 更新已有实例 ## Sheet底部操作表
示例获取刚才弹出的Loading把它更新为加载成功。 操作表控件,用于弱阻塞性交互。显示区域为从屏幕底部向上弹出的新图层,可以放置丰富的内容,自由度较高。
### 布局
操作表控件空间较大,可以放置更多的文字、按钮和其它任何控件。
```swift ```swift
Alert.find("loading", last: { (a) in Sheet { sheet in
a.update { (vm) in sheet.add(title: "ProHUD")
vm.scene = .success sheet.add(subTitle: "什么是操作表控件")
vm.title = "同步成功" sheet.add(message: "操作表控件,用于弱阻塞性交互。显示区域为从屏幕底部向上弹出的新图层,可以放置丰富的内容,自由度较高。")
vm.message = nil sheet.add(spacing: 24)
sheet.add(action: "确认", style: .destructive) { sheet in
Alert(.confirm) { alert in
alert.vm.title = "处理点击事件"
alert.add(action: "我知道了")
}
}
sheet.add(action: "取消", style: .gray)
} }
})
``` ```
### 避免重复发布 同样支持添加任意其它视图:
示例:发布一个横幅或者弹窗,如果已经有了就更新标题。
```swift ```swift
Toast.find("aaa", last: { (t) in Sheet { sheet in
t.update() { (vm) in sheet.add(title: "ProHUD")
vm.title = "已经存在了" // 图片
let imgv = UIImageView(image: UIImage(named: "landscape"))
imgv.contentMode = .scaleAspectFill
imgv.clipsToBounds = true
imgv.layer.cornerRadiusWithContinuous = 16
sheet.add(subview: imgv).snp.makeConstraints { make in
make.height.equalTo(200)
} }
}) { // seg
Toast.push(title: "这是一条id为aaa的横幅", message: "避免重复发布同一条信息") { (t) in let seg = UISegmentedControl(items: ["开发", "测试", "预发", "生产"])
t.identifier = "aaa" seg.selectedSegmentIndex = 0
t.update { (vm) in sheet.add(subview: seg).snp.makeConstraints { make in
vm.scene = .warning make.height.equalTo(40)
vm.duration = 0 make.width.equalTo(400)
} }
// slider
let slider = UISlider()
slider.minimumValue = 0
slider.maximumValue = 100
slider.value = 50
sheet.add(subview: slider).snp.makeConstraints { make in
make.height.equalTo(50)
} }
} }
``` ```
### 拦截背景点击事件
### 修改样式 有时候如果不希望点击背景直接`pop`掉,可以实现 `onTappedBackground` 以拦截背景点击事件
你可以在AppDelegate中配置好颜色、字体、间距等
```swift ```swift
ProHUD.config { (cfg) in Sheet { sheet in
cfg.rootViewController = window!.rootViewController sheet.add(title: "ProHUD")
cfg.primaryLabelColor = .black // 标题颜色 sheet.add(message: "点击背景将不会dismiss必须在下方做出选择才能关掉")
cfg.secondaryLabelColor = .darkGray // 正文颜色 sheet.add(spacing: 24)
cfg.alert { (a) in sheet.add(action: "确认")
a.titleFont = .bold(22) sheet.add(action: "取消", style: .gray)
a.bodyFont = .regular(17) } onTappedBackground: { sheet in
a.boldTextFont = .bold(18) print("点击了背景")
a.buttonFont = .bold(18) Toast.lazyPush(identifier: "alert") { toast in
a.forceQuitTimer = 3 toast.vm = .error
a.iconSize = .init(width: 48, height: 48) toast.vm.title = "点击了背景"
a.margin = 8 toast.vm.message = "点击背景将不会dismiss必须在下方做出选择才能关掉"
a.padding = 16 toast.vm.duration = 2
}
cfg.toast { (t) in
t.titleFont = .bold(18)
t.bodyFont = .regular(16)
}
cfg.guard { (g) in
g.titleFont = .bold(22)
g.subTitleFont = .bold(20)
g.bodyFont = .regular(17)
g.buttonFont = .bold(18)
} }
} }
``` ```
### 场景及其扩展 ## 个性化设置
你可以在一个文件中扩展场景,例如:
```swift
extension ProHUD.Scene {
static var confirm: ProHUD.Scene {
var scene = ProHUD.Scene(identifier: "confirm")
scene.image = UIImage(named: "ProHUDMessage")
return scene
}
static var delete: ProHUD.Scene {
var scene = ProHUD.Scene(identifier: "delete")
scene.image = UIImage(named: "ProHUDTrash")
scene.title = "确认删除"
scene.message = "此操作不可撤销"
return scene
}
static var buy: ProHUD.Scene {
var scene = ProHUD.Scene(identifier: "buy")
scene.image = UIImage(named: "ProHUDBuy")
scene.title = "确认付款"
scene.message = "一旦购买拒不退款"
return scene
}
}
```
这样你在发布横幅或者弹窗的时候scene参数就可以填写`.confirm, .delete, .buy`这三种了。例如:
```swift
Alert.push(scene: .delete) { (a) in
a.update() { (vm) in
vm.add(action: .destructive, title: "删除") { [weak a] in
// 删除操作
a?.pop()
}
vm.add(action: .cancel, title: "取消", handler: nil)
}
}
```
这样就可以弹出一个预先配置好的确认删除样式的弹窗。
### 完全自定义布局 ### 完全自定义布局
ProHUD支持完全自定义布局即将整个容器交给使用者来布局`Alert.Configuration.shared` 中配置了 `reloadData` 规则之后,实例在显示前以及更新内容时都会进入此函数,执行自定义的 `reloadData` 代码。也可以指定部分 `identifier` 走自定义布局代码,其余走内置布局代码,例如:
```swift ```swift
ProHUD.config { (cfg) in Alert.Configuration.shared { config in
cfg.alert { (config) in config.reloadData { vc in
config.reloadData { (vc) in if vc.identifier == "custom" {
// 这是数据模型 return true
vc.vm
// 这是要弹出的vc
vc
// 你可以在这里完全自由布局
} }
return false
}
}
Alert { alert in
alert.identifier = "custom"
alert.contentView.backgroundColor = .systemYellow
alert.view.addSubview(alert.contentView)
alert.contentView.layer.cornerRadiusWithContinuous = 32
alert.contentView.snp.makeConstraints { make in
make.width.equalTo(UIScreen.main.bounds.width - 100)
make.height.equalTo(UIScreen.main.bounds.height - 200)
make.center.equalToSuperview()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
alert.pop()
} }
} }
``` ```
### 个性化选项
ProHUD内置的布局也支持丰富的个性化参数例如
- 标题、正文、按钮字体字号
- 背景颜色、模糊效果
- 文字颜色
- 图标大小
- 卡片圆角
- Sheet组件卡片距离屏幕的边距
具体请探索 `ProHUD.Configuration` 类代码。
## 文档 ## 文档

View File

@ -98,13 +98,6 @@ public extension ViewModel {
return obj return obj
} }
static func text(title: String?, message: String?) -> ViewModel {
let obj = ViewModel()
obj.title = title
obj.message = message
return obj
}
// MARK: loading // MARK: loading
static var loading: ViewModel { static var loading: ViewModel {
let obj = ViewModel(icon: UIImage(inProHUD: "prohud.windmill")) let obj = ViewModel(icon: UIImage(inProHUD: "prohud.windmill"))