diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 2855b97..e6c595e 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -8,8 +8,12 @@ /* Begin PBXBuildFile section */ 33E2B6CF0D9BD11D8C027DE6 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB925B38880FB296AF5D219F /* Pods_Example.framework */; }; - CD10F0DA211582580077CAFF /* header.gif in Resources */ = {isa = PBXBuildFile; fileRef = CD10F0D9211582580077CAFF /* header.gif */; }; - CD95D26F22E732CE007559A3 /* TestA.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD95D26E22E732CE007559A3 /* TestA.swift */; }; + CD4B40AA23017C09005111B9 /* RootVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4B40A923017C09005111B9 /* RootVC.swift */; }; + CD8BFF1823014850001E08DD /* TestToastVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8BFF1723014850001E08DD /* TestToastVC.swift */; }; + CD8BFF1A2301485E001E08DD /* TestAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8BFF192301485E001E08DD /* TestAlertVC.swift */; }; + CD8BFF1C23014867001E08DD /* TestGuardVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8BFF1B23014867001E08DD /* TestGuardVC.swift */; }; + CD8BFF1E230148DD001E08DD /* BaseListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8BFF1D230148DD001E08DD /* BaseListVC.swift */; }; + CD8BFF2023014CB5001E08DD /* EmptyVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8BFF1F23014CB5001E08DD /* EmptyVC.swift */; }; CDA4E03C20D3935B00CD2A0C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA4E03B20D3935B00CD2A0C /* AppDelegate.swift */; }; CDA4E03E20D3935B00CD2A0C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA4E03D20D3935B00CD2A0C /* ViewController.swift */; }; CDA4E04120D3935B00CD2A0C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CDA4E03F20D3935B00CD2A0C /* Main.storyboard */; }; @@ -21,9 +25,13 @@ AB925B38880FB296AF5D219F /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BA1A6035F1B9A658B3BB225C /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; CA1298266BE89D7950DE99F2 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; - CD10F0D9211582580077CAFF /* header.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = header.gif; sourceTree = ""; }; + CD4B40A923017C09005111B9 /* RootVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootVC.swift; sourceTree = ""; }; CD59584620E36DA8000F6427 /* Example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example-Bridging-Header.h"; sourceTree = ""; }; - CD95D26E22E732CE007559A3 /* TestA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestA.swift; sourceTree = ""; }; + CD8BFF1723014850001E08DD /* TestToastVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestToastVC.swift; sourceTree = ""; }; + CD8BFF192301485E001E08DD /* TestAlertVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAlertVC.swift; sourceTree = ""; }; + CD8BFF1B23014867001E08DD /* TestGuardVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGuardVC.swift; sourceTree = ""; }; + CD8BFF1D230148DD001E08DD /* BaseListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseListVC.swift; sourceTree = ""; }; + CD8BFF1F23014CB5001E08DD /* EmptyVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyVC.swift; sourceTree = ""; }; CDA4E03820D3935B00CD2A0C /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; CDA4E03B20D3935B00CD2A0C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; CDA4E03D20D3935B00CD2A0C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -84,11 +92,15 @@ isa = PBXGroup; children = ( CDA4E03B20D3935B00CD2A0C /* AppDelegate.swift */, + CD4B40A923017C09005111B9 /* RootVC.swift */, + CD8BFF1D230148DD001E08DD /* BaseListVC.swift */, + CD8BFF1F23014CB5001E08DD /* EmptyVC.swift */, CDA4E03D20D3935B00CD2A0C /* ViewController.swift */, - CD95D26E22E732CE007559A3 /* TestA.swift */, + CD8BFF1723014850001E08DD /* TestToastVC.swift */, + CD8BFF192301485E001E08DD /* TestAlertVC.swift */, + CD8BFF1B23014867001E08DD /* TestGuardVC.swift */, CDA4E03F20D3935B00CD2A0C /* Main.storyboard */, CDA4E04220D3935C00CD2A0C /* Assets.xcassets */, - CD10F0D9211582580077CAFF /* header.gif */, CDA4E04420D3935C00CD2A0C /* LaunchScreen.storyboard */, CDA4E04720D3935C00CD2A0C /* Info.plist */, CD59584620E36DA8000F6427 /* Example-Bridging-Header.h */, @@ -161,7 +173,6 @@ CDA4E04620D3935C00CD2A0C /* LaunchScreen.storyboard in Resources */, CDA4E04320D3935C00CD2A0C /* Assets.xcassets in Resources */, CDA4E04120D3935B00CD2A0C /* Main.storyboard in Resources */, - CD10F0DA211582580077CAFF /* header.gif in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -230,9 +241,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CD8BFF1823014850001E08DD /* TestToastVC.swift in Sources */, CDA4E03E20D3935B00CD2A0C /* ViewController.swift in Sources */, + CD8BFF2023014CB5001E08DD /* EmptyVC.swift in Sources */, + CD8BFF1A2301485E001E08DD /* TestAlertVC.swift in Sources */, + CD4B40AA23017C09005111B9 /* RootVC.swift in Sources */, CDA4E03C20D3935B00CD2A0C /* AppDelegate.swift in Sources */, - CD95D26F22E732CE007559A3 /* TestA.swift in Sources */, + CD8BFF1C23014867001E08DD /* TestGuardVC.swift in Sources */, + CD8BFF1E230148DD001E08DD /* BaseListVC.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/Example/AppDelegate.swift b/Example/Example/AppDelegate.swift index dd25882..25ae9a1 100644 --- a/Example/Example/AppDelegate.swift +++ b/Example/Example/AppDelegate.swift @@ -9,8 +9,6 @@ import UIKit import ProHUD -let hud = ProHUD.shared - @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -20,9 +18,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - - + window = UIWindow.init(frame: UIScreen.main.bounds) + window?.rootViewController = RootVC() + window?.makeKeyAndVisible() + ProHUD.config { (cfg) in + cfg.rootViewController = window!.rootViewController + cfg.alert { (a) in + a.titleFont = .bold(22) + a.bodyFont = .regular(17) + a.boldTextFont = .bold(18) + a.buttonFont = .bold(18) + a.forceQuitTimer = 3 + } + cfg.toast { (t) in + t.titleFont = .bold(18) + t.bodyFont = .regular(16) + } + cfg.guard { (g) in + g.titleFont = .bold(22) + g.subTitleFont = .bold(20) + g.bodyFont = .regular(17) + g.buttonFont = .bold(18) + } + } return true } diff --git a/Example/Example/Assets.xcassets/alert-circle.imageset/Contents.json b/Example/Example/Assets.xcassets/alert-circle.imageset/Contents.json deleted file mode 100644 index 78cde9e..0000000 --- a/Example/Example/Assets.xcassets/alert-circle.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "alert-circle.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "alert-circle (1).png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template" - } -} \ No newline at end of file diff --git a/Example/Example/Assets.xcassets/alert-circle.imageset/alert-circle (1).png b/Example/Example/Assets.xcassets/alert-circle.imageset/alert-circle (1).png deleted file mode 100644 index 0088b5f..0000000 Binary files a/Example/Example/Assets.xcassets/alert-circle.imageset/alert-circle (1).png and /dev/null differ diff --git a/Example/Example/Assets.xcassets/alert-circle.imageset/alert-circle.png b/Example/Example/Assets.xcassets/alert-circle.imageset/alert-circle.png deleted file mode 100644 index edd5e44..0000000 Binary files a/Example/Example/Assets.xcassets/alert-circle.imageset/alert-circle.png and /dev/null differ diff --git a/Example/Example/Assets.xcassets/header_center.imageset/Contents.json b/Example/Example/Assets.xcassets/header_center.imageset/Contents.json deleted file mode 100644 index 028aba0..0000000 --- a/Example/Example/Assets.xcassets/header_center.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "header_center@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "header_center@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Example/Example/Assets.xcassets/header_center.imageset/header_center@2x.png b/Example/Example/Assets.xcassets/header_center.imageset/header_center@2x.png deleted file mode 100644 index e56bf3e..0000000 Binary files a/Example/Example/Assets.xcassets/header_center.imageset/header_center@2x.png and /dev/null differ diff --git a/Example/Example/Assets.xcassets/header_center.imageset/header_center@3x.png b/Example/Example/Assets.xcassets/header_center.imageset/header_center@3x.png deleted file mode 100644 index f550546..0000000 Binary files a/Example/Example/Assets.xcassets/header_center.imageset/header_center@3x.png and /dev/null differ diff --git a/Example/Example/Base.lproj/Main.storyboard b/Example/Example/Base.lproj/Main.storyboard index ded2e01..0d51b02 100644 --- a/Example/Example/Base.lproj/Main.storyboard +++ b/Example/Example/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -8,42 +8,39 @@ - + - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + - - - diff --git a/Example/Example/BaseListVC.swift b/Example/Example/BaseListVC.swift new file mode 100644 index 0000000..6360fd6 --- /dev/null +++ b/Example/Example/BaseListVC.swift @@ -0,0 +1,55 @@ +// +// BaseListVC.swift +// Example +// +// Created by xaoxuu on 2019/8/12. +// Copyright © 2019 Titan Studio. All rights reserved. +// + +import UIKit + +class BaseListVC: UIViewController { + + lazy var tableView: UITableView = { + let tv = UITableView() + + return tv + }() + + var titles: [String] { + return ["Toast", "Alert", "Guard"] + } + + override func viewDidLoad() { + super.viewDidLoad() + + + view.addSubview(tableView) + tableView.dataSource = self + tableView.delegate = self + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + tableView.snp.makeConstraints { (mk) in + mk.edges.equalToSuperview() + } + + } + + +} + +extension BaseListVC: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return titles.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + cell.textLabel?.text = titles[indexPath.row] + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } + +} diff --git a/Example/Example/EmptyVC.swift b/Example/Example/EmptyVC.swift new file mode 100644 index 0000000..e1afacb --- /dev/null +++ b/Example/Example/EmptyVC.swift @@ -0,0 +1,57 @@ +// +// EmptyVC.swift +// Example +// +// Created by xaoxuu on 2019/8/12. +// Copyright © 2019 Titan Studio. All rights reserved. +// + +import UIKit +import SnapKit +import Inspire + +class EmptyVC: UIViewController { + + + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor(white: 0.95, alpha: 1) + // Do any additional setup after loading the view. + + let lb = UILabel() + lb.numberOfLines = 0 + lb.text = title + lb.font = .regular(40) + view.addSubview(lb) + lb.snp.makeConstraints { (mk) in + mk.center.equalToSuperview() + mk.leading.greaterThanOrEqualToSuperview().offset(16) + mk.trailing.lessThanOrEqualToSuperview().offset(-16) + } + + let btn = UIButton(type: .system) + btn.titleLabel?.font = .bold(20) + btn.setTitle("Dismiss", for: .normal) + btn.addTarget(self, action: #selector(didTappedDismiss(_:)), for: .touchUpInside) + view.addSubview(btn) + btn.snp.makeConstraints { (mk) in + mk.top.equalToSuperview().offset(Inspire.current.layout.safeAreaInsets(for: self).top) + mk.trailing.equalToSuperview().offset(-16) + mk.height.equalTo(44) + } + } + + @objc func didTappedDismiss(_ sender: UIButton) { + dismiss(animated: true, completion: nil) + } + +} + +extension UIViewController { + func presentEmptyVC(title: String?) { + let vc = EmptyVC() + vc.title = title + present(vc, animated: true, completion: nil) + } +} diff --git a/Example/Example/RootVC.swift b/Example/Example/RootVC.swift new file mode 100644 index 0000000..8a34a89 --- /dev/null +++ b/Example/Example/RootVC.swift @@ -0,0 +1,34 @@ +// +// RootVC.swift +// Example +// +// Created by xaoxuu on 2019/8/12. +// Copyright © 2019 Titan Studio. All rights reserved. +// + +import UIKit + +class RootVC: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + let vc = ViewController() + let nav = UINavigationController(rootViewController: vc) + addChild(nav) + view.addSubview(nav.view) + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Example/Example/TestA.swift b/Example/Example/TestA.swift deleted file mode 100644 index f159b82..0000000 --- a/Example/Example/TestA.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// TestA.swift -// ProHUDExample -// -// Created by xaoxuu on 2019/7/23. -// Copyright © 2019 Titan Studio. All rights reserved. -// - -import UIKit - -public class TestA: NSObject { - - class func test1() { - print(self, "test1") - } - class func test2() { - print(self, "test2") - } - -} - - -open class TestB: NSObject { - - - class func test1() { - print(self, "test1") - } - open class func test2() { - print(self, "test2") - } - - -} - -class TestAA: TestA { - - override class func test2() { - print(self, "test2", "override") - } - -} - -class TestBB: TestB { - - override class func test2() { - print(self, "test2", "override") - } -} diff --git a/Example/Example/TestAlertVC.swift b/Example/Example/TestAlertVC.swift new file mode 100644 index 0000000..a5122ab --- /dev/null +++ b/Example/Example/TestAlertVC.swift @@ -0,0 +1,127 @@ +// +// TestAlertVC.swift +// Example +// +// Created by xaoxuu on 2019/8/12. +// Copyright © 2019 Titan Studio. All rights reserved. +// + +import UIKit +import ProHUD + +class TestAlertVC: BaseListVC { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + override var titles: [String] { + return ["场景:正在同步(超时)", "场景:同步成功(写法1)", "场景:同步成功(写法2)", "场景:同步失败和重试"] + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let row = indexPath.row + if row == 0 { + func f() { + Alert.push(scene: .loading, title: "正在同步", message: "请稍等片刻") { (a) in + a.identifier = "loading" + a.animate(rotate: true) + a.didForceQuit { [weak self] in + let t = Toast.push(scene: .loading, title: "正在同步", message: "请稍等片刻(点击展开为Alert)") { (vm) in + vm.identifier = "loading" + } + t.animate(rotate: true) + t.didTapped { [weak t] in + t?.pop() + f() + } + self?.simulateSync() + } + } + simulateSync() + } + f() + } else if row == 1 { + Alert.push() { (a) in + a.identifier = "loading" + a.animate(rotate: true) + a.update { (vm) in + vm.scene = .loading + vm.title = "正在同步" + vm.message = "请稍等片刻" + } + } + DispatchQueue.main.asyncAfter(deadline: .now()+2) { + Alert.find("loading", last: { (a) in + a.update { (vm) in + vm.scene = .success + vm.title = "同步成功" + vm.message = nil + } + }) + } + } else if row == 2 { + let a = Alert.push() { (a) in + a.identifier = "loading" + } + a.animate(rotate: true) + a.update { (vm) in + vm.scene = .loading + vm.title = "正在同步" + vm.message = "请稍等片刻" + } + DispatchQueue.main.asyncAfter(deadline: .now()+2) { + Alert.find("loading", last: { (a) in + a.update { (vm) in + vm.scene = .success + vm.title = "同步成功" + vm.message = nil + } + }) + } + } else if row == 3 { + Alert.push() { (a) in + a.identifier = "loading" + } + func loading() { + Alert.find("loading", last: { (a) in + a.update { (vm) in + vm.scene = .loading + vm.title = "正在同步" + vm.message = "请稍等片刻" + vm.remove(action: 0, 1) + } + a.animate(rotate: true) + DispatchQueue.main.asyncAfter(deadline: .now()+2) { + a.update { (vm) in + vm.scene = .error + vm.title = "同步失败" + vm.message = "请检查网络是否通畅" + vm.add(action: .default, title: "重试") { + loading() + } + vm.add(action: .cancel, title: "取消", handler: nil) + } + } + }) + } + loading() + } + } + + func simulateSync() { + DispatchQueue.main.asyncAfter(deadline: .now() + 15) { + Alert.find("loading", last: { (a) in + a.update { (vm) in + vm.scene = .success + vm.title = "同步成功" + vm.message = "啊哈哈哈哈哈哈哈哈" + } + }) + } + } + +} diff --git a/Example/Example/TestGuardVC.swift b/Example/Example/TestGuardVC.swift new file mode 100644 index 0000000..d80a6fc --- /dev/null +++ b/Example/Example/TestGuardVC.swift @@ -0,0 +1,106 @@ +// +// TestGuardVC.swift +// Example +// +// Created by xaoxuu on 2019/8/12. +// Copyright © 2019 Titan Studio. All rights reserved. +// + +import UIKit +import ProHUD +import Inspire + +class TestGuardVC: BaseListVC { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + override var titles: [String] { + return ["场景:删除菜单", "场景:升级至专业版", "场景:隐私协议页面"] + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let row = indexPath.row + if row == 0 { + Guard.push() { (vc) in + vc.update { (vm) in + vm.add(action: .destructive, title: "删除") { [weak vc] in + Alert.push(scene: .delete, title: "确认删除", message: "此操作不可撤销") { (vc) in + vc.update { (vm) in + vm.add(action: .destructive, title: "删除") { [weak vc] in + vc?.pop() + } + vm.add(action: .cancel, title: "取消", handler: nil) + } + } + vc?.pop() + } + vm.add(action: .cancel, title: "取消", handler: nil) + } + } + } else if row == 1 { + // 可以通过id来避免重复 + Guard.find("pro") { + Guard.push() { (vc) in + vc.identifier = "pro" + vc.update { (vm) in + vm.add(title: "升级至专业版") + vm.add(subTitle: "解锁功能") + vm.add(message: "功能1功能2...") + vm.add(subTitle: "价格") + vm.add(message: "只需一次性付费$2999即可永久享用。") + vm.add(action: .destructive, title: "购买") { [weak vc] in + Alert.push(scene: .confirm, title: "确认购买", message: "一旦购买拒不退款") { (vc) in + vc.identifier = "confirm" + vc.update { (vm) in + vm.add(action: .destructive, title: "购买") { [weak vc] in + vc?.update({ (vm) in + vm.scene = .success + vm.title = "购买成功" + vm.message = "感谢您的支持" + vm.remove(action: 1) + vm.update(action: 0, style: .default, title: "我知道了") { + vc?.pop() + } + }) + } + vm.add(action: .cancel, title: "取消", handler: nil) + } + } + vc?.pop() + } + vm.add(action: .cancel, title: "取消", handler: nil) + } + } + } + } else if row == 2 { + Guard.push() { (vc) in + vc.isFullScreen = true + vc.update { (vm) in + let titleLabel = vm.add(title: "隐私协议") + titleLabel.snp.makeConstraints { (mk) in + mk.height.equalTo(44) + } + let tv = UITextView() + tv.backgroundColor = .white + tv.isEditable = false + vc.textStack.addArrangedSubview(tv) + tv.text = "这里可以插入一个webView" + vm.add(message: "请认真阅读以上内容,当您阅读完毕并同意协议内容时点击接受按钮。") + + vm.add(action: .default, title: "接受") { [weak vc] in + vc?.pop() + } + } + + } + + } + } + + +} diff --git a/Example/Example/TestToastVC.swift b/Example/Example/TestToastVC.swift new file mode 100644 index 0000000..d8abb9c --- /dev/null +++ b/Example/Example/TestToastVC.swift @@ -0,0 +1,129 @@ +// +// TestToastVC.swift +// Example +// +// Created by xaoxuu on 2019/8/12. +// Copyright © 2019 Titan Studio. All rights reserved. +// + +import UIKit +import ProHUD + +class TestToastVC: BaseListVC { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + override var titles: [String] { + return ["场景:正在同步", + "场景:同步成功", + "场景:同步失败", + "场景:设备电量过低", + "传入指定图标", + "禁止手势移除", + "组合使用示例", + "避免重复发布同一条信息", + "根据id查找并修改实例"] + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let row = indexPath.row + if row == 0 { + Toast.push(scene: .loading, title: "正在同步", message: "请稍等片刻") { (vm) in + vm.identifier = "loading" + }.animate(rotate: true) + simulateSync() + } else if row == 1 { + let t = Toast.push(scene: .success, title: "同步成功", message: "点击查看详情") + t.didTapped { [weak self, weak t] in + self?.presentEmptyVC(title: "详情") + t?.pop() + } + } else if row == 2 { + Toast.push(scene: .error, title: "同步失败", message: "请稍后重试。点击查看详情") { (vc) in + vc.update { (vm) in + vm.duration = 0 + } + vc.didTapped { [weak self, weak vc] in + self?.presentEmptyVC(title: "这是错误详情") + vc?.pop() + } + } + } else if row == 3 { + Toast.push(scene: .warning, title: "设备电量过低", message: "请及时对设备进行充电,以免影响使用。") + + } else if row == 4 { + Toast.push(scene: .default, title: "传入指定图标测试", message: "这是消息内容") { (vc) in + vc.update { (vm) in + vm.icon = UIImage(named: "icon_download") + } + } + } else if row == 5 { + Toast.push(scene: .default, title: "禁止手势移除", message: "这条消息无法通过向上滑动移出屏幕。5秒后自动消失,每次拖拽都会刷新倒计时。") { (vc) in + vc.update { (vm) in + vm.isRemovable = false + vm.duration = 5 + } + } + } else if row == 6 { + Toast.push(scene: .default, title: "好友邀请", message: "你收到一条好友邀请,点击查看详情。", duration: 10) { (vc) in + vc.identifier = "xxx" + vc.didTapped { [weak vc] in + vc?.pop() + Alert.push(scene: .confirm, title: "好友邀请", message: "用户xxx想要添加你为好友,是否同意?") { (vc) in + vc.update { (vm) in + vm.add(action: .default, title: "接受") { [weak vc] in + vc?.pop() + Toast.push(scene: .success, title: "好友添加成功", message: "这是消息内容") + } + vm.add(action: .cancel, title: "拒绝") { + + } + } + } + } + } + } else if row == 7 { + Toast.find("aaa", last: { (t) in + t.pulse() + t.update() { (vm) in + vm.title = "已经存在了" + } + }) { + Toast.push(title: "这是一条id为aaa的横幅", message: "避免重复发布同一条信息") { (t) in + t.identifier = "aaa" + t.update { (vm) in + vm.scene = .warning + vm.duration = 0 + } + } + } + } else if row == 8 { + Toast.find("aaa", last: { (t) in + t.update { (vm) in + vm.scene = .success + vm.title = "找到了哈哈" + vm.message = "根据id查找并修改实例" + } + }) + + } + } + + func simulateSync() { + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + Toast.find("loading", last: { (t) in + t.update { (vm) in + vm.scene = .success + vm.title = "同步成功" + vm.message = "啊哈哈哈哈哈哈哈哈" + } + }) + } + } + +} diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 9df51f8..b3b7f91 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -8,149 +8,35 @@ import UIKit import ProHUD +import SnapKit -class ViewController: UIViewController { +class ViewController: BaseListVC { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. - - - ProHUD.config { (cfg) in - cfg.alert { (a) in - a.forceQuitTimer = 2 -// a.iconSize = .init(width: 20, height: 80) - } - cfg.toast { (t) in -// t.iconSize = .init(width: 300, height: 30) - } + title = "ProHUD" + } + + override var titles: [String] { + return ["Toast", "Alert", "Guard"] + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + if indexPath.row == 0 { + let vc = TestToastVC() + vc.title = titles[indexPath.row] + navigationController?.pushViewController(vc, animated: true) + } else if indexPath.row == 1 { + let vc = TestAlertVC() + vc.title = titles[indexPath.row] + navigationController?.pushViewController(vc, animated: true) + } else { + let vc = TestGuardVC() + vc.title = titles[indexPath.row] + navigationController?.pushViewController(vc, animated: true) } - - } - - - @IBAction func test(_ sender: UIButton) { - - -// testUpdateAction() - testGuard() -// fastGuard() - } - - func testDelete() { - let a = ProHUD.push(alert: .delete, title: "确认删除", message: "此操作不可撤销") - a.add(action: .destructive, title: "确认", action: { [weak a] in - a?.remove(action: 0, 1) - a?.update(scene: .loading, title: "正在删除", message: "请稍后片刻") - DispatchQueue.main.asyncAfter(deadline: .now()+1) { - a?.update(scene: .success, title: "删除成功", message: "啊哈哈哈哈").duration(2) - ProHUD.push(toast: .success, title: "删除成功", message: "aaa") - } - }).add(action: .cancel, title: "取消", action: nil) - - } - func testToast() { - let t = ProHUD.Toast(scene: .loading, title: "正在加载", message: "请稍候片刻") - - let a = ProHUD.push(alert : .loading, title: "正在加载", message: "请稍候片刻") - a.didForceQuit { - hud.push(t) - } - t.didTapped { [weak t] in - t?.pop() - let a2 = ProHUD.push(alert: .loading, title: "正在加载", message: "马上就要成功了") - DispatchQueue.main.asyncAfter(deadline: .now()+1) { - let a3 = ProHUD.push(alert: .error, title: "加载失败", message: "点击充实") - a3.add(action: .default, title: "重新加载") { [weak a3] in - a3?.update(scene: .success, title: "加载成功", message: "马上就要成功了") - a3?.update(action: 0, style: .default, title: "OK", action: { [weak a3] in - a3?.pop() - }).remove(action: 1, 2) - }.add(action: .destructive, title: "终止", action: nil).add(action: .cancel, title: "取消", action: nil) - - } - - } - - - - } - func testGuard() { - let g = ProHUD.Guard(title: "请求权限", message: "请打开相机权限开关,否则无法进行测量。") - - g.add(title: "呵呵") - g.add(message: "请打开相机权限开关,否则无法进行测量。请打开相机权限开关,否则无法进行测量。") - g.add(action: .default, title: "测试弹窗", action: { [weak self] in - self?.testToast() - }) - g.add(action: .destructive, title: "测试删除弹窗", action: { [weak self] in - self?.testDelete() - }) - g.add(action: .cancel, title: "我知道了", action: nil) - - g.push(to: self) - debugPrint("test: ", g) - } - - func testUpdateAction() { - let a = ProHUD.push(alert: .confirm, title: "确认删除", message: "此操作无法撤销") - a.add(action: .destructive, title: "删除") { - a.remove(action: 0, 1).update(scene: .loading, title: "正在删除", message: "请稍后片刻") - }.add(action: .cancel, title: "取消", action: nil) - - - - } - - func fastGuard() { - let g = ProHUD.push(guard: self, title: "测试", message: "测试测试") - g.add(action: .default, title: "默认按钮", action: { - - }) - g.add(action: .cancel, title: "取消", action: nil) - g.view.backgroundColor = .clear - -// g.contentView.backgroundColor = UIColor.white - - - } - - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - - - - - -// -// ProHUD.show(alert: .loading, title: "确认删除", message: "此操作不可撤销").timeout(nil) -// -// ProHUD.show(alert: .confirm, title: "确认删除", message: "此操作不可撤销").timeout(3) - -// -// 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") -// }.addAction(style: .cancel, title: nil) { -// -// } - - - - -// ProHUD.show(toast: .loading, title: "正在加载", message: "拉升的反馈老实交代分开就撒开了击快乐圣反馈老实交代分开就撒开了击快乐圣") -// ProHUD.show(toast: .loading, title: "正在加载", message: "哈克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞哈克里斯蒂娜疯狂拉升的反馈老实交代分开就撒开了击快乐圣诞") - } } diff --git a/Example/Example/header.gif b/Example/Example/header.gif deleted file mode 100644 index e62e8b1..0000000 Binary files a/Example/Example/header.gif and /dev/null differ diff --git a/ProHUD.xcodeproj/project.pbxproj b/ProHUD.xcodeproj/project.pbxproj index dde47e4..a8e2f4c 100644 --- a/ProHUD.xcodeproj/project.pbxproj +++ b/ProHUD.xcodeproj/project.pbxproj @@ -10,21 +10,19 @@ 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 */; }; CDB6A07B22EEF06500AF6CF0 /* HUDController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB6A07A22EEF06500AF6CF0 /* HUDController.swift */; }; CDB6A07D22EEF19D00AF6CF0 /* HUDConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB6A07C22EEF19D00AF6CF0 /* HUDConfig.swift */; }; + CDC39CFD22FD6DDF0070E914 /* GuardModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -32,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 = ""; }; CD16490C22EF09B40077988C /* AlertConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertConfig.swift; sourceTree = ""; }; - CD16490E22EF09D50077988C /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = ""; }; CD16491122EF0D900077988C /* HUDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDView.swift; sourceTree = ""; }; CD16491322EF12220077988C /* ProHUD.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = ProHUD.xcassets; sourceTree = ""; }; CD6CD86B22F1858F00F4FD4A /* ToastModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastModel.swift; sourceTree = ""; }; - CD6CD86D22F185A000F4FD4A /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; CD6CD86F22F185A700F4FD4A /* ToastController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastController.swift; sourceTree = ""; }; CD6CD87122F185AF00F4FD4A /* ToastConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastConfig.swift; sourceTree = ""; }; CD6CD87422F185C200F4FD4A /* GuardController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardController.swift; sourceTree = ""; }; CD6CD87822F185D000F4FD4A /* GuardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardConfig.swift; sourceTree = ""; }; - CD6CD87A22F185D600F4FD4A /* GuardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardView.swift; sourceTree = ""; }; 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 = ""; }; CD95D22022E72C4C007559A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -50,6 +45,7 @@ CD95D26A22E72DB3007559A3 /* ProHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProHUD.swift; sourceTree = ""; }; CDB6A07A22EEF06500AF6CF0 /* HUDController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDController.swift; sourceTree = ""; }; CDB6A07C22EEF19D00AF6CF0 /* HUDConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDConfig.swift; sourceTree = ""; }; + CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardModel.swift; sourceTree = ""; }; DC31EBFAC56868D6096A233A /* Pods-ProHUD.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ProHUD.release.xcconfig"; path = "Pods/Target Support Files/Pods-ProHUD/Pods-ProHUD.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -88,7 +84,6 @@ CD95D26822E72DA1007559A3 /* AlertController.swift */, CD16490A22EF09AB0077988C /* AlertModel.swift */, CD16490C22EF09B40077988C /* AlertConfig.swift */, - CD16490E22EF09D50077988C /* AlertView.swift */, ); path = Alert; sourceTree = ""; @@ -97,7 +92,6 @@ isa = PBXGroup; children = ( CD6CD86B22F1858F00F4FD4A /* ToastModel.swift */, - CD6CD86D22F185A000F4FD4A /* ToastView.swift */, CD6CD86F22F185A700F4FD4A /* ToastController.swift */, CD6CD87122F185AF00F4FD4A /* ToastConfig.swift */, ); @@ -109,7 +103,7 @@ children = ( CD6CD87422F185C200F4FD4A /* GuardController.swift */, CD6CD87822F185D000F4FD4A /* GuardConfig.swift */, - CD6CD87A22F185D600F4FD4A /* GuardView.swift */, + CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */, ); path = Guard; sourceTree = ""; @@ -277,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 */, @@ -292,6 +283,7 @@ CD16490B22EF09AB0077988C /* AlertModel.swift in Sources */, CD16491222EF0D900077988C /* HUDView.swift in Sources */, CDB6A07B22EEF06500AF6CF0 /* HUDController.swift in Sources */, + CDC39CFD22FD6DDF0070E914 /* GuardModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ProHUD/Alert/AlertConfig.swift b/ProHUD/Alert/AlertConfig.swift index c166c4d..0e056a0 100644 --- a/ProHUD/Alert/AlertConfig.swift +++ b/ProHUD/Alert/AlertConfig.swift @@ -22,11 +22,17 @@ 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 + } // MARK: 文本样式 /// 标题字体 @@ -34,7 +40,7 @@ public extension ProHUD.Configuration { /// 标题最多行数 public var titleMaxLines = Int(1) - /// 加粗正文字体(如果只有标题或者只有正文,则显示这种字体) + /// 加粗字体(如果只有标题或者只有正文,则显示这种字体) public var boldTextFont = UIFont.boldSystemFont(ofSize: 18) /// 正文字体 @@ -48,16 +54,14 @@ public extension ProHUD.Configuration { // MARK: 生命周期 - /// 加载视图 - /// - Parameter callback: 回调代码 - public mutating func loadSubviews(_ callback: @escaping (ProHUD.Alert) -> Void) { - privLoadSubviews = callback + /// 刷新数据和布局 + public func reloadData(_ callback: @escaping (ProHUD.Alert) -> Void) { + privReloadData = callback } - /// 更新视图 - /// - Parameter callback: 回调代码 - public mutating func reloadData(_ callback: @escaping (ProHUD.Alert) -> Void) { - privReloadData = callback + /// 默认持续时间(当viewmodel的duration为nil时,会从这里获取) + public func durationForScene(_ callback: @escaping (ProHUD.Alert.Scene) -> TimeInterval?) { + privDurationForScene = callback } /// 多少秒后显示强制退出的按钮(只有无按钮的弹窗才会出现) @@ -68,77 +72,45 @@ public extension ProHUD.Configuration { /// 加载强制退出按钮 /// - Parameter callback: 回调代码 - public mutating func loadForceQuitButton(_ callback: @escaping (ProHUD.Alert) -> Void) { + public func loadForceQuitButton(_ callback: @escaping (ProHUD.Alert) -> Void) { privLoadForceQuitButton = callback } + } } -// MARK: - 默认实现 - +// MARK: - 内部调用 internal extension ProHUD.Configuration.Alert { - var loadSubviews: (ProHUD.Alert) -> Void { - return privLoadSubviews - } var reloadData: (ProHUD.Alert) -> Void { return privReloadData } - var loadForceQuitButton: (ProHUD.Alert) -> Void { - return privLoadForceQuitButton + var durationForScene: (ProHUD.Alert.Scene) -> TimeInterval? { + return privDurationForScene } - var setupDefaultDuration: (ProHUD.Alert) -> Void { - return { (vc) in - // 如果设置了超时时间,但是增加了按钮 - if let t = vc.model.duration, t > 0 { - if vc.buttonEvents.count > 0 { - vc.duration(nil) - } - } else if vc.model.duration == nil && vc.model.scene != .loading { - // 如果没有设置超时时间 - vc.duration(2) - } - } - } - - var reloadStack: (ProHUD.Alert) -> Void { - return { (vc) in - if vc.textStack.arrangedSubviews.count > 0 { - vc.contentStack.addArrangedSubview(vc.textStack) - } else { - vc.textStack.removeFromSuperview() - } - if vc.actionStack.arrangedSubviews.count > 0 { - vc.contentStack.addArrangedSubview(vc.actionStack) - } else { - vc.actionStack.removeFromSuperview() - } - } - } } -fileprivate var privLoadSubviews: (ProHUD.Alert) -> Void = { + +// MARK: - 默认实现 +fileprivate var privLayoutContentView: (ProHUD.Alert) -> Void = { return { (vc) in - debug(vc, "loadSubviews") - let config = cfg.alert if vc.contentView.superview == nil { vc.view.addSubview(vc.contentView) - vc.contentView.contentView.addSubview(vc.contentStack) - - vc.contentStack.spacing = cfg.alert.margin + cfg.alert.padding - vc.contentView.layer.masksToBounds = true vc.contentView.layer.cornerRadius = cfg.alert.cornerRadius - let maxWidth = CGFloat.maximum(CGFloat.minimum(UIScreen.main.bounds.width * 0.68, cfg.alert.maxWidth), 268) vc.contentView.snp.makeConstraints { (mk) in mk.center.equalToSuperview() mk.width.lessThanOrEqualTo(maxWidth) } + } + if vc.contentStack.superview == nil { + vc.contentView.contentView.addSubview(vc.contentStack) + vc.contentStack.spacing = cfg.alert.margin + cfg.alert.padding vc.contentStack.snp.makeConstraints { (mk) in mk.centerX.equalToSuperview() mk.top.equalToSuperview().offset(cfg.alert.padding) @@ -147,23 +119,13 @@ fileprivate var privLoadSubviews: (ProHUD.Alert) -> Void = { mk.trailing.equalToSuperview().offset(-cfg.alert.padding) } } - } }() -fileprivate var privReloadData: (ProHUD.Alert) -> Void = { - return { (vc) in - debug(vc, "reloadData") - let config = cfg.alert - let isFirstLayout: Bool - // 图标和文字至少有一个,如果都没有添加到视图中,说明是第一次layout - if vc.textStack.superview == nil && vc.imageView?.superview == nil { - isFirstLayout = true - } else { - isFirstLayout = false - } +fileprivate var privIconForScene: (ProHUD.Alert.Scene) -> UIImage? = { + return { (scene) in let imgStr: String - switch vc.model.scene { + switch scene { case .success: imgStr = "ProHUDSuccess" case .warning: @@ -179,7 +141,14 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { default: imgStr = "ProHUDMessage" } - let img = vc.model.icon ?? ProHUD.image(named: imgStr) + return ProHUD.image(named: imgStr) + } +}() + +fileprivate var privUpdateImage: (ProHUD.Alert) -> Void = { + return { (vc) in + let config = cfg.alert + let img = vc.vm.icon ?? privIconForScene(vc.vm.scene) if let imgv = vc.imageView { imgv.image = img } else { @@ -197,9 +166,13 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { vc.imageView = icon } vc.imageView?.layer.removeAllAnimations() - - // text - if vc.model.title?.count ?? 0 > 0 || vc.model.message?.count ?? 0 > 0 { + } +}() + +fileprivate var privUpdateTextStack: (ProHUD.Alert) -> Void = { + return { (vc) in + let config = cfg.alert + 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.75) @@ -212,19 +185,19 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { mk.trailing.lessThanOrEqualTo(vc.contentView).offset(-config.padding*2) } } - if vc.model.title?.count ?? 0 > 0 { + if vc.vm.title?.count ?? 0 > 0 { if let lb = vc.titleLabel { - lb.text = vc.model.title + lb.text = vc.vm.title } else { let title = UILabel() title.textAlignment = .center title.numberOfLines = config.titleMaxLines title.textColor = cfg.primaryLabelColor - title.text = vc.model.title + title.text = vc.vm.title vc.textStack.addArrangedSubview(title) vc.titleLabel = title } - if vc.model.message?.count ?? 0 > 0 { + if vc.vm.message?.count ?? 0 > 0 { // 有message vc.titleLabel?.font = config.titleFont } else { @@ -234,20 +207,20 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { } else { vc.titleLabel?.removeFromSuperview() } - if vc.model.message?.count ?? 0 > 0 { + if vc.vm.message?.count ?? 0 > 0 { if let lb = vc.bodyLabel { - lb.text = vc.model.message + lb.text = vc.vm.message } else { let body = UILabel() body.textAlignment = .center body.font = config.bodyFont body.numberOfLines = config.bodyMaxLines body.textColor = cfg.secondaryLabelColor - body.text = vc.model.message + body.text = vc.vm.message vc.textStack.addArrangedSubview(body) vc.bodyLabel = body } - if vc.model.title?.count ?? 0 > 0 { + if vc.vm.title?.count ?? 0 > 0 { // 有title vc.bodyLabel?.font = config.bodyFont } else { @@ -260,48 +233,35 @@ fileprivate var privReloadData: (ProHUD.Alert) -> Void = { } else { vc.textStack.removeFromSuperview() } - if vc.actionStack.superview != nil { - if isFirstLayout { - vc.contentStack.addArrangedSubview(vc.actionStack) - } else { - vc.actionStack.transform = .init(scaleX: 1, y: 0.001) - UIView.animateForAlert { - vc.contentStack.addArrangedSubview(vc.actionStack) - vc.view.layoutIfNeeded() - } - } + vc.textStack.layoutIfNeeded() + } +}() + + +fileprivate var privUpdateActionStack: (ProHUD.Alert) -> Void = { + return { (vc) in + let config = cfg.alert + if vc.actionStack.arrangedSubviews.count > 0 { + // 有按钮 + vc.contentStack.addArrangedSubview(vc.actionStack) // 适配横竖屏和iPad - if isPortrait == false { + if isPortrait == false && vc.actionStack.arrangedSubviews.count < 4 { vc.actionStack.axis = .horizontal vc.actionStack.alignment = .fill vc.actionStack.distribution = .fillEqually } - vc.actionStack.snp.makeConstraints { (mk) in + vc.actionStack.snp.makeConstraints { (mk) in mk.width.greaterThanOrEqualTo(200) mk.leading.trailing.equalToSuperview() } - if isFirstLayout == false { - UIView.animateForAlert { - vc.actionStack.transform = .identity - } - } - - } - - if isFirstLayout { - vc.view.layoutIfNeeded() - vc.imageView?.transform = .init(scaleX: 0.75, y: 0.75) - UIView.animateForAlert { - vc.imageView?.transform = .identity - vc.view.layoutIfNeeded() - } } else { - UIView.animateForAlert { - vc.view.layoutIfNeeded() + // 无按钮 + for v in vc.actionStack.arrangedSubviews { + v.removeFromSuperview() } + vc.actionStack.removeFromSuperview() } - - + vc.actionStack.layoutIfNeeded() } }() @@ -311,7 +271,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 { @@ -335,12 +295,80 @@ fileprivate var privLoadForceQuitButton: (ProHUD.Alert) -> Void = { bg.transform = .identity } vc.addTouchUpAction(for: btn) { [weak vc] in - debug("点击了隐藏") - vc?.model.forceQuitCallback?() + debug("点击了【\(config.forceQuitTitle)】") + vc?.vm.forceQuitCallback?() vc?.pop() } } }() +/// 刷新数据和布局 +fileprivate var privReloadData: (ProHUD.Alert) -> Void = { + return { (vc) in + debug(vc, "reloadData") + let config = cfg.alert + let isFirstLayout: Bool + if vc.contentView.superview == nil { + isFirstLayout = true + // 布局主容器视图 + privLayoutContentView(vc) + } else { + isFirstLayout = false + } + // 更新图片 + privUpdateImage(vc) + + // 更新文本容器 + privUpdateTextStack(vc) + + // 更新操作容器 + privUpdateActionStack(vc) + vc.contentStack.layoutIfNeeded() + vc.contentView.layoutIfNeeded() + + // 动画 + if isFirstLayout { + vc.view.layoutIfNeeded() + vc.imageView?.transform = .init(scaleX: 0.75, y: 0.75) + UIView.animateForAlert { + vc.view.layoutIfNeeded() + vc.imageView?.transform = .identity + } + } else { + UIView.animateForAlert { + vc.view.layoutIfNeeded() + } + } + + // 设置持续时间 + vc.vm.updateDuration() + + // 强制退出按钮 + vc.vm.forceQuitTimerBlock?.cancel() + if vc.buttonEvents.count == 0 { + vc.vm.forceQuitTimerBlock = DispatchWorkItem(block: { [weak vc] in + if let vc = vc { + if vc.buttonEvents.count == 0 { + privLoadForceQuitButton(vc) + } + } + }) + DispatchQueue.main.asyncAfter(deadline: .now() + config.forceQuitTimer, execute: vc.vm.forceQuitTimerBlock!) + } else { + vc.vm.forceQuitTimerBlock = nil + } + + } +}() +fileprivate var privDurationForScene: (ProHUD.Alert.Scene) -> TimeInterval? = { + return { (scene) in + switch scene { + case .loading: + return nil + default: + return 2 + } + } +}() diff --git a/ProHUD/Alert/AlertController.swift b/ProHUD/Alert/AlertController.swift index 41031ad..1eedafc 100644 --- a/ProHUD/Alert/AlertController.swift +++ b/ProHUD/Alert/AlertController.swift @@ -19,7 +19,7 @@ public extension ProHUD { internal static var alertWindow: UIWindow? /// 内容视图 - public var contentView = BlurView() + public var contentView = createBlurView() /// 内容容器(包括icon、textStack、actionStack) public var contentStack: StackContainer = { @@ -53,10 +53,10 @@ public extension ProHUD { }() /// 视图模型 - public var model = ViewModel() - + public var vm = ViewModel() // MARK: 生命周期 + private var isLoadFinished = false /// 实例化 /// - Parameter scene: 场景 @@ -65,16 +65,20 @@ public extension ProHUD { /// - Parameter icon: 图标 public convenience init(scene: Scene = .default, title: String? = nil, message: String? = nil, icon: UIImage? = nil, actions: ((Alert) -> Void)? = nil) { self.init() - view.tintColor = cfg.alert.tintColor - model.scene = scene - model.title = title - model.message = message - model.icon = icon + vm.vc = self + vm.scene = scene + vm.title = title + vm.message = message + vm.icon = icon actions?(self) - willLayoutSubviews() - } + public override func viewDidLoad() { + super.viewDidLoad() + view.tintColor = cfg.alert.tintColor + cfg.alert.reloadData(self) + isLoadFinished = true + } } @@ -84,13 +88,10 @@ public extension ProHUD { public extension Alert { - // MARK: 生命周期函数 - /// 推入屏幕 @discardableResult func push() -> Alert { - let hud = ProHUD.shared if Alert.alerts.contains(self) == false { - let window = Alert.getAlertWindow(self) + let window = Alert.privGetAlertWindow(self) window.makeKeyAndVisible() window.resignKey() window.addSubview(view) @@ -103,19 +104,15 @@ public extension Alert { } Alert.alerts.append(self) } - Alert.updateAlertsLayout() - - // setup duration - if let _ = model.duration, model.durationBlock == nil { - duration(model.duration) - } + Alert.privUpdateAlertsLayout() return self } /// 弹出屏幕 func pop() { - let window = Alert.getAlertWindow(self) - Alert.removeItemFromArray(alert: self) + willDisappearCallback?() + 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) @@ -129,117 +126,51 @@ public extension Alert { UIView.animateForAlertBuildOut(animations: { window.backgroundColor = window.backgroundColor?.withAlphaComponent(0) }) { (done) in - Alert.alertWindow = nil + if Alert.alerts.count == 0 { + Alert.alertWindow = nil + } } } } - - // MARK: 设置函数 - - /// 添加按钮 - /// - Parameter style: 样式 - /// - Parameter text: 标题 - /// - Parameter handler: 事件处理 - @discardableResult func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { - let btn = privAddButton(custom: Button.actionButton(title: title), action: handler) - if let b = btn as? Button { - b.update(style: style) - } - return btn + /// 更新 + /// - Parameter callback: 回调 + func update(_ callback: ((inout ViewModel) -> Void)? = nil) { + callback?(&vm) + cfg.alert.reloadData(self) } + /// 最小化事件 /// - Parameter callback: 事件回调 - @discardableResult func didForceQuit(_ callback: (() -> Void)?) -> Alert { - model.forceQuitCallback = callback - return self + func didForceQuit(_ callback: (() -> Void)?) { + vm.forceQuitCallback = callback } - /// 消失事件 - /// - Parameter callback: 事件回调 - @discardableResult func didDisappear(_ callback: (() -> Void)?) -> Alert { - disappearCallback = callback - return self - } - /// 设置持续时间 - /// - Parameter duration: 持续时间 - @discardableResult func duration(_ duration: TimeInterval?) -> Alert { - model.setupDuration(duration: duration) { [weak self] in - self?.pop() - } - return self - } - - /// 更新 - /// - Parameter scene: 场景 - /// - Parameter title: 标题 - /// - Parameter message: 正文 - @discardableResult func update(scene: Scene, title: String?, message: String?) -> Alert { - model.scene = scene - model.title = title - model.message = message - willLayoutSubviews() - return self - } - - /// 更新图标 - /// - Parameter icon: 图标 - @discardableResult func update(icon: UIImage?) -> Alert { - model.icon = icon - cfg.alert.reloadData(self) - return self - } - - @discardableResult func animate(rotate: Bool) -> Alert { - if rotate { + /// 图片旋转效果 + /// - Parameter flag: 是否开启 + func rotate(_ flag: Bool = true) { + if flag { DispatchQueue.main.async { let ani = CABasicAnimation(keyPath: "transform.rotation.z") - ani.toValue = M_PI*2.0 - ani.duration = 2 + ani.toValue = Double.pi * 2.0 + ani.duration = 3 ani.repeatCount = 10000 self.imageView?.layer.add(ani, forKey: "rotationAnimation") } } else { imageView?.layer.removeAllAnimations() } - return self } - /// 更新按钮 - /// - Parameter index: 索引 - /// - Parameter style: 样式 - /// - Parameter title: 标题 - /// - Parameter action: 事件 - @discardableResult func update(action index: Int, style: UIAlertAction.Style, title: String?, action: (() -> Void)?) -> Alert { - 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) - } - btn.layoutIfNeeded() - if let ac = action { - addTouchUpAction(for: btn, action: ac) - } - } - return self - } - /// 移除按钮 - /// - Parameter index: 索引 - @discardableResult func remove(action index: Int...) -> Alert { - for (i, idx) in index.enumerated() { - privRemoveAction(index: idx-i) - } - return self - } + } -// MARK: 类函数 - +// MARK: - 实例管理器 public extension Alert { /// 推入屏幕 @@ -247,13 +178,13 @@ public extension Alert { /// - Parameter title: 标题 /// - Parameter message: 正文 /// - Parameter actions: 更多操作 - @discardableResult class func push(alert scene: Alert.Scene, title: String? = nil, message: String? = nil, actions: ((Alert) -> Void)? = nil) -> Alert { + @discardableResult class func push(scene: Alert.Scene = .default, title: String? = nil, message: String? = nil, _ actions: ((Alert) -> Void)? = nil) -> Alert { return Alert(scene: scene, title: title, message: message, actions: actions).push() } - /// 获取指定的实例 + /// 查找指定的实例 /// - Parameter identifier: 指定实例的标识 - class func alerts(_ identifier: String?) -> [Alert] { + class func find(_ identifier: String?) -> [Alert] { var aa = [Alert]() for a in Alert.alerts { if a.identifier == identifier { @@ -263,6 +194,18 @@ public extension Alert { return aa } + /// 查找指定的实例 + /// - Parameter identifier: 标识 + /// - Parameter last: 已经存在(获取最后一个) + /// - Parameter none: 不存在 + class func find(_ identifier: String?, last: ((Alert) -> Void)? = nil, none: (() -> Void)? = nil) { + if let t = find(identifier).last { + last?(t) + } else { + none?() + } + } + /// 弹出屏幕 /// - Parameter alert: 实例 class func pop(_ alert: Alert) { @@ -272,82 +215,101 @@ public extension Alert { /// 弹出屏幕 /// - Parameter identifier: 指定实例的标识 class func pop(_ identifier: String?) { - for a in alerts(identifier) { + for a in find(identifier) { a.pop() } } } -// MARK: - 私有 -fileprivate extension Alert { +// MARK: - 创建和设置 +internal extension Alert { + + /// 插入一个按钮 + /// - 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() + } + addTouchUpAction(for: btn) { [weak self] in + handler?() + if btn.tag == UIAlertAction.Style.cancel.rawValue { + self?.pop() + } + } + if isLoadFinished { + actionStack.layoutIfNeeded() + UIView.animateForAlert { + self.view.layoutIfNeeded() + } + } + return btn + } + + /// 更新按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter handler: 事件 + 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) -> Alert { + @discardableResult func remove(action index: Int) -> Alert { if index < 0 { for view in self.actionStack.arrangedSubviews { if let btn = view as? UIButton { btn.removeFromSuperview() + if let _ = buttonEvents[btn] { + buttonEvents.removeValue(forKey: btn) + } } } } else if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton { btn.removeFromSuperview() + if let _ = buttonEvents[btn] { + buttonEvents.removeValue(forKey: btn) + } } if self.actionStack.arrangedSubviews.count == 0 { self.actionStack.removeFromSuperview() } - willLayoutSubviews() UIView.animateForAlert { self.view.layoutIfNeeded() } return self } - - func willLayoutSubviews() { - model.setupWillLayout(duration: 0.001) { [weak self] in - if let a = self { - // 布局 - cfg.alert.loadSubviews(a) - cfg.alert.reloadData(a) - cfg.alert.setupDefaultDuration(a) - // 强制退出按钮 - a.model.setupForceQuit(duration: cfg.alert.forceQuitTimer) { [weak self] in - if let aa = self, aa.actionStack.superview == nil { - cfg.alert.loadForceQuitButton(aa) - } - } - } - } - } - - @discardableResult func privAddButton(custom button: UIButton, action: (() -> Void)?) -> UIButton { - model.duration = nil - if actionStack.superview == nil { - contentStack.addArrangedSubview(actionStack) - } - self.view.layoutIfNeeded() - button.transform = .init(scaleX: 1, y: 0.001) - actionStack.addArrangedSubview(button) - UIView.animateForAlert { - button.transform = .identity - self.view.layoutIfNeeded() - } - addTouchUpAction(for: button) { [weak self] in - action?() - if button.tag == UIAlertAction.Style.cancel.rawValue { - self?.pop() - } - } - willLayoutSubviews() - return button - } - } 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: { @@ -358,7 +320,7 @@ fileprivate extension Alert { } } } - class func getAlertWindow(_ vc: UIViewController) -> UIWindow { + class func privGetAlertWindow(_ vc: UIViewController) -> UIWindow { if let w = alertWindow { return w } @@ -370,7 +332,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 { @@ -379,7 +341,7 @@ fileprivate extension Alert { } } } - updateAlertsLayout() + privUpdateAlertsLayout() } else if alerts.count == 1 { alerts.removeAll() } else { diff --git a/ProHUD/Alert/AlertModel.swift b/ProHUD/Alert/AlertModel.swift index ca210b0..ebdc493 100644 --- a/ProHUD/Alert/AlertModel.swift +++ b/ProHUD/Alert/AlertModel.swift @@ -33,22 +33,42 @@ public extension Alert { } - struct ViewModel { + class ViewModel { /// 使用场景 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? + /// 持续时间(为空代表根据场景不同的默认配置,为0代表无穷大) + public var duration: TimeInterval? { + didSet { + updateDuration() + } + } + + public weak var vc: Alert? + + // MARK: 私有 /// 持续时间 internal var durationBlock: DispatchWorkItem? @@ -59,40 +79,59 @@ public extension Alert { /// 强制退出代码 internal var forceQuitCallback: (() -> Void)? - internal var willLayoutBlock: DispatchWorkItem? - - internal mutating func setupDuration(duration: TimeInterval?, callback: @escaping () -> Void) { - self.duration = duration + internal func updateDuration() { durationBlock?.cancel() - if let t = duration, t > 0 { - durationBlock = DispatchWorkItem(block: callback) + if let t = duration ?? cfg.alert.durationForScene(scene), t > 0 { + durationBlock = DispatchWorkItem(block: { [weak self] in + self?.vc?.pop() + }) DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: durationBlock!) } else { durationBlock = nil } } - internal mutating func setupForceQuit(duration: TimeInterval?, callback: @escaping () -> Void) { - forceQuitTimerBlock?.cancel() - if let t = duration, t > 0 { - forceQuitTimerBlock = DispatchWorkItem(block: callback) - DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: forceQuitTimerBlock!) - } else { - forceQuitTimerBlock = nil - } - } - - internal mutating func setupWillLayout(duration: TimeInterval?, callback: @escaping () -> Void) { - willLayoutBlock?.cancel() - if let t = duration, t > 0 { - willLayoutBlock = DispatchWorkItem(block: callback) - DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: willLayoutBlock!) - } else { - willLayoutBlock = nil - } - } - } } +public extension Alert.ViewModel { + + /// 添加按钮 + /// - Parameter style: 样式 + /// - Parameter text: 标题 + /// - Parameter handler: 事件处理 + @discardableResult func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + duration = 0 + return vc!.insert(action: nil, style: style, title: title, handler: handler) + } + + /// 插入按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter handler: 事件处理 + @discardableResult func insert(action index: Int, style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + duration = 0 + return vc!.insert(action: index, style: style, title: title, handler: handler) + } + + /// 更新按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter 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?.remove(action: idx-i) + } + } + + +} diff --git a/ProHUD/Alert/AlertView.swift b/ProHUD/Alert/AlertView.swift deleted file mode 100644 index a384214..0000000 --- a/ProHUD/Alert/AlertView.swift +++ /dev/null @@ -1,54 +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 { - 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 - } - - } - -} diff --git a/ProHUD/Guard/GuardConfig.swift b/ProHUD/Guard/GuardConfig.swift index 8e4b61e..153af26 100644 --- a/ProHUD/Guard/GuardConfig.swift +++ b/ProHUD/Guard/GuardConfig.swift @@ -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,22 +74,16 @@ internal extension ProHUD.Configuration.Guard { } -fileprivate var privLoadSubviews: (ProHUD.Guard) -> Void = { +// MARK: - 默认实现 +fileprivate var privReloadData: (ProHUD.Guard) -> Void = { return { (vc) in - debug(vc, "loadSubviews") + debug(vc, "reloadData") let config = cfg.guard // background vc.view.tintColor = config.tintColor vc.view.backgroundColor = UIColor(white: 0, alpha: 0) vc.view.addSubview(vc.contentView) vc.contentView.contentView.addSubview(vc.contentStack) - } -}() - -fileprivate var privReloadData: (ProHUD.Guard) -> Void = { - return { (vc) in - debug(vc, "reloadData") - let config = cfg.guard // 更新布局 var width = UIScreen.main.bounds.width if width > config.cardMaxWidth { @@ -111,12 +97,15 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = { vc.contentView.layer.shadowOpacity = 0.12 } vc.contentView.snp.makeConstraints { (mk) in + if isPortrait && vc.isFullScreen { + mk.top.equalToSuperview() + } 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() @@ -125,12 +114,21 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = { } // stack vc.contentStack.snp.makeConstraints { (mk) in - mk.top.equalToSuperview().offset(config.padding + config.margin) - mk.centerX.equalToSuperview() - if width == config.cardMaxWidth { - mk.bottom.equalToSuperview().offset(-config.padding) + if isPortrait && vc.isFullScreen { + mk.top.equalToSuperview().offset(Inspire.shared.screen.safeAreaInsets.top) } else { - mk.bottom.equalToSuperview().offset(-config.padding-Inspire.shared.screen.safeAreaInsets.bottom) + mk.top.equalToSuperview().offset(config.padding) + } + mk.centerX.equalToSuperview() + 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) } if isPortrait { mk.width.equalToSuperview().offset(-config.padding * 2) @@ -138,6 +136,6 @@ fileprivate var privReloadData: (ProHUD.Guard) -> Void = { mk.width.equalToSuperview().offset(-config.padding * 4) } } - + config.reloadStack(vc) } }() diff --git a/ProHUD/Guard/GuardController.swift b/ProHUD/Guard/GuardController.swift index 60cc46c..578cc2e 100644 --- a/ProHUD/Guard/GuardController.swift +++ b/ProHUD/Guard/GuardController.swift @@ -7,6 +7,7 @@ // import SnapKit +import Inspire public typealias Guard = ProHUD.Guard @@ -15,7 +16,7 @@ public extension ProHUD { class Guard: HUDController { /// 内容视图 - public var contentView = BlurView() + public var contentView = createBlurView() /// 内容容器(包括textStack、actionStack,可以自己插入需要的控件) public var contentStack: StackContainer = { @@ -42,15 +43,21 @@ public extension ProHUD { }() /// 是否是强制性的(点击空白处是否可以消失) - public var force = false + public var isForce = false + + /// 是否是全屏的(仅手机竖屏有效) + public var isFullScreen = false /// 是否正在显示 - private var displaying = false + private var isDisplaying = false /// 背景颜色 - public var backgroundColor: UIColor? = UIColor(white: 0, alpha: 0.5) + public var backgroundColor: UIColor? = UIColor(white: 0, alpha: 0.4) + + public var vm = ViewModel() // MARK: 生命周期 + private var isLoadFinished = false /// 实例化 /// - Parameter title: 标题 @@ -58,8 +65,7 @@ public extension ProHUD { /// - Parameter actions: 更多操作 public convenience init(title: String? = nil, message: String? = nil, actions: ((Guard) -> Void)? = nil) { self.init() - - view.tintColor = cfg.guard.tintColor + vm.vc = self if let _ = title { add(title: title) } @@ -67,31 +73,31 @@ public extension ProHUD { add(message: message) } actions?(self) - 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: 视图控制器 - func push(to viewController: UIViewController? = nil) -> Guard { + @discardableResult func push(to viewController: UIViewController? = nil) -> Guard { func f(_ vc: UIViewController) { view.layoutIfNeeded() vc.addChild(self) @@ -100,12 +106,12 @@ public extension Guard { view.snp.makeConstraints { (mk) in mk.edges.equalToSuperview() } - if displaying == false { - translateOut() + if isDisplaying == false { + privTranslateOut() } - displaying = true + isDisplaying = true UIView.animateForGuard { - self.translateIn() + self.privTranslateIn() } } if let vc = viewController ?? cfg.rootViewController { @@ -118,114 +124,33 @@ public extension Guard { /// 从父视图控制器弹出 func pop() { - if displaying { + if isDisplaying { debug("pop") willDisappearCallback?() - displaying = false + isDisplaying = false view.isUserInteractionEnabled = false self.removeFromParent() UIView.animateForGuard(animations: { - self.translateOut() + self.privTranslateOut() }) { (done) in - if self.displaying == false { + if self.isDisplaying == false { self.view.removeFromSuperview() } } } } - // 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 willDisappear(_ callback: (() -> Void)?) -> Guard { - willDisappearCallback = callback - return self - } - /// 消失事件 - /// - Parameter callback: 事件回调 - @discardableResult func didDisappear(_ callback: (() -> Void)?) -> Guard { - disappearCallback = callback - return self - } } - -// MARK: 类函数 - +// MARK: - 实例管理器 public extension Guard { /// 推入屏幕 @@ -233,13 +158,13 @@ public extension Guard { /// - Parameter title: 标题 /// - Parameter message: 正文 /// - Parameter icon: 图标 - @discardableResult class func push(to viewController: UIViewController? = nil, actions: ((Guard) -> Void)? = nil) -> Guard { + @discardableResult class func push(to viewController: UIViewController? = nil, _ actions: ((Guard) -> Void)? = nil) -> Guard { return Guard(actions: actions).push(to: viewController) } - /// 获取指定的实例 + /// 查找指定的实例 /// - Parameter identifier: 指定实例的标识 - class func guards(_ identifier: String? = nil, from viewController: UIViewController? = nil) -> [Guard] { + class func find(_ identifier: String?, from viewController: UIViewController? = nil) -> [Guard] { var gg = [Guard]() if let vc = viewController ?? cfg.rootViewController { for child in vc.children { @@ -259,16 +184,29 @@ public extension Guard { return gg } + /// 查找指定的实例 + /// - Parameter identifier: 标识 + /// - Parameter last: 已经存在(获取最后一个) + /// - Parameter none: 不存在 + class func find(_ identifier: String?, from viewController: UIViewController? = nil, last: ((Guard) -> Void)? = nil, none: (() -> Void)? = nil) { + if let t = find(identifier, from: viewController).last { + last?(t) + } else { + none?() + } + } + + /// 弹出屏幕 /// - Parameter alert: 实例 class func pop(_ guard: Guard) { `guard`.pop() } - /// 弹出屏幕 + /// 弹出所有实例 /// - Parameter identifier: 指定实例的标识 - class func pop(from viewController: UIViewController?) { - for g in guards(from: viewController) { + class func pop(_ identifier: String?, from viewController: UIViewController?) { + for g in find(identifier, from: viewController) { g.pop() } } @@ -277,44 +215,123 @@ public extension Guard { -// MARK: - 私有 - -fileprivate extension Guard { +// 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) + /// 更新按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter handler: 事件 + 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 { btn.removeFromSuperview() + if let _ = buttonEvents[btn] { + buttonEvents.removeValue(forKey: btn) + } } } } else if index < self.actionStack.arrangedSubviews.count, let btn = self.actionStack.arrangedSubviews[index] as? UIButton { btn.removeFromSuperview() + if let _ = buttonEvents[btn] { + buttonEvents.removeValue(forKey: btn) + } } cfg.guard.reloadStack(self) UIView.animateForAlert { @@ -323,7 +340,31 @@ fileprivate extension Guard { return self } - - } +fileprivate extension Guard { + + /// 点击事件 + /// - Parameter sender: 手势 + @objc func privDidTapped(_ sender: UITapGestureRecognizer) { + let point = sender.location(in: contentView) + if point.x < 0 || point.y < 0 { + if isForce == 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) + } + +} diff --git a/ProHUD/Guard/GuardModel.swift b/ProHUD/Guard/GuardModel.swift new file mode 100644 index 0000000..b4711da --- /dev/null +++ b/ProHUD/Guard/GuardModel.swift @@ -0,0 +1,76 @@ +// +// GuardModel.swift +// ProHUD +// +// Created by xaoxuu on 2019/8/9. +// Copyright © 2019 Titan Studio. All rights reserved. +// + +import UIKit + +public extension Guard { + + struct ViewModel { + + internal weak var vc: Guard? + + } + +} + +public extension Guard.ViewModel { + + /// 加载一个标题 + /// - Parameter text: 文本 + @discardableResult func add(title: String?) -> UILabel { + return vc!.add(title: title) + } + + /// 加载一个副标题 + /// - Parameter text: 文本 + @discardableResult func add(subTitle: String?) -> UILabel { + return vc!.add(subTitle: subTitle) + } + + /// 加载一段正文 + /// - Parameter text: 文本 + @discardableResult func add(message: String?) -> UILabel { + return vc!.add(message: message) + } + + /// 加载一个按钮 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter action: 事件 + @discardableResult func add(action style: UIAlertAction.Style, title: String?, handler: (() -> Void)?) -> UIButton { + return vc!.insert(action: nil, style: style, title: title, handler: handler) + } + + /// 插入一个按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter handler: 事件处理 + @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) + } + + /// 更新按钮 + /// - Parameter index: 索引 + /// - Parameter style: 样式 + /// - Parameter title: 标题 + /// - Parameter 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?.remove(index: idx-i) + } + } + + +} diff --git a/ProHUD/Guard/GuardView.swift b/ProHUD/Guard/GuardView.swift deleted file mode 100644 index 5d335ec..0000000 --- a/ProHUD/Guard/GuardView.swift +++ /dev/null @@ -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 - } - - } -} - diff --git a/ProHUD/HUDConfig.swift b/ProHUD/HUDConfig.swift index 2b1bb70..b5047e5 100644 --- a/ProHUD/HUDConfig.swift +++ b/ProHUD/HUDConfig.swift @@ -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 + } +}() diff --git a/ProHUD/HUDController.swift b/ProHUD/HUDController.swift index b2a9808..48d601d 100644 --- a/ProHUD/HUDController.swift +++ b/ProHUD/HUDController.swift @@ -13,9 +13,10 @@ public class HUDController: UIViewController { /// ID标识 public var identifier = String(Date().timeIntervalSince1970) + internal var willAppearCallback: (() -> Void)? + internal var didAppearCallback: (() -> Void)? internal var willDisappearCallback: (() -> Void)? - /// 消失回调 - internal var disappearCallback: (() -> Void)? + internal var didDisappearCallback: (() -> Void)? /// 按钮事件 internal var buttonEvents = [UIButton:() -> Void]() @@ -40,10 +41,31 @@ public class HUDController: UIViewController { // Do any additional setup after loading the view. } - + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + willAppearCallback?() + } + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + didAppearCallback?() + } public override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - disappearCallback?() + didDisappearCallback?() + } + + public func viewWillAppear(_ callback: (() -> Void)?) { + willAppearCallback = callback + } + public func viewDidAppear(_ callback: (() -> Void)?) { + didAppearCallback = callback + } + + public func viewWillDisappear(_ callback: (() -> Void)?) { + willDisappearCallback = callback + } + public func viewDidDisappear(_ callback: (() -> Void)?) { + didDisappearCallback = callback } } diff --git a/ProHUD/HUDView.swift b/ProHUD/HUDView.swift index 5e5462b..8e8f588 100644 --- a/ProHUD/HUDView.swift +++ b/ProHUD/HUDView.swift @@ -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) + } + } diff --git a/ProHUD/Toast/ToastConfig.swift b/ProHUD/Toast/ToastConfig.swift index 673eed1..cc858da 100644 --- a/ProHUD/Toast/ToastConfig.swift +++ b/ProHUD/Toast/ToastConfig.swift @@ -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,56 @@ 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 } + /// 默认持续时间(当viewmodel的duration为nil时,会从这里获取) + public mutating func durationForScene(_ callback: @escaping (ProHUD.Toast.Scene) -> TimeInterval?) { + privDurationForScene = callback + } + } } -// MARK: - 默认实现 +// MARK: - 内部调用 internal extension ProHUD.Configuration.Toast { - var loadSubviews: (ProHUD.Toast) -> Void { - return privLoadSubviews - } + var reloadData: (ProHUD.Toast) -> Void { return privReloadData } + + var durationForScene: (ProHUD.Toast.Scene) -> TimeInterval? { + return privDurationForScene + } + } -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.imageView.layer.removeAllAnimations() 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 +111,45 @@ fileprivate var privReloadData: (ProHUD.Toast) -> Void = { } vc.view.layoutIfNeeded() - switch vc.model.scene { - case .loading: - vc.duration(nil) - default: - vc.duration(3) - } + + // 设置持续时间 + vc.vm.updateDuration() } }() + + + +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) + } +}() + + +fileprivate var privDurationForScene: (ProHUD.Toast.Scene) -> TimeInterval? = { + return { (scene) in + switch scene { + case .loading: + return nil + case .error, .warning: + return 5 + default: + return 3 + } + } +}() diff --git a/ProHUD/Toast/ToastController.swift b/ProHUD/Toast/ToastController.swift index dadcbee..c283383 100644 --- a/ProHUD/Toast/ToastController.swift +++ b/ProHUD/Toast/ToastController.swift @@ -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,16 @@ 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, duration: TimeInterval? = nil, actions: ((Toast) -> Void)? = nil) { self.init() + vm.vc = self - model.scene = scene - model.title = title - model.message = message - model.icon = icon + vm.scene = scene + vm.title = title + vm.message = message + vm.icon = icon + vm.duration = duration actions?(self) - // 布局 - cfg.toast.loadSubviews(self) - cfg.toast.reloadData(self) // 点击 let tap = UITapGestureRecognizer(target: self, action: #selector(privDidTapped(_:))) @@ -94,16 +86,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 +142,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,83 +156,58 @@ 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 - return self + func didTapped(_ callback: (() -> Void)?) { + vm.tapCallback = callback } - /// 消失事件 - /// - Parameter callback: 事件回调 - @discardableResult func didDisappear(_ callback: (() -> Void)?) -> Toast { - disappearCallback = callback - return self + /// 图片旋转效果 + /// - Parameter flag: 是否开启 + func rotate(_ flag: Bool = true) { + if flag { + DispatchQueue.main.async { + let ani = CABasicAnimation(keyPath: "transform.rotation.z") + ani.toValue = Double.pi * 2.0 + ani.duration = 3 + ani.repeatCount = 10000 + self.imageView.layer.add(ani, forKey: "rotationAnimation") + } + } else { + imageView.layer.removeAllAnimations() + } } - /// 更新 - /// - 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 + /// 脉冲效果 + func pulse() { + DispatchQueue.main.async { + UIView.animate(withDuration: 0.2, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.allowUserInteraction, .curveEaseOut], animations: { + self.window?.transform = .init(scaleX: 1.04, y: 1.04) + }) { (done) in + UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1, options: [.allowUserInteraction, .curveEaseIn], animations: { + self.window?.transform = .identity + }) { (done) in + + } + } + } } } -// MARK: 类函数 - +// MARK: - 实例管理器 public extension Toast { /// 推入屏幕 @@ -243,13 +215,13 @@ public extension Toast { /// - Parameter title: 标题 /// - Parameter message: 内容 /// - Parameter actions: 更多操作 - @discardableResult class func push(toast scene: Toast.Scene, title: String? = nil, message: String? = nil, actions: ((Toast) -> Void)? = nil) -> Toast { - return Toast(scene: scene, title: title, message: message, actions: actions).push() + @discardableResult class func push(scene: Toast.Scene = .default, title: String? = nil, message: String? = nil, duration: TimeInterval? = nil, _ actions: ((Toast) -> Void)? = nil) -> Toast { + return Toast(scene: scene, title: title, message: message, duration: duration, actions: actions).push() } - /// 获取指定的toast + /// 查找指定的实例 /// - Parameter identifier: 标识 - class func toasts(_ identifier: String?) -> [Toast] { + class func find(_ identifier: String?) -> [Toast] { var tt = [Toast]() for t in toasts { if t.identifier == identifier { @@ -259,73 +231,87 @@ public extension Toast { return tt } + /// 查找指定的实例 + /// - Parameter identifier: 标识 + /// - Parameter last: 已经存在(获取最后一个) + /// - Parameter none: 不存在 + class func find(_ identifier: String?, last: ((Toast) -> Void)? = nil, none: (() -> Void)? = nil) { + if let t = find(identifier).last { + last?(t) + } else { + none?() + } + } + /// 弹出屏幕 /// - Parameter toast: 实例 class func pop(_ toast: Toast) { - toast.pop() - } - - /// 弹出屏幕 - /// - Parameter identifier: 指定实例的标识 - class func pop(_ identifier: String?) { - for t in toasts(identifier) { - t.pop() - } - } - -} - -// MARK: 私有 - -fileprivate var willUpdateToastsLayout: DispatchWorkItem? - -fileprivate extension Toast { - - /// 点击事件 - /// - Parameter sender: 手势 - @objc func privDidTapped(_ sender: UITapGestureRecognizer) { - model.tapCallback?() - } - - /// 拖拽事件 - /// - Parameter sender: 手势 - @objc func privDidPan(_ sender: UIPanGestureRecognizer) { - model.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) { - // 移除 - self.pop() - } else { - UIView.animateForToast(animations: { - self.window?.transform = .identity - }) { (done) in - // FIXME: 重置计时器 - - } - } - } - } - - /// 从数组中移除 - /// - Parameter toast: 实例 - class func removeItemFromArray(toast: Toast) { + toast.willDisappearCallback?() if toasts.count > 1 { for (i, t) in toasts.enumerated() { if t == toast { toasts.remove(at: i) } } - updateToastsLayout() + 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 + } } - class func updateToastsLayout() { + + /// 弹出屏幕 + /// - Parameter identifier: 指定实例的标识 + class func pop(_ identifier: String?) { + for t in find(identifier) { + t.pop() + } + } + +} + +// MARK: - 创建和设置 +fileprivate var willprivUpdateToastsLayout: DispatchWorkItem? + +fileprivate extension Toast { + + /// 点击事件 + /// - Parameter sender: 手势 + @objc func privDidTapped(_ sender: UITapGestureRecognizer) { + vm.tapCallback?() + } + + /// 拖拽事件 + /// - Parameter sender: 手势 + @objc func privDidPan(_ sender: UIPanGestureRecognizer) { + 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 vm.isRemovable == 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 + let d = self.vm.duration + self.vm.duration = d + } + } + } + } + + class func privUpdateToastsLayout() { func f() { let top = Inspire.shared.screen.updatedSafeAreaInsets.top for (i, e) in toasts.enumerated() { @@ -342,18 +328,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!) } + } diff --git a/ProHUD/Toast/ToastModel.swift b/ProHUD/Toast/ToastModel.swift index 2cafc4a..e46bb33 100644 --- a/ProHUD/Toast/ToastModel.swift +++ b/ProHUD/Toast/ToastModel.swift @@ -16,12 +16,6 @@ public extension Toast { /// 加载中场景 case loading - /// 确认场景 - case confirm - - /// 删除场景 - case delete - /// 成功场景 case success @@ -33,46 +27,65 @@ public extension Toast { } - struct ViewModel { + class ViewModel { /// 使用场景 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 { + updateDuration() + } + } + + public weak var vc: Toast? + + /// 是否可以通过手势移除(向上滑出屏幕) + public var isRemovable = 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 + internal func updateDuration() { durationBlock?.cancel() - if let t = duration, t > 0 { - durationBlock = DispatchWorkItem(block: callback) + if let t = duration ?? cfg.toast.durationForScene(scene), t > 0 { + durationBlock = DispatchWorkItem(block: { [weak self] in + self?.vc?.pop() + }) DispatchQueue.main.asyncAfter(deadline: .now()+t, execute: durationBlock!) } else { durationBlock = nil } } - } } - - diff --git a/ProHUD/Toast/ToastView.swift b/ProHUD/Toast/ToastView.swift deleted file mode 100644 index 39a4593..0000000 --- a/ProHUD/Toast/ToastView.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// ToastView.swift -// ProHUD -// -// Created by xaoxuu on 2019/7/31. -// Copyright © 2019 Titan Studio. All rights reserved. -// - -import Foundation