This commit is contained in:
xaoxuu 2019-08-12 15:02:36 +08:00
parent 26d7aa0049
commit ca6c36650b
17 changed files with 608 additions and 641 deletions

View File

@ -22,8 +22,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true
}

View File

@ -17,6 +17,8 @@ class ViewController: UIViewController {
ProHUD.config { (cfg) in
cfg.rootViewController = self
cfg.alert { (a) in
a.duration = 1
a.forceQuitTimer = 3
@ -29,7 +31,11 @@ class ViewController: UIViewController {
}
cfg.toast { (t) in
// t.iconSize = .init(width: 300, height: 30)
// t.iconSize = .init(width: 30, height: 30)
// t.iconForScene { (scene) -> UIImage? in
// return UIImage(named: "icon_download")
// }
}
}
@ -51,6 +57,7 @@ class ViewController: UIViewController {
}
a.update { (vm) in
vm.add(action: .default, title: "OK", handler: nil)
}
// a.update()
// Alert.push(scene: .loading, title: "Loading") { (a) in
@ -127,67 +134,107 @@ class ViewController: UIViewController {
}
func testToast() {
let t = Toast(scene: .loading, title: "正在加载", message: "请稍候片刻")
let a = Alert.push(scene : .loading, title: "正在加载", message: "请稍候片刻")
a.didForceQuit {
t.push()
}
t.didTapped { [weak t] in
t?.pop()
Alert.push(scene: .loading, title: "正在加载", message: "马上就要成功了")
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
Alert.push(scene: .error, title: "加载失败", message: "点击充实") { (vm) in
vm.duration = 0
vm.identifier = "hehe"
let a = vm.vc!
vm.add(action: .default, title: "重新加载") {
a.vm.scene = .success
a.vm.title = "加载成功"
a.vm.message = "马上就要成功了aaaa"
a.vm.remove(action: 1, 2)
a.vm.update(action: 0, style: .default, title: "OK") { [weak a] in
a?.pop()
}
}
vm.add(action: .destructive, title: "终止", handler: nil)
vm.add(action: .cancel, title: "取消", handler: nil)
}
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
if let a = Alert.alerts("hehe").last {
a.update { (vm) in
vm.add(action: .cancel, title: "CANCEL", handler: nil)
}
}
}
func f() {
Toast.push { (vm) in
vm.scene = .error
vm.title = "正在加载"
// vm.duration = 1
vm.message = "请稍候片刻"
}.didTapped {
debugPrint("didTapped")
}.didDisappear {
debugPrint("didDisappear")
}
}
f()
// let t = Toast(scene: .loading, title: "", message: "")
// let a = Alert.push(scene : .loading, title: "", message: "")
// a.didForceQuit {
// t.push()
// }
//
// t.didTapped { [weak t] in
// t?.pop()
// Alert.push(scene: .loading, title: "", message: "")
// DispatchQueue.main.asyncAfter(deadline: .now()+1) {
// Alert.push(scene: .error, title: "", message: "") { (vm) in
// vm.duration = 0
// vm.identifier = "hehe"
// let a = vm.vc!
// vm.add(action: .default, title: "") {
// a.vm.scene = .success
// a.vm.title = ""
// a.vm.message = "aaaa"
// a.vm.remove(action: 1, 2)
// a.vm.update(action: 0, style: .default, title: "OK") { [weak a] in
// a?.pop()
// }
//
// }
// vm.add(action: .destructive, title: "", handler: nil)
// vm.add(action: .cancel, title: "", handler: nil)
// }
//
// DispatchQueue.main.asyncAfter(deadline: .now()+1) {
// if let a = Alert.alerts("hehe").last {
// a.update { (vm) in
// vm.add(action: .cancel, title: "CANCEL", handler: nil)
// }
//
// }
// }
//
//
// }
//
// }
}
func testGuard() {
let g = ProHUD.Guard(title: "请求权限", message: "请打开相机权限开关,否则无法进行测量。")
g.add(title: "呵呵")
g.add(message: "请打开相机权限开关,否则无法进行测量。请打开相机权限开关,否则无法进行测量。")
g.add(action: .default, title: "测试弹窗", handler: { [weak self] in
self?.testToast()
})
g.add(action: .destructive, title: "测试删除弹窗", handler: { [weak self] in
self?.testDelete()
})
g.add(action: .cancel, title: "我知道了", handler: nil)
g.push(to: self)
debugPrint("test: ", g)
Guard.push { (vm) in
vm.add(title: "大标题")
vm.add(subTitle: "副标题")
vm.add(message: "请打开相机权限开关,否则无法进行测量。请打开相机权限开关,否则无法进行测量。")
vm.add(action: .default, title: "OK") { [weak vm] in
vm?.insert(action: 0, style: .destructive, title: "Del") { [weak vm] in
vm?.update(action: 0, style: .destructive, title: "Delete") {
vm?.remove(action: 0)
}
}
}
vm.insert(action: 0, style: .destructive, title: "Del") { [weak vm] in
vm?.update(action: 0, style: .destructive, title: "Delete") {
vm?.remove(action: 0)
}
}
vm.add(action: .cancel, title: "Cancel") {
}
}
// let g = ProHUD.Guard(title: "", message: "")
//
// g.add(title: "")
// g.add(message: "")
// g.add(action: .default, title: "", handler: { [weak self] in
// self?.testToast()
// })
// g.add(action: .destructive, title: "", handler: { [weak self] in
// self?.testDelete()
// })
// g.add(action: .cancel, title: "", handler: nil)
//
// g.push(to: self)
// debugPrint("test: ", g)
}
func testUpdateAction() {

View File

@ -10,16 +10,13 @@
1AE9C44ABAF3F797A5518CE8 /* Pods_ProHUD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2011798511AD590A613E54E /* Pods_ProHUD.framework */; };
CD16490B22EF09AB0077988C /* AlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16490A22EF09AB0077988C /* AlertModel.swift */; };
CD16490D22EF09B40077988C /* AlertConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16490C22EF09B40077988C /* AlertConfig.swift */; };
CD16490F22EF09D50077988C /* AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16490E22EF09D50077988C /* AlertView.swift */; };
CD16491222EF0D900077988C /* HUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16491122EF0D900077988C /* HUDView.swift */; };
CD16491422EF12220077988C /* ProHUD.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD16491322EF12220077988C /* ProHUD.xcassets */; };
CD6CD86C22F1858F00F4FD4A /* ToastModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD86B22F1858F00F4FD4A /* ToastModel.swift */; };
CD6CD86E22F185A000F4FD4A /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD86D22F185A000F4FD4A /* ToastView.swift */; };
CD6CD87022F185A700F4FD4A /* ToastController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD86F22F185A700F4FD4A /* ToastController.swift */; };
CD6CD87222F185AF00F4FD4A /* ToastConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD87122F185AF00F4FD4A /* ToastConfig.swift */; };
CD6CD87522F185C200F4FD4A /* GuardController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD87422F185C200F4FD4A /* GuardController.swift */; };
CD6CD87922F185D000F4FD4A /* GuardConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD87822F185D000F4FD4A /* GuardConfig.swift */; };
CD6CD87B22F185D600F4FD4A /* GuardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6CD87A22F185D600F4FD4A /* GuardView.swift */; };
CD95D22122E72C4C007559A3 /* ProHUD.h in Headers */ = {isa = PBXBuildFile; fileRef = CD95D21F22E72C4C007559A3 /* ProHUD.h */; settings = {ATTRIBUTES = (Public, ); }; };
CD95D26922E72DA1007559A3 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD95D26822E72DA1007559A3 /* AlertController.swift */; };
CD95D26B22E72DB3007559A3 /* ProHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD95D26A22E72DB3007559A3 /* ProHUD.swift */; };
@ -33,16 +30,13 @@
C2011798511AD590A613E54E /* Pods_ProHUD.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ProHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CD16490A22EF09AB0077988C /* AlertModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertModel.swift; sourceTree = "<group>"; };
CD16490C22EF09B40077988C /* AlertConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertConfig.swift; sourceTree = "<group>"; };
CD16490E22EF09D50077988C /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = "<group>"; };
CD16491122EF0D900077988C /* HUDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDView.swift; sourceTree = "<group>"; };
CD16491322EF12220077988C /* ProHUD.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = ProHUD.xcassets; sourceTree = "<group>"; };
CD6CD86B22F1858F00F4FD4A /* ToastModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastModel.swift; sourceTree = "<group>"; };
CD6CD86D22F185A000F4FD4A /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
CD6CD86F22F185A700F4FD4A /* ToastController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastController.swift; sourceTree = "<group>"; };
CD6CD87122F185AF00F4FD4A /* ToastConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastConfig.swift; sourceTree = "<group>"; };
CD6CD87422F185C200F4FD4A /* GuardController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardController.swift; sourceTree = "<group>"; };
CD6CD87822F185D000F4FD4A /* GuardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardConfig.swift; sourceTree = "<group>"; };
CD6CD87A22F185D600F4FD4A /* GuardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardView.swift; sourceTree = "<group>"; };
CD95D21C22E72C4C007559A3 /* ProHUD.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ProHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CD95D21F22E72C4C007559A3 /* ProHUD.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProHUD.h; sourceTree = "<group>"; };
CD95D22022E72C4C007559A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -90,7 +84,6 @@
CD95D26822E72DA1007559A3 /* AlertController.swift */,
CD16490A22EF09AB0077988C /* AlertModel.swift */,
CD16490C22EF09B40077988C /* AlertConfig.swift */,
CD16490E22EF09D50077988C /* AlertView.swift */,
);
path = Alert;
sourceTree = "<group>";
@ -99,7 +92,6 @@
isa = PBXGroup;
children = (
CD6CD86B22F1858F00F4FD4A /* ToastModel.swift */,
CD6CD86D22F185A000F4FD4A /* ToastView.swift */,
CD6CD86F22F185A700F4FD4A /* ToastController.swift */,
CD6CD87122F185AF00F4FD4A /* ToastConfig.swift */,
);
@ -111,7 +103,6 @@
children = (
CD6CD87422F185C200F4FD4A /* GuardController.swift */,
CD6CD87822F185D000F4FD4A /* GuardConfig.swift */,
CD6CD87A22F185D600F4FD4A /* GuardView.swift */,
CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */,
);
path = Guard;
@ -280,12 +271,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CD6CD87B22F185D600F4FD4A /* GuardView.swift in Sources */,
CD95D26922E72DA1007559A3 /* AlertController.swift in Sources */,
CD6CD87922F185D000F4FD4A /* GuardConfig.swift in Sources */,
CDB6A07D22EEF19D00AF6CF0 /* HUDConfig.swift in Sources */,
CD16490F22EF09D50077988C /* AlertView.swift in Sources */,
CD6CD86E22F185A000F4FD4A /* ToastView.swift in Sources */,
CD6CD87222F185AF00F4FD4A /* ToastConfig.swift in Sources */,
CD6CD87522F185C200F4FD4A /* GuardController.swift in Sources */,
CD6CD87022F185A700F4FD4A /* ToastController.swift in Sources */,

View File

@ -22,12 +22,14 @@ public extension ProHUD.Configuration {
///
public var padding = CGFloat(16)
// MARK:
/// default
///
public var tintColor: UIColor?
// MARK:
///
public var iconSize = CGSize(width: 48, height: 48)
///
/// - Parameter callback:
public func iconForScene(_ callback: @escaping (ProHUD.Alert.Scene) -> UIImage?) {
privIconForScene = callback
}
@ -38,7 +40,7 @@ public extension ProHUD.Configuration {
///
public var titleMaxLines = Int(1)
///
///
public var boldTextFont = UIFont.boldSystemFont(ofSize: 18)
///
@ -78,7 +80,6 @@ public extension ProHUD.Configuration {
// MARK: -
internal extension ProHUD.Configuration.Alert {
var reloadData: (ProHUD.Alert) -> Void {
return privReloadData
@ -87,7 +88,6 @@ internal extension ProHUD.Configuration.Alert {
// MARK: -
fileprivate var privLayoutContentView: (ProHUD.Alert) -> Void = {
return { (vc) in
if vc.contentView.superview == nil {
@ -263,7 +263,7 @@ fileprivate var privLoadForceQuitButton: (ProHUD.Alert) -> Void = {
let config = cfg.alert
let btn = ProHUD.Alert.Button.forceQuitButton()
btn.setTitle(cfg.alert.forceQuitTitle, for: .normal)
let bg = ProHUD.BlurView()
let bg = createBlurView()
bg.layer.masksToBounds = true
bg.layer.cornerRadius = config.cornerRadius
if let last = vc.view.subviews.last {

View File

@ -19,7 +19,7 @@ public extension ProHUD {
internal static var alertWindow: UIWindow?
///
public var contentView = BlurView()
public var contentView = createBlurView()
/// icontextStackactionStack)
public var contentStack: StackContainer = {
@ -88,12 +88,10 @@ public extension ProHUD {
public extension Alert {
// MARK:
///
@discardableResult func push() -> Alert {
if Alert.alerts.contains(self) == false {
let window = Alert.getAlertWindow(self)
let window = Alert.privGetAlertWindow(self)
window.makeKeyAndVisible()
window.resignKey()
window.addSubview(view)
@ -106,14 +104,14 @@ public extension Alert {
}
Alert.alerts.append(self)
}
Alert.updateAlertsLayout()
Alert.privUpdateAlertsLayout()
return self
}
///
func pop() {
let window = Alert.getAlertWindow(self)
Alert.removeItemFromArray(alert: self)
let window = Alert.privGetAlertWindow(self)
Alert.privRemoveItemFromArray(alert: self)
UIView.animateForAlertBuildOut(animations: {
self.view.alpha = 0
self.view.transform = .init(scaleX: 1.08, y: 1.08)
@ -132,29 +130,24 @@ public extension Alert {
}
}
// MARK:
///
/// - Parameter callback:
func update(_ callback: ((inout ViewModel) -> Void)? = nil) {
callback?(&vm)
cfg.alert.reloadData(self)
}
///
/// - Parameter callback:
@discardableResult func didForceQuit(_ callback: (() -> Void)?) -> Alert {
func didForceQuit(_ callback: (() -> Void)?) {
vm.forceQuitCallback = callback
return self
}
///
/// - Parameter callback:
@discardableResult func didDisappear(_ callback: (() -> Void)?) -> Alert {
func didDisappear(_ callback: (() -> Void)?) {
disappearCallback = callback
return self
}
///
/// - Parameter callback:
func update(_ callback: ((inout Alert.ViewModel) -> Void)? = nil) {
callback?(&vm)
cfg.alert.reloadData(self)
}
func animate(rotate: Bool) {
@ -177,8 +170,7 @@ public extension Alert {
}
// MARK:
// MARK: -
public extension Alert {
///
@ -186,7 +178,7 @@ public extension Alert {
/// - Parameter title:
/// - Parameter message:
/// - Parameter actions:
@discardableResult class func push(scene: Alert.Scene, title: String? = nil, message: String? = nil, actions: ((inout Alert.ViewModel) -> Void)? = nil) -> Alert {
@discardableResult class func push(scene: Alert.Scene = .default, title: String? = nil, message: String? = nil, actions: ((inout ViewModel) -> Void)? = nil) -> Alert {
return Alert(scene: scene, title: title, message: message, actions: actions).push()
}
@ -218,45 +210,30 @@ public extension Alert {
}
// MARK: -
// MARK: -
internal extension Alert {
///
/// - Parameter index:
@discardableResult func privRemoveAction(index: Int) -> Alert {
if index < 0 {
for view in self.actionStack.arrangedSubviews {
if let btn = view as? UIButton {
btn.removeFromSuperview()
}
}
} else if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton {
btn.removeFromSuperview()
}
if self.actionStack.arrangedSubviews.count == 0 {
self.actionStack.removeFromSuperview()
}
UIView.animateForAlert {
self.view.layoutIfNeeded()
}
return self
}
@discardableResult func privAddButton(custom button: UIButton, at index: Int? = nil, handler: (() -> Void)?) -> UIButton {
///
/// - Parameter style:
/// - Parameter title:
/// - Parameter action:
@discardableResult func insert(action index: Int?, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
let btn = Button.actionButton(title: title)
if let idx = index, idx < actionStack.arrangedSubviews.count {
actionStack.insertArrangedSubview(btn, at: idx)
} else {
actionStack.addArrangedSubview(btn)
}
btn.update(style: style)
if actionStack.superview == nil {
contentStack.addArrangedSubview(actionStack)
contentStack.layoutIfNeeded()
}
if let idx = index, idx < actionStack.arrangedSubviews.count {
actionStack.insertArrangedSubview(button, at: idx)
} else {
actionStack.addArrangedSubview(button)
}
addTouchUpAction(for: button) { [weak self] in
addTouchUpAction(for: btn) { [weak self] in
handler?()
if button.tag == UIAlertAction.Style.cancel.rawValue {
if btn.tag == UIAlertAction.Style.cancel.rawValue {
self?.pop()
}
}
@ -266,10 +243,10 @@ internal extension Alert {
self.view.layoutIfNeeded()
}
}
return button
return btn
}
func privUpdateButton(action index: Int, style: UIAlertAction.Style, title: String?, _ handler: (() -> Void)?) {
func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) {
if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton {
btn.setTitle(title, for: .normal)
if let b = btn as? Button {
@ -287,10 +264,30 @@ internal extension Alert {
}
}
///
/// - Parameter index:
@discardableResult func remove(action index: Int) -> Alert {
if index < 0 {
for view in self.actionStack.arrangedSubviews {
if let btn = view as? UIButton {
btn.removeFromSuperview()
}
}
} else if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton {
btn.removeFromSuperview()
}
if self.actionStack.arrangedSubviews.count == 0 {
self.actionStack.removeFromSuperview()
}
UIView.animateForAlert {
self.view.layoutIfNeeded()
}
return self
}
}
fileprivate extension Alert {
class func updateAlertsLayout() {
class func privUpdateAlertsLayout() {
for (i, a) in alerts.reversed().enumerated() {
let scale = CGFloat(pow(0.7, CGFloat(i)))
UIView.animate(withDuration: 1.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseInOut], animations: {
@ -301,7 +298,7 @@ fileprivate extension Alert {
}
}
}
class func getAlertWindow(_ vc: UIViewController) -> UIWindow {
class func privGetAlertWindow(_ vc: UIViewController) -> UIWindow {
if let w = alertWindow {
return w
}
@ -313,7 +310,7 @@ fileprivate extension Alert {
return w
}
class func removeItemFromArray(alert: Alert) {
class func privRemoveItemFromArray(alert: Alert) {
if alerts.count > 1 {
for (i, a) in alerts.enumerated() {
if a == alert {
@ -322,7 +319,7 @@ fileprivate extension Alert {
}
}
}
updateAlertsLayout()
privUpdateAlertsLayout()
} else if alerts.count == 1 {
alerts.removeAll()
} else {

View File

@ -33,7 +33,7 @@ public extension Alert {
}
struct ViewModel {
class ViewModel {
/// ID
public var identifier = String(Date().timeIntervalSince1970)
@ -67,9 +67,8 @@ public extension Alert {
didSet {
durationBlock?.cancel()
if let t = duration, t > 0 {
let v = vc
durationBlock = DispatchWorkItem(block: {
v?.pop()
durationBlock = DispatchWorkItem(block: { [weak self] in
self?.vc?.pop()
})
DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: durationBlock!)
} else {
@ -80,6 +79,8 @@ public extension Alert {
public weak var vc: Alert?
// MARK:
///
internal var durationBlock: DispatchWorkItem?
@ -99,13 +100,9 @@ public extension Alert.ViewModel {
/// - Parameter style:
/// - Parameter text:
/// - Parameter handler:
@discardableResult mutating func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
@discardableResult func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
duration = 0
let btn = vc?.privAddButton(custom: Alert.Button.actionButton(title: title), handler: handler)
if let b = btn as? Alert.Button {
b.update(style: style)
}
return btn!
return vc!.insert(action: nil, style: style, title: title, handler: handler)
}
///
@ -113,13 +110,9 @@ public extension Alert.ViewModel {
/// - Parameter style:
/// - Parameter title:
/// - Parameter handler:
@discardableResult mutating func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
@discardableResult func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
duration = 0
let btn = vc?.privAddButton(custom: Alert.Button.actionButton(title: title), at: index, handler: handler)
if let b = btn as? Alert.Button {
b.update(style: style)
}
return btn!
return vc!.insert(action: index, style: style, title: title, handler: handler)
}
///
@ -127,16 +120,15 @@ public extension Alert.ViewModel {
/// - Parameter style:
/// - Parameter title:
/// - Parameter handler:
mutating func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) {
vc?.privUpdateButton(action: index, style: style, title: title, handler)
func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) {
vc?.update(action: index, style: style, title: title, handler: handler)
}
///
/// - Parameter index:
mutating func remove(action index: Int...) {
guard let alert = self.vc else { return }
func remove(action index: Int...) {
for (i, idx) in index.enumerated() {
alert.privRemoveAction(index: idx-i)
vc?.remove(action: idx-i)
}
}

View File

@ -1,55 +0,0 @@
//
// AlertView.swift
// ProHUD
//
// Created by xaoxuu on 2019/7/29.
// Copyright © 2019 Titan Studio. All rights reserved.
//
import UIKit
internal extension Alert {
class Button: UIButton {
class func actionButton(title: String?) -> UIButton {
let btn = Button(type: .system)
btn.setTitle(title, for: .normal)
btn.layer.cornerRadius = cfg.alert.cornerRadius / 2
btn.titleLabel?.font = cfg.alert.buttonFont
return btn
}
func update(style: UIAlertAction.Style) {
let pd = CGFloat(8)
if style != .cancel {
backgroundColor = cfg.dynamicColor.withAlphaComponent(0.04)
contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5)
} else {
backgroundColor = .clear
contentEdgeInsets = .init(top: pd*0.5, left: pd*1.5, bottom: pd*0.5, right: pd*1.5)
}
switch style {
case .default:
setTitleColor(tintColor, for: .normal)
case .destructive:
setTitleColor(.init(red: 244/255, green: 67/255, blue: 54/255, alpha: 1), for: .normal)
case .cancel:
setTitleColor(cfg.secondaryLabelColor, for: .normal)
@unknown default:
break
}
tag = style.rawValue
}
class func forceQuitButton() -> UIButton {
let btn = Button(type: .system)
let pd = cfg.alert.padding/2
btn.contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5)
btn.imageEdgeInsets.right = pd*1.5
btn.setTitleColor(UIColor(red:1.00, green:0.55, blue:0.21, alpha:1.00), for: .normal)
btn.titleLabel?.font = cfg.alert.buttonFont
return btn
}
}
}

View File

@ -22,7 +22,6 @@ public extension ProHUD.Configuration {
///
public var padding = CGFloat(16)
// MARK:
///
public var tintColor: UIColor?
@ -41,12 +40,6 @@ public extension ProHUD.Configuration {
///
public var buttonCornerRadius = CGFloat(12)
///
/// - Parameter callback:
public mutating func loadSubviews(_ callback: @escaping (ProHUD.Guard) -> Void) {
privLoadSubviews = callback
}
///
/// - Parameter callback:
public mutating func reloadData(_ callback: @escaping (ProHUD.Guard) -> Void) {
@ -56,15 +49,14 @@ public extension ProHUD.Configuration {
}
}
// MARK: -
// MARK: -
internal extension ProHUD.Configuration.Guard {
var loadSubviews: (ProHUD.Guard) -> Void {
return privLoadSubviews
}
var reloadData: (ProHUD.Guard) -> Void {
return privReloadData
}
var reloadStack: (ProHUD.Guard) -> Void {
return { (vc) in
if vc.textStack.arrangedSubviews.count > 0 {
@ -82,14 +74,7 @@ internal extension ProHUD.Configuration.Guard {
}
fileprivate var privLoadSubviews: (ProHUD.Guard) -> Void = {
return { (vc) in
debug(vc, "loadSubviews")
let config = cfg.guard
}
}()
// MARK: -
fileprivate var privReloadData: (ProHUD.Guard) -> Void = {
return { (vc) in
debug(vc, "reloadData")
@ -114,10 +99,10 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = {
vc.contentView.snp.makeConstraints { (mk) in
mk.centerX.equalToSuperview()
if UIDevice.current.userInterfaceIdiom == .phone {
if width == config.cardMaxWidth {
mk.bottom.equalToSuperview().offset(-Inspire.shared.screen.safeAreaInsets.bottom)
} else {
if width < config.cardMaxWidth {
mk.bottom.equalToSuperview()
} else {
mk.bottom.equalToSuperview().offset(-Inspire.shared.screen.safeAreaInsets.bottom)
}
} else if UIDevice.current.userInterfaceIdiom == .pad {
mk.centerY.equalToSuperview()
@ -126,12 +111,17 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = {
}
// stack
vc.contentStack.snp.makeConstraints { (mk) in
mk.top.equalToSuperview().offset(config.padding + config.margin)
mk.top.equalToSuperview().offset(config.padding)
mk.centerX.equalToSuperview()
if width == config.cardMaxWidth {
mk.bottom.equalToSuperview().offset(-config.padding)
if width < config.cardMaxWidth {
let bottom = Inspire.shared.screen.safeAreaInsets.bottom
if bottom == 0 {
mk.bottom.equalToSuperview().offset(-config.padding)
} else {
mk.bottom.equalToSuperview().offset(-config.padding/2 - bottom)
}
} else {
mk.bottom.equalToSuperview().offset(-config.padding-Inspire.shared.screen.safeAreaInsets.bottom)
mk.bottom.equalToSuperview().offset(-config.padding)
}
if isPortrait {
mk.width.equalToSuperview().offset(-config.padding * 2)
@ -139,6 +129,6 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = {
mk.width.equalToSuperview().offset(-config.padding * 4)
}
}
config.reloadStack(vc)
}
}()

View File

@ -15,7 +15,7 @@ public extension ProHUD {
class Guard: HUDController {
///
public var contentView = BlurView()
public var contentView = createBlurView()
/// textStackactionStack)
public var contentStack: StackContainer = {
@ -53,6 +53,7 @@ public extension ProHUD {
public var vm = ViewModel()
// MARK:
internal var isLoadFinished = false
///
/// - Parameter title:
@ -69,29 +70,27 @@ public extension ProHUD {
}
actions?(&vm)
view.tintColor = cfg.guard.tintColor
cfg.guard.loadSubviews(self)
cfg.guard.reloadData(self)
cfg.guard.reloadStack(self)
//
let tap = UITapGestureRecognizer(target: self, action: #selector(privDidTapped(_:)))
view.addGestureRecognizer(tap)
}
public override func viewDidLoad() {
super.viewDidLoad()
view.tintColor = cfg.guard.tintColor
cfg.guard.reloadData(self)
isLoadFinished = true
}
}
}
// MARK: -
public extension Guard {
// MARK:
///
/// - Parameter viewController:
@discardableResult func push(to viewController: UIViewController? = nil) -> Guard {
@ -104,11 +103,11 @@ public extension Guard {
mk.edges.equalToSuperview()
}
if displaying == false {
translateOut()
privTranslateOut()
}
displaying = true
UIView.animateForGuard {
self.translateIn()
self.privTranslateIn()
}
}
if let vc = viewController ?? cfg.rootViewController {
@ -127,7 +126,7 @@ public extension Guard {
view.isUserInteractionEnabled = false
self.removeFromParent()
UIView.animateForGuard(animations: {
self.translateOut()
self.privTranslateOut()
}) { (done) in
if self.displaying == false {
self.view.removeFromSuperview()
@ -136,92 +135,23 @@ public extension Guard {
}
}
// MARK:
///
/// - Parameter text:
@discardableResult func add(title: String?) -> UILabel {
let lb = UILabel()
lb.font = cfg.guard.titleFont
lb.textColor = cfg.primaryLabelColor
lb.numberOfLines = 0
lb.textAlignment = .justified
lb.text = title
textStack.addArrangedSubview(lb)
if #available(iOS 11.0, *) {
let count = textStack.arrangedSubviews.count
if count > 1 {
textStack.setCustomSpacing(cfg.guard.margin * 2, after: textStack.arrangedSubviews[count-2])
}
} else {
// Fallback on earlier versions
}
cfg.guard.reloadStack(self)
return lb
///
/// - Parameter callback:
func update(_ callback: ((inout ViewModel) -> Void)? = nil) {
callback?(&vm)
cfg.guard.reloadData(self)
}
///
/// - Parameter text:
@discardableResult func add(subTitle: String?) -> UILabel {
let lb = add(title: subTitle)
lb.font = cfg.guard.subTitleFont
return lb
}
///
/// - Parameter text:
@discardableResult func add(message: String?) -> UILabel {
let lb = UILabel()
lb.font = cfg.guard.bodyFont
lb.textColor = cfg.secondaryLabelColor
lb.numberOfLines = 0
lb.textAlignment = .justified
lb.text = message
textStack.addArrangedSubview(lb)
cfg.guard.reloadStack(self)
return lb
}
///
/// - Parameter style:
/// - Parameter title:
/// - Parameter action:
@discardableResult func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
let btn = Button.actionButton(title: title)
btn.titleLabel?.font = cfg.guard.buttonFont
actionStack.addArrangedSubview(btn)
cfg.guard.reloadStack(self)
btn.update(style: style)
addTouchUpAction(for: btn) { [weak self] in
handler?()
if btn.tag == UIAlertAction.Style.cancel.rawValue {
self?.pop()
}
}
return btn
}
///
/// - Parameter index:
@discardableResult func remove(action index: Int...) -> Guard {
for (i, idx) in index.enumerated() {
privRemoveAction(index: idx-i)
}
return self
}
///
/// - Parameter callback:
@discardableResult func didDisappear(_ callback: (() -> Void)?) -> Guard {
func didDisappear(_ callback: (() -> Void)?) {
disappearCallback = callback
return self
}
}
// MARK:
// MARK: -
public extension Guard {
///
@ -261,7 +191,7 @@ public extension Guard {
`guard`.pop()
}
///
///
/// - Parameter identifier:
class func pop(from viewController: UIViewController?) {
for g in guards(from: viewController) {
@ -273,36 +203,103 @@ public extension Guard {
// MARK: -
// MARK: -
internal extension Guard {
///
/// - Parameter sender:
@objc func privDidTapped(_ sender: UITapGestureRecognizer) {
let point = sender.location(in: contentView)
if point.x < 0 || point.y < 0 {
if force == false {
//
pop()
///
/// - Parameter text:
@discardableResult func add(title: String?) -> UILabel {
let lb = UILabel()
lb.font = cfg.guard.titleFont
lb.textColor = cfg.primaryLabelColor
lb.numberOfLines = 0
lb.textAlignment = .center
lb.text = title
textStack.addArrangedSubview(lb)
if #available(iOS 11.0, *) {
let count = textStack.arrangedSubviews.count
if count > 1 {
textStack.setCustomSpacing(cfg.guard.margin * 2, after: textStack.arrangedSubviews[count-2])
}
} else {
// Fallback on earlier versions
}
cfg.guard.reloadStack(self)
return lb
}
///
/// - Parameter text:
@discardableResult func add(subTitle: String?) -> UILabel {
let lb = add(title: subTitle)
lb.font = cfg.guard.subTitleFont
lb.textAlignment = .justified
return lb
}
///
/// - Parameter text:
@discardableResult func add(message: String?) -> UILabel {
let lb = UILabel()
lb.font = cfg.guard.bodyFont
lb.textColor = cfg.secondaryLabelColor
lb.numberOfLines = 0
lb.textAlignment = .justified
lb.text = message
textStack.addArrangedSubview(lb)
cfg.guard.reloadStack(self)
return lb
}
///
/// - Parameter index:
/// - Parameter style:
/// - Parameter title:
/// - Parameter action:
@discardableResult func insert(action index: Int?, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
let btn = Button.actionButton(title: title)
btn.titleLabel?.font = cfg.guard.buttonFont
if let idx = index, idx < actionStack.arrangedSubviews.count {
actionStack.insertArrangedSubview(btn, at: idx)
} else {
actionStack.addArrangedSubview(btn)
}
btn.update(style: style)
cfg.guard.reloadStack(self)
addTouchUpAction(for: btn) { [weak self] in
handler?()
if btn.tag == UIAlertAction.Style.cancel.rawValue {
self?.pop()
}
}
if isLoadFinished {
UIView.animateForGuard {
self.view.layoutIfNeeded()
}
}
return btn
}
func translateIn() {
view.backgroundColor = backgroundColor
contentView.transform = .identity
}
func translateOut() {
view.backgroundColor = UIColor(white: 0, alpha: 0)
contentView.transform = .init(translationX: 0, y: view.frame.size.height - contentView.frame.minY + cfg.guard.margin)
func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) {
if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton {
btn.setTitle(title, for: .normal)
if let b = btn as? Button {
b.update(style: style)
}
if let _ = buttonEvents[btn] {
buttonEvents.removeValue(forKey: btn)
}
addTouchUpAction(for: btn) { [weak self] in
handler?()
if btn.tag == UIAlertAction.Style.cancel.rawValue {
self?.pop()
}
}
}
}
///
/// - Parameter index:
@discardableResult func privRemoveAction(index: Int) -> Guard {
@discardableResult func remove(index: Int) -> Guard {
if index < 0 {
for view in self.actionStack.arrangedSubviews {
if let btn = view as? UIButton {
@ -322,3 +319,29 @@ internal extension Guard {
}
fileprivate extension Guard {
///
/// - Parameter sender:
@objc func privDidTapped(_ sender: UITapGestureRecognizer) {
let point = sender.location(in: contentView)
if point.x < 0 || point.y < 0 {
if force == false {
//
pop()
}
}
}
func privTranslateIn() {
view.backgroundColor = backgroundColor
contentView.transform = .identity
}
func privTranslateOut() {
view.backgroundColor = UIColor(white: 0, alpha: 0)
contentView.transform = .init(translationX: 0, y: view.frame.size.height - contentView.frame.minY + cfg.guard.margin)
}
}

View File

@ -10,7 +10,7 @@ import UIKit
public extension Guard {
struct ViewModel {
class ViewModel {
/// ID
public var identifier = String(Date().timeIntervalSince1970)
@ -46,7 +46,7 @@ public extension Guard.ViewModel {
/// - Parameter title:
/// - Parameter action:
@discardableResult func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
return vc!.add(action: style, title: title, handler: handler)
return vc!.insert(action: nil, style: style, title: title, handler: handler)
}
///
@ -54,9 +54,8 @@ public extension Guard.ViewModel {
/// - Parameter style:
/// - Parameter title:
/// - Parameter handler:
@discardableResult mutating func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
return UIButton()
@discardableResult func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton {
return vc!.insert(action: index, style: style, title: title, handler: handler)
}
///
@ -64,15 +63,15 @@ public extension Guard.ViewModel {
/// - Parameter style:
/// - Parameter title:
/// - Parameter handler:
mutating func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) {
// vc?.privUpdateButton(action: index, style: style, title: title, handler)
func update(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) {
vc?.update(action: index, style: style, title: title, handler: handler)
}
///
/// - Parameter index:
func remove(action index: Int...) {
for (i, idx) in index.enumerated() {
vc!.privRemoveAction(index: idx-i)
vc?.remove(index: idx-i)
}
}

View File

@ -1,46 +0,0 @@
//
// GuardView.swift
// ProHUD
//
// Created by xaoxuu on 2019/7/31.
// Copyright © 2019 Titan Studio. All rights reserved.
//
import UIKit
internal extension Guard {
class Button: UIButton {
class func actionButton(title: String?) -> Button {
let btn = Button(type: .system)
btn.setTitle(title, for: .normal)
btn.layer.cornerRadius = cfg.guard.buttonCornerRadius
btn.titleLabel?.font = cfg.guard.buttonFont
return btn
}
func update(style: UIAlertAction.Style) {
let pd = CGFloat(8)
if style != .cancel {
contentEdgeInsets = .init(top: pd*1.5+2, left: pd*1.5, bottom: pd*1.5+2, right: pd*1.5)
} else {
contentEdgeInsets = .init(top: pd*1+2, left: pd*1.5, bottom: pd*1+2, right: pd*1.5)
}
switch style {
case .default:
backgroundColor = tintColor
setTitleColor(.white, for: .normal)
case .destructive:
backgroundColor = .init(red: 244/255, green: 67/255, blue: 54/255, alpha: 1)
setTitleColor(.white, for: .normal)
case .cancel:
setTitleColor(cfg.secondaryLabelColor, for: .normal)
@unknown default:
break
}
tag = style.rawValue
}
}
}

View File

@ -19,19 +19,19 @@ public extension ProHUD {
/// iOS13
public lazy var dynamicColor: UIColor = {
// if #available(iOS 13.0, *) {
// let color = UIColor { (traitCollection: UITraitCollection) -> UIColor in
// if traitCollection.userInterfaceStyle == .dark {
// return .white
// } else {
// return .black
// }
// }
// return color
// } else {
// // Fallback on earlier versions
// }
return .init(white: 0.15, alpha: 1)
if #available(iOS 13.0, *) {
let color = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return .init(white: 1, alpha: 1)
} else {
return .init(white: 0.1, alpha: 1)
}
}
return color
} else {
// Fallback on earlier versions
}
return .init(white: 0.1, alpha: 1)
}()
///
@ -41,9 +41,13 @@ public extension ProHUD {
///
public lazy var secondaryLabelColor: UIColor = {
return dynamicColor.withAlphaComponent(0.6)
return dynamicColor.withAlphaComponent(0.66)
}()
public func blurView(_ callback: @escaping () -> UIVisualEffectView) {
createBlurView = callback
}
public var toast = Toast()
public var alert = Alert()
@ -78,3 +82,19 @@ public extension ProHUD {
config(&cfg)
}
}
internal var createBlurView: () -> UIVisualEffectView = {
return {
let vev = UIVisualEffectView()
if #available(iOS 13.0, *) {
vev.effect = UIBlurEffect(style: .systemMaterial)
// vev.effect = UIBlurEffect(style: .extraLight)
} else if #available(iOS 11.0, *) {
vev.effect = UIBlurEffect(style: .extraLight)
} else {
vev.effect = .none
vev.backgroundColor = .white
}
return vev
}
}()

View File

@ -27,34 +27,94 @@ public extension ProHUD {
}
class BlurView: UIVisualEffectView {
init() {
if #available(iOS 13.0, *) {
// super.init(effect: UIBlurEffect(style: .systemMaterial))
super.init(effect: UIBlurEffect(style: .extraLight))
} else if #available(iOS 11.0, *) {
super.init(effect: UIBlurEffect(style: .extraLight))
} else {
super.init(effect: .none)
backgroundColor = .white
}
}
internal extension Alert {
class Button: UIButton {
class func actionButton(title: String?) -> Button {
let btn = Button(type: .system)
btn.setTitle(title, for: .normal)
btn.layer.cornerRadius = cfg.alert.cornerRadius / 2
btn.titleLabel?.font = cfg.alert.buttonFont
return btn
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
func update(style: UIAlertAction.Style) {
let pd = CGFloat(8)
if style != .cancel {
backgroundColor = cfg.dynamicColor.withAlphaComponent(0.04)
contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5)
} else {
backgroundColor = .clear
contentEdgeInsets = .init(top: pd*0.5, left: pd*1.5, bottom: pd*0.5, right: pd*1.5)
}
switch style {
case .default:
setTitleColor(tintColor, for: .normal)
case .destructive:
setTitleColor(.init(red: 244/255, green: 67/255, blue: 54/255, alpha: 1), for: .normal)
case .cancel:
setTitleColor(cfg.secondaryLabelColor, for: .normal)
@unknown default:
break
}
tag = style.rawValue
}
class func forceQuitButton() -> UIButton {
let btn = Button(type: .system)
let pd = cfg.alert.padding/2
btn.contentEdgeInsets = .init(top: pd*1.5, left: pd*1.5, bottom: pd*1.5, right: pd*1.5)
btn.imageEdgeInsets.right = pd*1.5
btn.setTitleColor(UIColor(red:1.00, green:0.55, blue:0.21, alpha:1.00), for: .normal)
btn.titleLabel?.font = cfg.alert.buttonFont
return btn
}
}
}
internal extension Guard {
class Button: UIButton {
class func actionButton(title: String?) -> Button {
let btn = Button(type: .system)
btn.setTitle(title, for: .normal)
btn.layer.cornerRadius = cfg.guard.buttonCornerRadius
btn.titleLabel?.font = cfg.guard.buttonFont
return btn
}
func update(style: UIAlertAction.Style) {
let pd = CGFloat(8)
if style != .cancel {
contentEdgeInsets = .init(top: pd*1.5+2, left: pd*1.5, bottom: pd*1.5+2, right: pd*1.5)
} else {
contentEdgeInsets = .init(top: pd*1+2, left: pd*1.5, bottom: pd*1+2, right: pd*1.5)
}
switch style {
case .default:
backgroundColor = tintColor
setTitleColor(.white, for: .normal)
case .destructive:
backgroundColor = .init(red: 244/255, green: 67/255, blue: 54/255, alpha: 1)
setTitleColor(.white, for: .normal)
case .cancel:
backgroundColor = .clear
setTitleColor(cfg.secondaryLabelColor, for: .normal)
@unknown default:
break
}
tag = style.rawValue
}
}
}
internal extension UIView {
class func animateEaseOut(duration: TimeInterval = 1, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
private class func animateEaseOut(duration: TimeInterval = 1, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
animate(withDuration: duration, delay: delay, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.allowUserInteraction, .curveEaseOut], animations: animations, completion: completion)
}
@ -64,27 +124,26 @@ internal extension UIView {
class func animateForAlertBuildOut(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
animateEaseOut(duration: 0.38, delay: 0, animations: animations, completion: completion)
}
class func animateForAlert(animations: @escaping () -> Void) {
animateEaseOut(duration: 1, delay: 0, animations: animations, completion: nil)
}
class func animateForAlert(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
animateEaseOut(duration: 1, delay: 0, animations: animations, completion: completion)
}
class func animateForToast(animations: @escaping () -> Void) {
animateEaseOut(duration: 1, delay: 0, animations: animations, completion: nil)
class func animateForAlert(animations: @escaping () -> Void) {
animateForAlert(animations: animations, completion: nil)
}
class func animateForToast(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
animateEaseOut(duration: 1, delay: 0, animations: animations, completion: completion)
animateEaseOut(duration: 1.2, delay: 0, animations: animations, completion: completion)
}
class func animateForGuard(animations: @escaping () -> Void) {
animateForGuard(animations: animations, completion: nil)
class func animateForToast(animations: @escaping () -> Void) {
animateForToast(animations: animations, completion: nil)
}
class func animateForGuard(animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
animateEaseOut(duration: 0.6, delay: 0, animations: animations, completion: completion)
}
class func animateForGuard(animations: @escaping () -> Void) {
animateForGuard(animations: animations, completion: nil)
}
}

View File

@ -21,10 +21,13 @@ public extension ProHUD.Configuration {
public var padding = CGFloat(16)
// MARK:
/// default
public var tintColor: UIColor?
///
public var iconSize = CGSize(width: 48, height: 48)
///
/// - Parameter callback:
public func iconForScene(_ callback: @escaping (ProHUD.Toast.Scene) -> UIImage?) {
privIconForScene = callback
}
// MARK:
///
@ -37,71 +40,49 @@ public extension ProHUD.Configuration {
///
public var bodyMaxLines = Int(10)
/// View
/// - Parameter callback:
public mutating func loadSubviews(_ callback: @escaping (ProHUD.Toast) -> Void) {
privLoadSubviews = callback
}
///
/// - Parameter callback:
public mutating func reloadData(_ callback: @escaping (ProHUD.Toast) -> Void) {
privReloadData = callback
}
/// Loading
public var duration = TimeInterval(3)
}
}
// MARK: -
// MARK: -
internal extension ProHUD.Configuration.Toast {
var loadSubviews: (ProHUD.Toast) -> Void {
return privLoadSubviews
}
var reloadData: (ProHUD.Toast) -> Void {
return privReloadData
}
}
fileprivate var privLoadSubviews: (ProHUD.Toast) -> Void = {
return { (vc) in
debug(vc, "loadSubviews")
vc.view.tintColor = cfg.toast.tintColor
vc.view.addSubview(vc.titleLabel)
vc.view.addSubview(vc.bodyLabel)
vc.view.addSubview(vc.imageView)
}
}()
// MARK: -
fileprivate var privReloadData: (ProHUD.Toast) -> Void = {
return { (vc) in
debug(vc, "reloadData")
let config = cfg.toast
let scene = vc.model.scene
//
let imgStr: String
switch vc.model.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 scene = vc.vm.scene
if vc.titleLabel.superview == nil {
vc.view.addSubview(vc.titleLabel)
}
let img = vc.model.icon ?? ProHUD.image(named: imgStr)
vc.imageView.image = img
if vc.bodyLabel.superview == nil {
vc.view.addSubview(vc.bodyLabel)
}
if vc.imageView.superview == nil {
vc.view.addSubview(vc.imageView)
}
//
vc.imageView.image = vc.vm.icon ?? privIconForScene(vc.vm.scene)
vc.titleLabel.textColor = cfg.primaryLabelColor
vc.titleLabel.text = vc.model.title
vc.titleLabel.text = vc.vm.title
vc.bodyLabel.textColor = cfg.secondaryLabelColor
vc.bodyLabel.text = vc.model.message
vc.bodyLabel.text = vc.vm.message
//
vc.imageView.snp.makeConstraints { (mk) in
@ -123,13 +104,35 @@ fileprivate var privReloadData: (ProHUD.Toast) -> Void = {
}
vc.view.layoutIfNeeded()
switch vc.model.scene {
case .loading:
vc.duration(nil)
default:
vc.duration(3)
//
if vc.vm.duration == nil {
if vc.vm.scene == .loading {
vc.vm.duration = 0
} else {
vc.vm.duration = config.duration
}
}
}
}()
fileprivate var privIconForScene: (ProHUD.Toast.Scene) -> UIImage? = {
return { (scene) in
let imgStr: String
switch scene {
case .success:
imgStr = "ProHUDSuccess"
case .warning:
imgStr = "ProHUDWarning"
case .error:
imgStr = "ProHUDError"
case .loading:
imgStr = "ProHUDLoading"
default:
imgStr = "ProHUDMessage"
}
return ProHUD.image(named: imgStr)
}
}()

View File

@ -47,23 +47,16 @@ public extension ProHUD {
///
public var backgroundView: UIVisualEffectView = {
let vev = UIVisualEffectView()
if #available(iOS 13.0, *) {
// vev.effect = UIBlurEffect.init(style: .systemMaterial))
vev.effect = UIBlurEffect.init(style: .extraLight)
} else if #available(iOS 11.0, *) {
vev.effect = UIBlurEffect.init(style: .extraLight)
} else {
vev.effect = .none
vev.backgroundColor = .white
}
let vev = createBlurView()
vev.layer.masksToBounds = true
vev.layer.cornerRadius = cfg.toast.cornerRadius
return vev
}()
///
public var model = ViewModel()
public var vm = ViewModel()
internal var maxY = CGFloat(0)
// MARK:
@ -72,17 +65,15 @@ public extension ProHUD {
/// - Parameter title:
/// - Parameter message:
/// - Parameter icon:
public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil, actions: ((Toast) -> Void)? = nil) {
public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil, actions: ((inout ViewModel) -> Void)? = nil) {
self.init()
vm.vc = self
model.scene = scene
model.title = title
model.message = message
model.icon = icon
actions?(self)
//
cfg.toast.loadSubviews(self)
cfg.toast.reloadData(self)
vm.scene = scene
vm.title = title
vm.message = message
vm.icon = icon
actions?(&vm)
//
let tap = UITapGestureRecognizer(target: self, action: #selector(privDidTapped(_:)))
@ -94,16 +85,21 @@ public extension ProHUD {
}
public override func viewDidLoad() {
super.viewDidLoad()
cfg.toast.reloadData(self)
}
}
}
// MARK: -
public extension Toast {
// MARK:
///
@discardableResult func push() -> Toast {
let config = cfg.toast
@ -145,7 +141,7 @@ public extension Toast {
if Toast.toasts.contains(self) == false {
Toast.toasts.append(self)
}
Toast.updateToastsLayout()
Toast.privUpdateToastsLayout()
if isNew {
window.transform = .init(translationX: 0, y: -window.frame.maxY)
UIView.animateForToast {
@ -159,32 +155,21 @@ public extension Toast {
///
func pop() {
Toast.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
}
Toast.pop(self)
}
// MARK:
///
/// - Parameter duration:
@discardableResult func duration(_ duration: TimeInterval?) -> Toast {
model.setupDuration(duration: duration) { [weak self] in
self?.pop()
}
return self
///
/// - Parameter callback:
func update(_ callback: ((inout ViewModel) -> Void)? = nil) {
callback?(&vm)
cfg.toast.reloadData(self)
}
///
/// - Parameter callback:
@discardableResult func didTapped(_ callback: (() -> Void)?) -> Toast {
model.tapCallback = callback
vm.tapCallback = callback
return self
}
@ -195,47 +180,10 @@ public extension Toast {
return self
}
///
/// - Parameter scene:
/// - Parameter title:
/// - Parameter message:
@discardableResult func update(scene: Scene, title: String?, message: String?) -> Toast {
model.scene = scene
model.title = title
model.message = message
cfg.toast.reloadData(self)
return self
}
///
/// - Parameter title:
@discardableResult func update(title: String?) -> Toast {
model.title = title
cfg.toast.reloadData(self)
return self
}
///
/// - Parameter message:
@discardableResult func update(message: String?) -> Toast {
model.message = message
cfg.toast.reloadData(self)
return self
}
///
/// - Parameter icon:
@discardableResult func update(icon: UIImage?) -> Toast {
model.icon = icon
cfg.toast.reloadData(self)
return self
}
}
// MARK:
// MARK: -
public extension Toast {
///
@ -243,7 +191,7 @@ public extension Toast {
/// - Parameter title:
/// - Parameter message:
/// - Parameter actions:
@discardableResult class func push(scene: Toast.Scene, title: String? = nil, message: String? = nil, actions: ((Toast) -> Void)? = nil) -> Toast {
@discardableResult class func push(scene: Toast.Scene = .default, title: String? = nil, message: String? = nil, actions: ((inout ViewModel) -> Void)? = nil) -> Toast {
return Toast(scene: scene, title: title, message: message, actions: actions).push()
}
@ -252,7 +200,7 @@ public extension Toast {
class func toasts(_ identifier: String?) -> [Toast] {
var tt = [Toast]()
for t in toasts {
if t.model.identifier == identifier {
if t.vm.identifier == identifier {
tt.append(t)
}
}
@ -262,7 +210,25 @@ public extension Toast {
///
/// - Parameter toast:
class func pop(_ toast: Toast) {
toast.pop()
if toasts.count > 1 {
for (i, t) in toasts.enumerated() {
if t == toast {
toasts.remove(at: i)
}
}
privUpdateToastsLayout()
} else if toasts.count == 1 {
toasts.removeAll()
} else {
debug("漏洞已经没有toast了")
}
UIView.animateForToast(animations: {
toast.window?.transform = .init(translationX: 0, y: -20-toast.maxY)
}) { (done) in
toast.view.removeFromSuperview()
toast.removeFromParent()
toast.window = nil
}
}
///
@ -275,57 +241,40 @@ public extension Toast {
}
// MARK:
fileprivate var willUpdateToastsLayout: DispatchWorkItem?
// MARK: -
fileprivate var willprivUpdateToastsLayout: DispatchWorkItem?
fileprivate extension Toast {
///
/// - Parameter sender:
@objc func privDidTapped(_ sender: UITapGestureRecognizer) {
model.tapCallback?()
vm.tapCallback?()
}
///
/// - Parameter sender:
@objc func privDidPan(_ sender: UIPanGestureRecognizer) {
model.durationBlock?.cancel()
vm.durationBlock?.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 model.removable == true && (((window?.frame.origin.y ?? 0) < 0 && v.y < 0) || v.y < -1200) {
if vm.removable == true && (((window?.frame.origin.y ?? 0) < 0 && v.y < 0) || v.y < -1200) {
//
self.pop()
} else {
UIView.animateForToast(animations: {
self.window?.transform = .identity
}) { (done) in
// FIXME:
let d = self.vm.duration
self.vm.duration = d
}
}
}
}
///
/// - Parameter toast:
class 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 {
debug("漏洞已经没有toast了")
}
}
class func updateToastsLayout() {
class func privUpdateToastsLayout() {
func f() {
let top = Inspire.shared.screen.updatedSafeAreaInsets.top
for (i, e) in toasts.enumerated() {
@ -342,18 +291,20 @@ fileprivate extension Toast {
let lastY = toasts[i-1].window?.frame.maxY ?? .zero
y = lastY + config.margin
}
e.maxY = y + window.frame.size.height
UIView.animateForToast {
e.window?.frame.origin.y = y
window.frame.origin.y = y
}
}
}
}
willUpdateToastsLayout?.cancel()
willUpdateToastsLayout = DispatchWorkItem(block: {
willprivUpdateToastsLayout?.cancel()
willprivUpdateToastsLayout = DispatchWorkItem(block: {
f()
})
DispatchQueue.main.asyncAfter(deadline: .now()+0.001, execute: willUpdateToastsLayout!)
DispatchQueue.main.asyncAfter(deadline: .now()+0.001, execute: willprivUpdateToastsLayout!)
}
}

View File

@ -16,12 +16,6 @@ public extension Toast {
///
case loading
///
case confirm
///
case delete
///
case success
@ -33,7 +27,7 @@ public extension Toast {
}
struct ViewModel {
class ViewModel {
/// ID
public var identifier = String(Date().timeIntervalSince1970)
@ -42,40 +36,56 @@ public extension Toast {
public var scene = Scene.default
///
public var title: String?
public var title: String? {
didSet {
vc?.titleLabel.text = title
}
}
///
public var message: String?
public var message: String? {
didSet {
vc?.bodyLabel.text = message
}
}
///
public var icon: UIImage?
public var icon: UIImage? {
didSet {
vc?.imageView.image = icon
}
}
///
internal var duration: TimeInterval?
public var duration: TimeInterval? {
didSet {
durationBlock?.cancel()
if let t = duration, t > 0 {
durationBlock = DispatchWorkItem(block: { [weak self] in
self?.vc?.pop()
})
DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: durationBlock!)
} else {
durationBlock = nil
}
}
}
public weak var vc: Toast?
///
public var removable = true
// MARK:
///
internal var durationBlock: DispatchWorkItem?
///
public var removable = true
///
internal var tapCallback: (() -> Void)?
internal mutating func setupDuration(duration: TimeInterval?, callback: @escaping () -> Void) {
self.duration = duration
durationBlock?.cancel()
if let t = duration, t > 0 {
durationBlock = DispatchWorkItem(block: callback)
DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: durationBlock!)
} else {
durationBlock = nil
}
}
}
}

View File

@ -1,9 +0,0 @@
//
// ToastView.swift
// ProHUD
//
// Created by xaoxuu on 2019/7/31.
// Copyright © 2019 Titan Studio. All rights reserved.
//
import Foundation