This commit is contained in:
xaoxuu 2019-07-31 21:08:25 +08:00
parent 1b665b9b4c
commit 40bf7d38a8
11 changed files with 639 additions and 97 deletions

View File

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

View File

@ -16,29 +16,53 @@ class ViewController: UIViewController {
// Do any additional setup after loading the view. // Do any additional setup after loading the view.
ProHUD.configAlert { (alert) in
alert.minimizeTimeout = 1
}
} }
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let a = ProHUD.show(alert: .delete, title: "确认删除", message: "此操作不可撤销") // let a = ProHUD.show(alert: .delete, title: "", message: "")
//
a.addAction(style: .destructive, title: "删除") { [weak a] in // a.addAction(style: .destructive, title: "") { [weak a] in
a?.updateContent(scene: .success, title: "操作成功", message: "恭喜您已经成功删除了xxx") // a?.updateContent(scene: .success, title: "", message: "xxx")
a?.updateAction(index: 0, style: .default, title: "好的", action: { // a?.updateAction(index: 0, style: .default, title: "", action: {
a?.remove() // a?.remove()
}).removeAction(index: 1) // }).removeAction(index: 1)
// a?.updateContent(scene: .success, title: "").removeAction(index: -1).timeout(2) //// a?.updateContent(scene: .success, title: "").removeAction(index: -1).timeout(2)
}.addAction(style: .cancel, title: "取消", action: nil).didDisappear { // }.addAction(style: .cancel, title: "", action: nil).didDisappear {
debugPrint("didDisappear") // debugPrint("didDisappear")
} // }
//
// ProHUD.show(alert: .delete, title: "", message: "").addAction(style: .destructive, title: "") { // ProHUD.show(alert: .delete, title: "", message: "").addAction(style: .destructive, title: "") {
// //
// }.addAction(style: .cancel, title: "", action: nil).didDisappear { // }.addAction(style: .cancel, title: "", action: nil).didDisappear {
// debugPrint("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) { DispatchQueue.main.asyncAfter(deadline: .now()+1) {

View File

@ -35,8 +35,9 @@ public extension ProHUD.Configuration {
public var iconSize = CGSize(width: 48, height: 48) 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 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 = { lazy var loadSubviews: (ProHUD.Alert) -> Void = {
@ -45,22 +46,21 @@ public extension ProHUD.Configuration {
vc.view.addSubview(vc.contentView) vc.view.addSubview(vc.contentView)
vc.contentView.contentView.addSubview(vc.contentStack) vc.contentView.contentView.addSubview(vc.contentStack)
let config = hud.config.alert vc.contentStack.spacing = alertConfig.margin + alertConfig.padding
vc.contentStack.spacing = config.margin + config.padding
vc.contentView.layer.masksToBounds = true vc.contentView.layer.masksToBounds = true
vc.contentView.layer.cornerRadius = config.cornerRadius vc.contentView.layer.cornerRadius = alertConfig.cornerRadius
vc.contentView.snp.makeConstraints { (mk) in vc.contentView.snp.makeConstraints { (mk) in
mk.center.equalToSuperview() 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 vc.contentStack.snp.makeConstraints { (mk) in
mk.centerX.equalToSuperview() mk.centerX.equalToSuperview()
mk.top.equalToSuperview().offset(config.padding) mk.top.equalToSuperview().offset(alertConfig.padding)
mk.bottom.equalToSuperview().offset(-config.padding) mk.bottom.equalToSuperview().offset(-alertConfig.padding)
mk.leading.equalToSuperview().offset(config.padding) mk.leading.equalToSuperview().offset(alertConfig.padding)
mk.trailing.equalToSuperview().offset(-config.padding) mk.trailing.equalToSuperview().offset(-alertConfig.padding)
} }
} }
@ -70,7 +70,6 @@ public extension ProHUD.Configuration {
lazy var updateFrame: (ProHUD.Alert) -> Void = { lazy var updateFrame: (ProHUD.Alert) -> Void = {
return { (vc) in return { (vc) in
debugPrint(vc, "updateFrame") debugPrint(vc, "updateFrame")
let config = hud.config.alert
let isFirstLayout: Bool let isFirstLayout: Bool
// layout // layout
if vc.textStack.superview == nil && vc.imageView?.superview == nil { if vc.textStack.superview == nil && vc.imageView?.superview == nil {
@ -102,10 +101,10 @@ public extension ProHUD.Configuration {
let icon = UIImageView(image: img) let icon = UIImageView(image: img)
vc.contentStack.addArrangedSubview(icon) vc.contentStack.addArrangedSubview(icon)
icon.snp.makeConstraints { (mk) in icon.snp.makeConstraints { (mk) in
mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding*2.5) mk.top.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*2.5)
mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2.5) mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*2.5)
mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding*4) mk.leading.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*4)
mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*4) mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*4)
} }
vc.imageView = icon vc.imageView = icon
} }
@ -114,10 +113,10 @@ public extension ProHUD.Configuration {
if vc.vm.title?.count ?? 0 > 0 || vc.vm.message?.count ?? 0 > 0 { if vc.vm.title?.count ?? 0 > 0 || vc.vm.message?.count ?? 0 > 0 {
vc.contentStack.addArrangedSubview(vc.textStack) vc.contentStack.addArrangedSubview(vc.textStack)
vc.textStack.snp.makeConstraints { (mk) in vc.textStack.snp.makeConstraints { (mk) in
mk.top.greaterThanOrEqualTo(vc.contentView).offset(config.padding*1.5) mk.top.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*1.5)
mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-config.padding*1.5) mk.bottom.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*1.5)
mk.leading.greaterThanOrEqualTo(vc.contentView).offset(config.padding*2) mk.leading.greaterThanOrEqualTo(vc.contentView).offset(alertConfig.padding*2)
mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2) mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-alertConfig.padding*2)
} }
if vc.vm.title?.count ?? 0 > 0 { if vc.vm.title?.count ?? 0 > 0 {
if let lb = vc.titleLabel { if let lb = vc.titleLabel {
@ -125,7 +124,7 @@ public extension ProHUD.Configuration {
} else { } else {
let title = UILabel() let title = UILabel()
title.textAlignment = .center title.textAlignment = .center
title.numberOfLines = config.titleMaxLines title.numberOfLines = alertConfig.titleMaxLines
title.textColor = UIColor.init(white: 0.2, alpha: 1) title.textColor = UIColor.init(white: 0.2, alpha: 1)
title.text = vc.vm.title title.text = vc.vm.title
vc.textStack.addArrangedSubview(title) vc.textStack.addArrangedSubview(title)
@ -133,10 +132,10 @@ public extension ProHUD.Configuration {
} }
if vc.vm.message?.count ?? 0 > 0 { if vc.vm.message?.count ?? 0 > 0 {
// message // message
vc.titleLabel?.font = config.largeTitleFont vc.titleLabel?.font = alertConfig.largeTitleFont
} else { } else {
// message // message
vc.titleLabel?.font = config.titleFont vc.titleLabel?.font = alertConfig.titleFont
} }
} else { } else {
vc.titleLabel?.removeFromSuperview() vc.titleLabel?.removeFromSuperview()
@ -147,8 +146,8 @@ public extension ProHUD.Configuration {
} else { } else {
let body = UILabel() let body = UILabel()
body.textAlignment = .center body.textAlignment = .center
body.font = config.bodyFont body.font = alertConfig.bodyFont
body.numberOfLines = config.bodyMaxLines body.numberOfLines = alertConfig.bodyMaxLines
body.textColor = UIColor.darkGray body.textColor = UIColor.darkGray
body.text = vc.vm.message body.text = vc.vm.message
vc.textStack.addArrangedSubview(body) vc.textStack.addArrangedSubview(body)
@ -156,10 +155,10 @@ public extension ProHUD.Configuration {
} }
if vc.vm.title?.count ?? 0 > 0 { if vc.vm.title?.count ?? 0 > 0 {
// title // title
vc.messageLabel?.font = config.bodyFont vc.messageLabel?.font = alertConfig.bodyFont
} else { } else {
// title // title
vc.messageLabel?.font = config.titleFont vc.messageLabel?.font = alertConfig.titleFont
} }
} else { } else {
vc.messageLabel?.removeFromSuperview() vc.messageLabel?.removeFromSuperview()
@ -202,10 +201,11 @@ public extension ProHUD.Configuration {
let btn = UIButton.hideButton() let btn = UIButton.hideButton()
vc.view.addSubview(btn) vc.view.addSubview(btn)
btn.snp.makeConstraints { (mk) in 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 vc.addTouchUpAction(for: btn) { [weak vc] in
debugPrint("点击了隐藏") debugPrint("点击了隐藏")
vc?.minimizeCallback?()
vc?.remove() 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)
}
}

View File

@ -18,12 +18,14 @@ public extension ProHUD {
/// icontextStackactionStack) /// icontextStackactionStack)
internal var contentStack: StackContainer = { internal var contentStack: StackContainer = {
let stack = StackContainer() let stack = StackContainer()
stack.spacing = alertConfig.margin
return stack return stack
}() }()
/// ///
internal var textStack: StackContainer = { internal var textStack: StackContainer = {
let stack = StackContainer() let stack = StackContainer()
stack.spacing = alertConfig.margin
return stack return stack
}() }()
internal var imageView: UIImageView? internal var imageView: UIImageView?
@ -33,6 +35,7 @@ public extension ProHUD {
internal var actionStack: StackContainer = { internal var actionStack: StackContainer = {
let stack = StackContainer() let stack = StackContainer()
stack.alignment = .fill stack.alignment = .fill
stack.spacing = alertConfig.margin
return stack return stack
}() }()
@ -42,6 +45,8 @@ public extension ProHUD {
/// ///
internal var showNavButtonsBlock: DispatchWorkItem? internal var showNavButtonsBlock: DispatchWorkItem?
internal var minimizeCallback: (() -> Void)?
// MARK: // MARK:
/// ///
@ -51,7 +56,7 @@ public extension ProHUD {
/// - Parameter icon: /// - Parameter icon:
public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil) { public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil) {
self.init() self.init()
view.tintColor = hud.config.alert.tintColor view.tintColor = alertConfig.tintColor
vm.scene = scene vm.scene = scene
vm.title = title vm.title = title
vm.message = message vm.message = message
@ -85,7 +90,7 @@ public extension ProHUD {
let count = hud.alerts.count let count = hud.alerts.count
if count == 0 && hud.alertWindow != nil { if count == 0 && hud.alertWindow != nil {
UIView.animateFastEaseOut(delay: 0, animations: { 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) window.backgroundColor = window.backgroundColor?.withAlphaComponent(0)
}) { (done) in }) { (done) in
hud.alertWindow = nil hud.alertWindow = nil
@ -130,6 +135,13 @@ public extension ProHUD {
return self return self
} }
///
/// - Parameter callback:
@discardableResult public func didMinimize(_ callback: (() -> Void)?) -> Alert {
minimizeCallback = callback
return self
}
/// ///
/// - Parameter callback: /// - Parameter callback:
@discardableResult public func didDisappear(_ callback: (() -> Void)?) -> Alert { @discardableResult public func didDisappear(_ callback: (() -> Void)?) -> Alert {
@ -144,7 +156,7 @@ public extension ProHUD {
vm.message = message vm.message = message
vm.scene = scene vm.scene = scene
vm.icon = icon vm.icon = icon
hud.config.alert.updateFrame(self) alertConfig.updateFrame(self)
return self return self
} }
@ -206,8 +218,8 @@ fileprivate extension ProHUD.Alert {
willLayout = DispatchWorkItem(block: { [weak self] in willLayout = DispatchWorkItem(block: { [weak self] in
if let a = self { if let a = self {
// //
hud.config.alert.loadSubviews(a) alertConfig.loadSubviews(a)
hud.config.alert.updateFrame(a) alertConfig.updateFrame(a)
// //
a.timeoutBlock?.cancel() a.timeoutBlock?.cancel()
if let t = a.timeout, t > 0 { if let t = a.timeout, t > 0 {
@ -219,14 +231,14 @@ fileprivate extension ProHUD.Alert {
a.timeoutBlock = nil 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?.cancel()
a.showNavButtonsBlock = DispatchWorkItem(block: { [weak self] in a.showNavButtonsBlock = DispatchWorkItem(block: { [weak self] in
if let s = self { 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 alert.view.alpha = 1
}) })
alerts.append(alert) alerts.append(alert)
updateAlertsScale() updateAlertsLayout()
// setup timeout // setup timeout
if let _ = alert.timeout, alert.timeoutBlock == nil { if let _ = alert.timeout, alert.timeoutBlock == nil {
alert.timeout(alert.timeout) alert.timeout(alert.timeout)
@ -302,6 +314,11 @@ public extension ProHUD {
public extension ProHUD { public extension ProHUD {
@discardableResult
class func show(_ alert: Alert) -> Alert {
return shared.show(alert)
}
@discardableResult @discardableResult
class func show(alert: Alert.Scene, title: String? = nil, message: String? = nil, icon: UIImage? = nil) -> Alert { 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) return shared.show(alert: alert, title: title, message: message, icon: icon)
@ -325,7 +342,7 @@ public extension ProHUD {
fileprivate extension ProHUD { fileprivate extension ProHUD {
func updateAlertsScale() { func updateAlertsLayout() {
for (i, a) in alerts.reversed().enumerated() { for (i, a) in alerts.reversed().enumerated() {
let scale = CGFloat(pow(0.7, CGFloat(i))) let scale = CGFloat(pow(0.7, CGFloat(i)))
UIView.animate(withDuration: 2, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseInOut], animations: { 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) alerts.remove(at: i)
} }
} }
updateAlertsScale() updateAlertsLayout()
} else if alerts.count == 1 { } else if alerts.count == 1 {
alerts.removeAll() alerts.removeAll()
} else { } else {

View File

@ -6,8 +6,6 @@
// Copyright © 2019 Titan Studio. All rights reserved. // Copyright © 2019 Titan Studio. All rights reserved.
// //
import UIKit
public extension ProHUD.Alert { public extension ProHUD.Alert {
enum Scene { enum Scene {
/// ///

View File

@ -33,14 +33,14 @@ extension UIButton {
class func actionButton(style: UIAlertAction.Style, title: String?) -> UIButton { class func actionButton(style: UIAlertAction.Style, title: String?) -> UIButton {
let btn = UIButton(type: .system) let btn = UIButton(type: .system)
btn.setTitle(title, for: .normal) btn.setTitle(title, for: .normal)
btn.layer.cornerRadius = hud.config.alert.cornerRadius / 2 btn.layer.cornerRadius = alertConfig.cornerRadius / 2
btn.titleLabel?.font = hud.config.alert.buttonFont btn.titleLabel?.font = alertConfig.buttonFont
btn.update(style: style) btn.update(style: style)
return btn return btn
} }
func update(style: UIAlertAction.Style) { func update(style: UIAlertAction.Style) {
let pd = hud.config.alert.padding/2 let pd = alertConfig.padding/2
if style != .cancel { if style != .cancel {
backgroundColor = .init(white: 0, alpha: 0.04) 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) contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5)

View File

@ -15,7 +15,7 @@ public extension ProHUD {
public override init(frame: CGRect) { public override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
spacing = hud.config.alert.margin spacing = alertConfig.margin
distribution = .fill distribution = .fill
alignment = .center alignment = .center
axis = .vertical axis = .vertical
@ -59,4 +59,12 @@ internal extension UIView {
animateEaseOut(duration: 2, delay: delay, animations: animations, completion: completion) 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)
}
} }

View File

@ -14,35 +14,16 @@ public class ProHUD: NSObject {
public static let shared = ProHUD() public static let shared = ProHUD()
internal var config = Configuration() internal var toasts = [Toast]()
internal var alerts = [Alert]()
public var elements = [String]()
// public var toasts = [Toast]()
public var alerts = [Alert]()
internal var alertWindow: UIWindow? internal var alertWindow: UIWindow?
deinit { deinit {
debugPrint(self, "deinit") debugPrint(self, "deinit")
} }
}
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)
}
} }

View File

@ -30,32 +30,157 @@ public extension ProHUD.Configuration {
public var iconSize = CGSize(width: 48, height: 48) public var iconSize = CGSize(width: 48, height: 48)
/// ///
lazy var loadSubviews: (ProHUD.Alert) -> Void = { lazy var loadSubviews: (ProHUD.Toast) -> Void = {
return { (vc) in return { (vc) in
debugPrint(vc, "loadSubviews") 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 return { (vc) in
debugPrint(vc, "updateFrame") 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: /// - Parameter callback:
public mutating func loadSubviews(_ callback: @escaping (ProHUD.Alert) -> Void) { public mutating func loadSubviews(_ callback: @escaping (ProHUD.Toast) -> Void) {
loadSubviews = callback loadSubviews = callback
} }
/// ///
/// - Parameter callback: /// - Parameter callback:
public mutating func updateFrame(_ callback: @escaping (ProHUD.Alert) -> Void) { public mutating func updateFrame(_ callback: @escaping (ProHUD.Toast) -> Void) {
updateFrame = callback updateFrame = callback
} }
} }
} }
internal var toastConfig = ProHUD.Configuration.Toast()
public extension ProHUD {
static func configToast(_ config: (inout Configuration.Toast) -> Void) {
config(&toastConfig)
}
}

View File

@ -6,4 +6,326 @@
// Copyright © 2019 Titan Studio. All rights reserved. // 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了")
}
}
}

View File

@ -6,4 +6,75 @@
// Copyright © 2019 Titan Studio. All rights reserved. // 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
}
}
}