mirror of https://github.com/xaoxuu/ProHUD
update
This commit is contained in:
parent
1b665b9b4c
commit
40bf7d38a8
|
@ -22,20 +22,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
|
||||
|
||||
hud.config(alert: { (a) in
|
||||
// a.tintColor = .green
|
||||
// a.loadSubviews { (aa) in
|
||||
//
|
||||
// }
|
||||
// a.padding = 12
|
||||
// a.margin = 4
|
||||
// a.loadSubviews { (aa) in
|
||||
//
|
||||
// }
|
||||
})
|
||||
hud.config(toast: { (a) in
|
||||
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -16,29 +16,53 @@ class ViewController: UIViewController {
|
|||
// Do any additional setup after loading the view.
|
||||
|
||||
|
||||
ProHUD.configAlert { (alert) in
|
||||
alert.minimizeTimeout = 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
|
||||
let a = ProHUD.show(alert: .delete, title: "确认删除", message: "此操作不可撤销")
|
||||
|
||||
a.addAction(style: .destructive, title: "删除") { [weak a] in
|
||||
a?.updateContent(scene: .success, title: "操作成功", message: "恭喜,您已经成功删除了xxx")
|
||||
a?.updateAction(index: 0, style: .default, title: "好的", action: {
|
||||
a?.remove()
|
||||
}).removeAction(index: 1)
|
||||
// a?.updateContent(scene: .success, title: "操作成功").removeAction(index: -1).timeout(2)
|
||||
}.addAction(style: .cancel, title: "取消", action: nil).didDisappear {
|
||||
debugPrint("didDisappear")
|
||||
}
|
||||
|
||||
// let a = ProHUD.show(alert: .delete, title: "确认删除", message: "此操作不可撤销")
|
||||
//
|
||||
// a.addAction(style: .destructive, title: "删除") { [weak a] in
|
||||
// a?.updateContent(scene: .success, title: "操作成功", message: "恭喜,您已经成功删除了xxx")
|
||||
// a?.updateAction(index: 0, style: .default, title: "好的", action: {
|
||||
// a?.remove()
|
||||
// }).removeAction(index: 1)
|
||||
//// a?.updateContent(scene: .success, title: "操作成功").removeAction(index: -1).timeout(2)
|
||||
// }.addAction(style: .cancel, title: "取消", action: nil).didDisappear {
|
||||
// debugPrint("didDisappear")
|
||||
// }
|
||||
//
|
||||
// ProHUD.show(alert: .delete, title: "克里斯蒂娜删除", message: "克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞").addAction(style: .destructive, title: "删除") {
|
||||
//
|
||||
// }.addAction(style: .cancel, title: "取消", action: nil).didDisappear {
|
||||
// debugPrint("didDisappear")
|
||||
// }
|
||||
|
||||
let t = ProHUD.Toast(scene: .loading, title: "正在加载", message: "哈哈")
|
||||
let a = ProHUD.show(alert: .loading, title: "正在加载", message: "请坐和放宽")
|
||||
a.didMinimize {
|
||||
hud.show(t)
|
||||
}
|
||||
t.didTapped { [weak t] in
|
||||
hud.show(a)
|
||||
t?.remove()
|
||||
}
|
||||
|
||||
// a.didMinimize {
|
||||
// ProHUD.show(toast: .loading, title: "正在加载", message: "哈哈").didTapped {
|
||||
// ProHUD.show(toast: .loading, title: "正在加载", message: "哈克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞哈克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞")
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
|
||||
|
||||
|
||||
|
|
|
@ -35,8 +35,9 @@ public extension ProHUD.Configuration {
|
|||
public var iconSize = CGSize(width: 48, height: 48)
|
||||
|
||||
public var tintColor = UIColor.init(red: 3/255, green: 169/255, blue: 244/255, alpha: 1)
|
||||
/// 多少秒后显示强制退出的按钮【终止/隐藏】
|
||||
public var forceQuitTimeout = TimeInterval(2)
|
||||
|
||||
/// 多少秒后显示最小化的按钮
|
||||
public var minimizeTimeout = TimeInterval(10)
|
||||
|
||||
/// 加载视图
|
||||
lazy var loadSubviews: (ProHUD.Alert) -> Void = {
|
||||
|
@ -45,22 +46,21 @@ public extension ProHUD.Configuration {
|
|||
vc.view.addSubview(vc.contentView)
|
||||
vc.contentView.contentView.addSubview(vc.contentStack)
|
||||
|
||||
let config = hud.config.alert
|
||||
vc.contentStack.spacing = config.margin + config.padding
|
||||
vc.contentStack.spacing = alertConfig.margin + alertConfig.padding
|
||||
|
||||
vc.contentView.layer.masksToBounds = true
|
||||
vc.contentView.layer.cornerRadius = config.cornerRadius
|
||||
vc.contentView.layer.cornerRadius = alertConfig.cornerRadius
|
||||
|
||||
vc.contentView.snp.makeConstraints { (mk) in
|
||||
mk.center.equalToSuperview()
|
||||
mk.width.lessThanOrEqualTo(CGFloat.minimum(UIScreen.main.bounds.width * 0.68, config.maxWidth))
|
||||
mk.width.lessThanOrEqualTo(CGFloat.minimum(UIScreen.main.bounds.width * 0.68, alertConfig.maxWidth))
|
||||
}
|
||||
vc.contentStack.snp.makeConstraints { (mk) in
|
||||
mk.centerX.equalToSuperview()
|
||||
mk.top.equalToSuperview().offset(config.padding)
|
||||
mk.bottom.equalToSuperview().offset(-config.padding)
|
||||
mk.leading.equalToSuperview().offset(config.padding)
|
||||
mk.trailing.equalToSuperview().offset(-config.padding)
|
||||
mk.top.equalToSuperview().offset(alertConfig.padding)
|
||||
mk.bottom.equalToSuperview().offset(-alertConfig.padding)
|
||||
mk.leading.equalToSuperview().offset(alertConfig.padding)
|
||||
mk.trailing.equalToSuperview().offset(-alertConfig.padding)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -70,7 +70,6 @@ public extension ProHUD.Configuration {
|
|||
lazy var updateFrame: (ProHUD.Alert) -> Void = {
|
||||
return { (vc) in
|
||||
debugPrint(vc, "updateFrame")
|
||||
let config = hud.config.alert
|
||||
let isFirstLayout: Bool
|
||||
// 图标和文字至少有一个,如果都没有添加到视图中,说明是第一次layout
|
||||
if vc.textStack.superview == nil && vc.imageView?.superview == nil {
|
||||
|
@ -102,10 +101,10 @@ public extension ProHUD.Configuration {
|
|||
let icon = UIImageView(image: img)
|
||||
vc.contentStack.addArrangedSubview(icon)
|
||||
icon.snp.makeConstraints { (mk) in
|
||||
mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding*2.5)
|
||||
mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2.5)
|
||||
mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding*4)
|
||||
mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*4)
|
||||
mk.top.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*2.5)
|
||||
mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*2.5)
|
||||
mk.leading.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*4)
|
||||
mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*4)
|
||||
}
|
||||
vc.imageView = icon
|
||||
}
|
||||
|
@ -114,10 +113,10 @@ public extension ProHUD.Configuration {
|
|||
if vc.vm.title?.count ?? 0 > 0 || vc.vm.message?.count ?? 0 > 0 {
|
||||
vc.contentStack.addArrangedSubview(vc.textStack)
|
||||
vc.textStack.snp.makeConstraints { (mk) in
|
||||
mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding*1.5)
|
||||
mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding*1.5)
|
||||
mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding*2)
|
||||
mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2)
|
||||
mk.top.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*1.5)
|
||||
mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*1.5)
|
||||
mk.leading.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*2)
|
||||
mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*2)
|
||||
}
|
||||
if vc.vm.title?.count ?? 0 > 0 {
|
||||
if let lb = vc.titleLabel {
|
||||
|
@ -125,7 +124,7 @@ public extension ProHUD.Configuration {
|
|||
} else {
|
||||
let title = UILabel()
|
||||
title.textAlignment = .center
|
||||
title.numberOfLines = config.titleMaxLines
|
||||
title.numberOfLines = alertConfig.titleMaxLines
|
||||
title.textColor = UIColor.init(white: 0.2, alpha: 1)
|
||||
title.text = vc.vm.title
|
||||
vc.textStack.addArrangedSubview(title)
|
||||
|
@ -133,10 +132,10 @@ public extension ProHUD.Configuration {
|
|||
}
|
||||
if vc.vm.message?.count ?? 0 > 0 {
|
||||
// 有message
|
||||
vc.titleLabel?.font = config.largeTitleFont
|
||||
vc.titleLabel?.font = alertConfig.largeTitleFont
|
||||
} else {
|
||||
// 没有message
|
||||
vc.titleLabel?.font = config.titleFont
|
||||
vc.titleLabel?.font = alertConfig.titleFont
|
||||
}
|
||||
} else {
|
||||
vc.titleLabel?.removeFromSuperview()
|
||||
|
@ -147,8 +146,8 @@ public extension ProHUD.Configuration {
|
|||
} else {
|
||||
let body = UILabel()
|
||||
body.textAlignment = .center
|
||||
body.font = config.bodyFont
|
||||
body.numberOfLines = config.bodyMaxLines
|
||||
body.font = alertConfig.bodyFont
|
||||
body.numberOfLines = alertConfig.bodyMaxLines
|
||||
body.textColor = UIColor.darkGray
|
||||
body.text = vc.vm.message
|
||||
vc.textStack.addArrangedSubview(body)
|
||||
|
@ -156,10 +155,10 @@ public extension ProHUD.Configuration {
|
|||
}
|
||||
if vc.vm.title?.count ?? 0 > 0 {
|
||||
// 有title
|
||||
vc.messageLabel?.font = config.bodyFont
|
||||
vc.messageLabel?.font = alertConfig.bodyFont
|
||||
} else {
|
||||
// 没有title
|
||||
vc.messageLabel?.font = config.titleFont
|
||||
vc.messageLabel?.font = alertConfig.titleFont
|
||||
}
|
||||
} else {
|
||||
vc.messageLabel?.removeFromSuperview()
|
||||
|
@ -202,10 +201,11 @@ public extension ProHUD.Configuration {
|
|||
let btn = UIButton.hideButton()
|
||||
vc.view.addSubview(btn)
|
||||
btn.snp.makeConstraints { (mk) in
|
||||
mk.leading.top.equalTo(vc.contentView).offset(hud.config.alert.margin/2)
|
||||
mk.leading.top.equalTo(vc.contentView).offset(alertConfig.margin/2)
|
||||
}
|
||||
vc.addTouchUpAction(for: btn) { [weak vc] in
|
||||
debugPrint("点击了隐藏")
|
||||
vc?.minimizeCallback?()
|
||||
vc?.remove()
|
||||
}
|
||||
}
|
||||
|
@ -225,3 +225,11 @@ public extension ProHUD.Configuration {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
internal var alertConfig = ProHUD.Configuration.Alert()
|
||||
|
||||
public extension ProHUD {
|
||||
static func configAlert(_ config: (inout Configuration.Alert) -> Void) {
|
||||
config(&alertConfig)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,14 @@ public extension ProHUD {
|
|||
/// 正文(包括icon、textStack、actionStack)
|
||||
internal var contentStack: StackContainer = {
|
||||
let stack = StackContainer()
|
||||
stack.spacing = alertConfig.margin
|
||||
return stack
|
||||
}()
|
||||
|
||||
/// 文本区域
|
||||
internal var textStack: StackContainer = {
|
||||
let stack = StackContainer()
|
||||
stack.spacing = alertConfig.margin
|
||||
return stack
|
||||
}()
|
||||
internal var imageView: UIImageView?
|
||||
|
@ -33,6 +35,7 @@ public extension ProHUD {
|
|||
internal var actionStack: StackContainer = {
|
||||
let stack = StackContainer()
|
||||
stack.alignment = .fill
|
||||
stack.spacing = alertConfig.margin
|
||||
return stack
|
||||
}()
|
||||
|
||||
|
@ -42,6 +45,8 @@ public extension ProHUD {
|
|||
/// 显示顶部按钮(最小化)
|
||||
internal var showNavButtonsBlock: DispatchWorkItem?
|
||||
|
||||
internal var minimizeCallback: (() -> Void)?
|
||||
|
||||
// MARK: 生命周期
|
||||
|
||||
/// 实例化
|
||||
|
@ -51,7 +56,7 @@ public extension ProHUD {
|
|||
/// - Parameter icon: 图标
|
||||
public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil) {
|
||||
self.init()
|
||||
view.tintColor = hud.config.alert.tintColor
|
||||
view.tintColor = alertConfig.tintColor
|
||||
vm.scene = scene
|
||||
vm.title = title
|
||||
vm.message = message
|
||||
|
@ -85,7 +90,7 @@ public extension ProHUD {
|
|||
let count = hud.alerts.count
|
||||
if count == 0 && hud.alertWindow != nil {
|
||||
UIView.animateFastEaseOut(delay: 0, animations: {
|
||||
self.view.transform = .init(scaleX: 1.02, y: 1.02)
|
||||
self.view.transform = .init(scaleX: 1.05, y: 1.05)
|
||||
window.backgroundColor = window.backgroundColor?.withAlphaComponent(0)
|
||||
}) { (done) in
|
||||
hud.alertWindow = nil
|
||||
|
@ -130,6 +135,13 @@ public extension ProHUD {
|
|||
return self
|
||||
}
|
||||
|
||||
/// 最小化事件
|
||||
/// - Parameter callback: 事件回调
|
||||
@discardableResult public func didMinimize(_ callback: (() -> Void)?) -> Alert {
|
||||
minimizeCallback = callback
|
||||
return self
|
||||
}
|
||||
|
||||
/// 消失事件
|
||||
/// - Parameter callback: 事件回调
|
||||
@discardableResult public func didDisappear(_ callback: (() -> Void)?) -> Alert {
|
||||
|
@ -144,7 +156,7 @@ public extension ProHUD {
|
|||
vm.message = message
|
||||
vm.scene = scene
|
||||
vm.icon = icon
|
||||
hud.config.alert.updateFrame(self)
|
||||
alertConfig.updateFrame(self)
|
||||
return self
|
||||
}
|
||||
|
||||
|
@ -206,8 +218,8 @@ fileprivate extension ProHUD.Alert {
|
|||
willLayout = DispatchWorkItem(block: { [weak self] in
|
||||
if let a = self {
|
||||
// 布局
|
||||
hud.config.alert.loadSubviews(a)
|
||||
hud.config.alert.updateFrame(a)
|
||||
alertConfig.loadSubviews(a)
|
||||
alertConfig.updateFrame(a)
|
||||
// 超时
|
||||
a.timeoutBlock?.cancel()
|
||||
if let t = a.timeout, t > 0 {
|
||||
|
@ -219,14 +231,14 @@ fileprivate extension ProHUD.Alert {
|
|||
a.timeoutBlock = nil
|
||||
}
|
||||
// 顶部按钮
|
||||
if hud.config.alert.forceQuitTimeout > 0 && self?.actionStack.superview == nil {
|
||||
if alertConfig.minimizeTimeout > 0 && self?.actionStack.superview == nil {
|
||||
a.showNavButtonsBlock?.cancel()
|
||||
a.showNavButtonsBlock = DispatchWorkItem(block: { [weak self] in
|
||||
if let s = self {
|
||||
hud.config.alert.showNavButtons(s)
|
||||
alertConfig.showNavButtons(s)
|
||||
}
|
||||
})
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+hud.config.alert.forceQuitTimeout, execute: a.showNavButtonsBlock!)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+alertConfig.minimizeTimeout, execute: a.showNavButtonsBlock!)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -257,7 +269,7 @@ public extension ProHUD {
|
|||
alert.view.alpha = 1
|
||||
})
|
||||
alerts.append(alert)
|
||||
updateAlertsScale()
|
||||
updateAlertsLayout()
|
||||
// setup timeout
|
||||
if let _ = alert.timeout, alert.timeoutBlock == nil {
|
||||
alert.timeout(alert.timeout)
|
||||
|
@ -302,6 +314,11 @@ public extension ProHUD {
|
|||
|
||||
public extension ProHUD {
|
||||
|
||||
@discardableResult
|
||||
class func show(_ alert: Alert) -> Alert {
|
||||
return shared.show(alert)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
class func show(alert: Alert.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Alert {
|
||||
return shared.show(alert: alert, title: title, message: message, icon: icon)
|
||||
|
@ -325,7 +342,7 @@ public extension ProHUD {
|
|||
|
||||
fileprivate extension ProHUD {
|
||||
|
||||
func updateAlertsScale() {
|
||||
func updateAlertsLayout() {
|
||||
for (i, a) in alerts.reversed().enumerated() {
|
||||
let scale = CGFloat(pow(0.7, CGFloat(i)))
|
||||
UIView.animate(withDuration: 2, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseInOut], animations: {
|
||||
|
@ -356,7 +373,7 @@ fileprivate extension ProHUD {
|
|||
alerts.remove(at: i)
|
||||
}
|
||||
}
|
||||
updateAlertsScale()
|
||||
updateAlertsLayout()
|
||||
} else if alerts.count == 1 {
|
||||
alerts.removeAll()
|
||||
} else {
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
// Copyright © 2019 Titan Studio. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension ProHUD.Alert {
|
||||
enum Scene {
|
||||
/// 默认场景
|
||||
|
|
|
@ -33,14 +33,14 @@ extension UIButton {
|
|||
class func actionButton(style: UIAlertAction.Style, title: String?) -> UIButton {
|
||||
let btn = UIButton(type: .system)
|
||||
btn.setTitle(title, for: .normal)
|
||||
btn.layer.cornerRadius = hud.config.alert.cornerRadius / 2
|
||||
btn.titleLabel?.font = hud.config.alert.buttonFont
|
||||
btn.layer.cornerRadius = alertConfig.cornerRadius / 2
|
||||
btn.titleLabel?.font = alertConfig.buttonFont
|
||||
btn.update(style: style)
|
||||
return btn
|
||||
}
|
||||
|
||||
func update(style: UIAlertAction.Style) {
|
||||
let pd = hud.config.alert.padding/2
|
||||
let pd = alertConfig.padding/2
|
||||
if style != .cancel {
|
||||
backgroundColor = .init(white: 0, alpha: 0.04)
|
||||
contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5)
|
||||
|
|
|
@ -15,7 +15,7 @@ public extension ProHUD {
|
|||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
spacing = hud.config.alert.margin
|
||||
spacing = alertConfig.margin
|
||||
distribution = .fill
|
||||
alignment = .center
|
||||
axis = .vertical
|
||||
|
@ -59,4 +59,12 @@ internal extension UIView {
|
|||
animateEaseOut(duration: 2, delay: delay, animations: animations, completion: completion)
|
||||
}
|
||||
|
||||
class func animateForToast(animations: @escaping () -> Void) {
|
||||
animateEaseOut(duration: 1, delay: 0, animations: animations, completion: nil)
|
||||
}
|
||||
|
||||
class func animateForToast(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
|
||||
animateEaseOut(duration: 1, delay: 0, animations: animations, completion: completion)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,12 +14,8 @@ public class ProHUD: NSObject {
|
|||
|
||||
public static let shared = ProHUD()
|
||||
|
||||
internal var config = Configuration()
|
||||
|
||||
public var elements = [String]()
|
||||
|
||||
// public var toasts = [Toast]()
|
||||
public var alerts = [Alert]()
|
||||
internal var toasts = [Toast]()
|
||||
internal var alerts = [Alert]()
|
||||
|
||||
internal var alertWindow: UIWindow?
|
||||
|
||||
|
@ -28,21 +24,6 @@ public class ProHUD: NSObject {
|
|||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public extension ProHUD {
|
||||
|
||||
func config(toast: (inout Configuration.Toast) -> Void) {
|
||||
toast(&config.toast)
|
||||
}
|
||||
func config(alert: (inout Configuration.Alert) -> Void) {
|
||||
alert(&config.alert)
|
||||
}
|
||||
func config(guard: (inout Configuration.Guard) -> Void) {
|
||||
`guard`(&config.guard)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -30,32 +30,157 @@ public extension ProHUD.Configuration {
|
|||
public var iconSize = CGSize(width: 48, height: 48)
|
||||
|
||||
/// 加载视图
|
||||
lazy var loadSubviews: (ProHUD.Alert) -> Void = {
|
||||
lazy var loadSubviews: (ProHUD.Toast) -> Void = {
|
||||
return { (vc) in
|
||||
debugPrint(vc, "loadSubviews")
|
||||
let config = toastConfig
|
||||
vc.view.addSubview(vc.contentView)
|
||||
vc.contentView.contentView.addSubview(vc.contentStack)
|
||||
vc.contentStack.spacing = config.margin
|
||||
|
||||
vc.contentView.layer.masksToBounds = true
|
||||
vc.contentView.layer.cornerRadius = config.cornerRadius
|
||||
|
||||
vc.contentView.snp.makeConstraints { (mk) in
|
||||
mk.leading.trailing.top.equalToSuperview()
|
||||
}
|
||||
vc.contentStack.snp.makeConstraints { (mk) in
|
||||
mk.top.equalToSuperview().offset(config.padding)
|
||||
mk.bottom.equalToSuperview().offset(-config.padding)
|
||||
mk.leading.equalToSuperview().offset(config.padding)
|
||||
mk.trailing.equalToSuperview().offset(-config.padding)
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
/// 更新视图
|
||||
lazy var updateFrame: (ProHUD.Alert) -> Void = {
|
||||
lazy var updateFrame: (ProHUD.Toast) -> Void = {
|
||||
return { (vc) in
|
||||
debugPrint(vc, "updateFrame")
|
||||
let config = toastConfig
|
||||
let isFirstLayout: Bool
|
||||
// 图标和文字至少有一个,如果都没有添加到视图中,说明是第一次layout
|
||||
if vc.textStack.superview == nil && vc.imageView?.superview == nil {
|
||||
isFirstLayout = true
|
||||
} else {
|
||||
isFirstLayout = false
|
||||
}
|
||||
let imgStr: String
|
||||
switch vc.vm.scene {
|
||||
case .success:
|
||||
imgStr = "ProHUDSuccess"
|
||||
case .warning:
|
||||
imgStr = "ProHUDWarning"
|
||||
case .error:
|
||||
imgStr = "ProHUDError"
|
||||
case .loading:
|
||||
imgStr = "ProHUDLoading"
|
||||
case .confirm:
|
||||
imgStr = "ProHUDMessage"
|
||||
case .delete:
|
||||
imgStr = "ProHUDTrash"
|
||||
default:
|
||||
imgStr = "ProHUDMessage"
|
||||
}
|
||||
let img = vc.vm.icon ?? ProHUD.image(named: imgStr)
|
||||
if let imgv = vc.imageView {
|
||||
imgv.image = img
|
||||
} else {
|
||||
let icon = UIImageView(image: img)
|
||||
vc.contentStack.addArrangedSubview(icon)
|
||||
// icon.snp.makeConstraints { (mk) in
|
||||
// mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding+config.margin)
|
||||
// mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding-config.margin)
|
||||
// mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding+config.margin)
|
||||
// mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding-config.margin)
|
||||
// }
|
||||
vc.imageView = icon
|
||||
}
|
||||
|
||||
// text
|
||||
if vc.vm.title?.count ?? 0 > 0 || vc.vm.message?.count ?? 0 > 0 {
|
||||
vc.contentStack.addArrangedSubview(vc.textStack)
|
||||
// vc.textStack.snp.makeConstraints { (mk) in
|
||||
// mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding*1.5)
|
||||
// mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding*1.5)
|
||||
// mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding*2)
|
||||
// mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2)
|
||||
// }
|
||||
if vc.vm.title?.count ?? 0 > 0 {
|
||||
if let lb = vc.titleLabel {
|
||||
lb.text = vc.vm.title
|
||||
} else {
|
||||
let title = UILabel()
|
||||
title.textAlignment = .justified
|
||||
title.numberOfLines = config.titleMaxLines
|
||||
title.textColor = UIColor.init(white: 0.2, alpha: 1)
|
||||
title.font = config.titleFont
|
||||
title.text = vc.vm.title
|
||||
vc.textStack.addArrangedSubview(title)
|
||||
vc.titleLabel = title
|
||||
}
|
||||
} else {
|
||||
vc.titleLabel?.removeFromSuperview()
|
||||
}
|
||||
if vc.vm.message?.count ?? 0 > 0 {
|
||||
if let lb = vc.messageLabel {
|
||||
lb.text = vc.vm.message
|
||||
} else {
|
||||
let body = UILabel()
|
||||
body.textAlignment = .justified
|
||||
body.font = config.bodyFont
|
||||
body.numberOfLines = config.bodyMaxLines
|
||||
body.textColor = UIColor.darkGray
|
||||
body.font = config.bodyFont
|
||||
body.text = vc.vm.message
|
||||
vc.textStack.addArrangedSubview(body)
|
||||
vc.messageLabel = body
|
||||
}
|
||||
} else {
|
||||
vc.messageLabel?.removeFromSuperview()
|
||||
}
|
||||
} else {
|
||||
vc.textStack.removeFromSuperview()
|
||||
}
|
||||
|
||||
if isFirstLayout {
|
||||
vc.view.layoutIfNeeded()
|
||||
vc.updateFrame()
|
||||
vc.imageView?.transform = .init(scaleX: 0.75, y: 0.75)
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
UIView.animateFastEaseOut(delay: 0, animations: {
|
||||
vc.imageView?.transform = .identity
|
||||
vc.view.layoutIfNeeded()
|
||||
vc.updateFrame()
|
||||
|
||||
}) { (done) in
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
/// 加载视图
|
||||
/// - Parameter callback: 回调代码
|
||||
public mutating func loadSubviews(_ callback: @escaping (ProHUD.Alert) -> Void) {
|
||||
public mutating func loadSubviews(_ callback: @escaping (ProHUD.Toast) -> Void) {
|
||||
loadSubviews = callback
|
||||
}
|
||||
|
||||
/// 更新视图
|
||||
/// - Parameter callback: 回调代码
|
||||
public mutating func updateFrame(_ callback: @escaping (ProHUD.Alert) -> Void) {
|
||||
public mutating func updateFrame(_ callback: @escaping (ProHUD.Toast) -> Void) {
|
||||
updateFrame = callback
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal var toastConfig = ProHUD.Configuration.Toast()
|
||||
|
||||
public extension ProHUD {
|
||||
static func configToast(_ config: (inout Configuration.Toast) -> Void) {
|
||||
config(&toastConfig)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,4 +6,326 @@
|
|||
// Copyright © 2019 Titan Studio. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
public extension ProHUD {
|
||||
class Toast: HUDController {
|
||||
|
||||
public var window: UIWindow?
|
||||
/// 内容容器
|
||||
public var contentView = BlurView()
|
||||
internal var contentStack: StackContainer = {
|
||||
let stack = StackContainer()
|
||||
stack.axis = .horizontal
|
||||
stack.alignment = .top
|
||||
stack.spacing = toastConfig.margin
|
||||
return stack
|
||||
}()
|
||||
|
||||
/// 图标
|
||||
internal var imageView: UIImageView?
|
||||
/// 文本区域
|
||||
internal var textStack: StackContainer = {
|
||||
let stack = StackContainer()
|
||||
stack.spacing = toastConfig.margin
|
||||
stack.alignment = .leading
|
||||
return stack
|
||||
}()
|
||||
internal var titleLabel: UILabel?
|
||||
internal var messageLabel: UILabel?
|
||||
|
||||
/// 视图模型
|
||||
public var vm = ViewModel()
|
||||
|
||||
public var removable = true
|
||||
|
||||
internal var tapCallback: (() -> Void)?
|
||||
|
||||
// MARK: 生命周期
|
||||
|
||||
/// 实例化
|
||||
/// - Parameter scene: 场景
|
||||
/// - Parameter title: 标题
|
||||
/// - Parameter message: 内容
|
||||
/// - Parameter icon: 图标
|
||||
public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil) {
|
||||
self.init()
|
||||
vm.scene = scene
|
||||
vm.title = title
|
||||
vm.message = message
|
||||
vm.icon = icon
|
||||
switch scene {
|
||||
case .loading:
|
||||
timeout = nil
|
||||
default:
|
||||
timeout = 2
|
||||
}
|
||||
|
||||
willLayout()
|
||||
|
||||
// 点击
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(privDidTapped(_:)))
|
||||
view.addGestureRecognizer(tap)
|
||||
// 拖动
|
||||
let pan = UIPanGestureRecognizer(target: self, action: #selector(privDidPan(_:)))
|
||||
view.addGestureRecognizer(pan)
|
||||
|
||||
}
|
||||
|
||||
public override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
disappearCallback?()
|
||||
}
|
||||
|
||||
|
||||
/// 移除
|
||||
public func remove() {
|
||||
hud.removeItemFromArray(toast: self)
|
||||
UIView.animateForToast(animations: {
|
||||
let frame = self.window?.frame ?? .zero
|
||||
self.window?.transform = .init(translationX: 0, y: -200-frame.maxY)
|
||||
}) { (done) in
|
||||
self.view.removeFromSuperview()
|
||||
self.removeFromParent()
|
||||
self.window = nil
|
||||
}
|
||||
}
|
||||
|
||||
internal func updateFrame() {
|
||||
let newSize = contentView.frame.size
|
||||
view.frame.size = newSize
|
||||
window?.frame.size = newSize
|
||||
hud.updateToastsLayout()
|
||||
}
|
||||
|
||||
// MARK: 设置函数
|
||||
|
||||
/// 设置超时时间
|
||||
/// - Parameter timeout: 超时时间
|
||||
@discardableResult public func timeout(_ timeout: TimeInterval?) -> Toast {
|
||||
self.timeout = timeout
|
||||
willLayout()
|
||||
return self
|
||||
}
|
||||
|
||||
/// 点击事件
|
||||
/// - Parameter callback: 事件回调
|
||||
@discardableResult public func didTapped(_ callback: (() -> Void)?) -> Toast {
|
||||
tapCallback = callback
|
||||
return self
|
||||
}
|
||||
|
||||
/// 消失事件
|
||||
/// - Parameter callback: 事件回调
|
||||
@discardableResult public func didDisappear(_ callback: (() -> Void)?) -> Toast {
|
||||
disappearCallback = callback
|
||||
return self
|
||||
}
|
||||
|
||||
/// 更新标题
|
||||
/// - Parameter title: 标题
|
||||
@discardableResult public func update(scene: Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast {
|
||||
vm.scene = scene
|
||||
vm.title = title
|
||||
vm.message = message
|
||||
vm.icon = icon
|
||||
toastConfig.updateFrame(self)
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileprivate extension ProHUD.Toast {
|
||||
func willLayout() {
|
||||
willLayout?.cancel()
|
||||
willLayout = DispatchWorkItem(block: { [weak self] in
|
||||
if let a = self {
|
||||
// 布局
|
||||
toastConfig.loadSubviews(a)
|
||||
toastConfig.updateFrame(a)
|
||||
// 超时
|
||||
a.timeoutBlock?.cancel()
|
||||
if let t = a.timeout, t > 0 {
|
||||
a.timeoutBlock = DispatchWorkItem(block: { [weak self] in
|
||||
self?.remove()
|
||||
})
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: a.timeoutBlock!)
|
||||
} else {
|
||||
a.timeoutBlock = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+0.001, execute: willLayout!)
|
||||
}
|
||||
|
||||
func willUpdateToastsLayout() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// 点击事件
|
||||
/// - Parameter sender: 手势
|
||||
@objc func privDidTapped(_ sender: UITapGestureRecognizer) {
|
||||
tapCallback?()
|
||||
}
|
||||
|
||||
/// 拖拽事件
|
||||
/// - Parameter sender: 手势
|
||||
@objc func privDidPan(_ sender: UIPanGestureRecognizer) {
|
||||
timeoutBlock?.cancel()
|
||||
let point = sender.translation(in: sender.view)
|
||||
window?.transform = .init(translationX: 0, y: point.y)
|
||||
if sender.state == .recognized {
|
||||
let v = sender.velocity(in: sender.view)
|
||||
if removable == true && (((window?.frame.origin.y ?? 0) < 0 && v.y < 0) || v.y < -1200) {
|
||||
// 移除
|
||||
self.remove()
|
||||
} else {
|
||||
UIView.animateForToast(animations: {
|
||||
self.window?.transform = .identity
|
||||
}) { (done) in
|
||||
// 重置计时器
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - AlertHUD public func
|
||||
|
||||
public extension ProHUD {
|
||||
|
||||
@discardableResult
|
||||
func show(_ toast: Toast) -> Toast {
|
||||
let config = toastConfig
|
||||
if toast.window == nil {
|
||||
let w = UIWindow(frame: .init(x: config.margin, y: config.margin, width: UIScreen.main.bounds.width - 2*config.margin, height: 500))
|
||||
toast.window = w
|
||||
w.rootViewController = toast
|
||||
w.windowLevel = UIWindow.Level(5000)
|
||||
w.backgroundColor = .clear
|
||||
w.layer.shadowRadius = 8
|
||||
w.layer.shadowOffset = .init(width: 0, height: 5)
|
||||
w.layer.shadowOpacity = 0.2
|
||||
}
|
||||
|
||||
let window = toast.window!
|
||||
window.makeKeyAndVisible()
|
||||
window.transform = .init(translationX: 0, y: -window.frame.maxY)
|
||||
|
||||
toasts.append(toast)
|
||||
|
||||
UIView.animateForToast {
|
||||
window.transform = .identity
|
||||
}
|
||||
return toast
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func show(toast: Toast.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast {
|
||||
return show(Toast(scene: toast, title: title, message: message, icon: icon))
|
||||
}
|
||||
|
||||
func toasts(identifier: String?) -> [Toast] {
|
||||
var tt = [Toast]()
|
||||
for t in toasts {
|
||||
if t.identifier == identifier {
|
||||
tt.append(t)
|
||||
}
|
||||
}
|
||||
return tt
|
||||
}
|
||||
|
||||
func remove(toast: Toast) {
|
||||
for t in toasts {
|
||||
if t == toast {
|
||||
t.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func remove(toast identifier: String?) {
|
||||
for t in toasts {
|
||||
if t.identifier == identifier {
|
||||
t.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AlertHUD public class func
|
||||
|
||||
public extension ProHUD {
|
||||
|
||||
@discardableResult
|
||||
class func show(_ toast: Toast) -> Toast {
|
||||
return shared.show(toast)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
class func show(toast: Toast.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Toast {
|
||||
return shared.show(toast: toast, title: title, message: message, icon: icon)
|
||||
}
|
||||
|
||||
class func alert(identifier: String?) -> [Toast] {
|
||||
return shared.toasts(identifier: identifier)
|
||||
}
|
||||
|
||||
class func remove(toast: Toast) {
|
||||
shared.remove(toast: toast)
|
||||
}
|
||||
|
||||
class func remove(toast identifier: String?) {
|
||||
shared.remove(toast: identifier)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: AlertHUD private func
|
||||
|
||||
fileprivate extension ProHUD {
|
||||
|
||||
func updateToastsLayout() {
|
||||
for (i, e) in toasts.enumerated() {
|
||||
let config = toastConfig
|
||||
var frame = e.window?.frame ?? .zero
|
||||
if i == 0 {
|
||||
frame.origin.y = 44
|
||||
} else {
|
||||
let lastY = toasts[i-1].window?.frame.maxY ?? .zero
|
||||
frame.origin.y = lastY + config.margin
|
||||
}
|
||||
UIView.animateForToast(animations: {
|
||||
e.window?.frame = frame
|
||||
}) { (done) in
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeItemFromArray(toast: Toast) {
|
||||
if toasts.count > 1 {
|
||||
for (i, t) in toasts.enumerated() {
|
||||
if t == toast {
|
||||
toasts.remove(at: i)
|
||||
}
|
||||
}
|
||||
updateToastsLayout()
|
||||
} else if toasts.count == 1 {
|
||||
toasts.removeAll()
|
||||
} else {
|
||||
debugPrint("漏洞:已经没有toast了")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,4 +6,75 @@
|
|||
// Copyright © 2019 Titan Studio. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
public extension ProHUD.Toast {
|
||||
enum Scene {
|
||||
/// 默认场景
|
||||
case `default`
|
||||
|
||||
/// 加载中场景
|
||||
case loading
|
||||
|
||||
/// 确认场景
|
||||
case confirm
|
||||
|
||||
/// 删除场景
|
||||
case delete
|
||||
|
||||
/// 成功场景
|
||||
case success
|
||||
|
||||
/// 警告场景
|
||||
case warning
|
||||
|
||||
/// 错误场景
|
||||
case error
|
||||
|
||||
public var backgroundColor: UIColor {
|
||||
switch self {
|
||||
case .success:
|
||||
return UIColor.green
|
||||
case .warning:
|
||||
return UIColor.yellow
|
||||
case .error:
|
||||
return UIColor.red
|
||||
default:
|
||||
return .clear
|
||||
}
|
||||
}
|
||||
|
||||
public var tintColor: UIColor {
|
||||
switch self {
|
||||
case .success, .error:
|
||||
return .white
|
||||
default:
|
||||
return UIColor.darkGray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ViewModel {
|
||||
|
||||
/// 使用场景
|
||||
public var scene = Scene.default
|
||||
|
||||
/// 标题
|
||||
public var title: String?
|
||||
|
||||
/// 正文
|
||||
public var message: String?
|
||||
|
||||
/// 图标
|
||||
public var icon: UIImage?
|
||||
|
||||
public init(title: String? = nil, message: String? = nil, icon: UIImage? = nil) {
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.icon = icon
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue