Merge branch 'dev-1.1'

# Conflicts:
#	ProHUD/Guard/GuardController.swift
#	ProHUD/HUDController.swift
This commit is contained in:
xaoxuu 2019-08-13 10:03:43 +08:00
commit 42e36b8f8e
34 changed files with 1602 additions and 1123 deletions

View File

@ -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 = "<group>"; };
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 = "<group>"; };
CD10F0D9211582580077CAFF /* header.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = header.gif; sourceTree = "<group>"; };
CD4B40A923017C09005111B9 /* RootVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootVC.swift; sourceTree = "<group>"; };
CD59584620E36DA8000F6427 /* Example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example-Bridging-Header.h"; sourceTree = "<group>"; };
CD95D26E22E732CE007559A3 /* TestA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestA.swift; sourceTree = "<group>"; };
CD8BFF1723014850001E08DD /* TestToastVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestToastVC.swift; sourceTree = "<group>"; };
CD8BFF192301485E001E08DD /* TestAlertVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAlertVC.swift; sourceTree = "<group>"; };
CD8BFF1B23014867001E08DD /* TestGuardVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGuardVC.swift; sourceTree = "<group>"; };
CD8BFF1D230148DD001E08DD /* BaseListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseListVC.swift; sourceTree = "<group>"; };
CD8BFF1F23014CB5001E08DD /* EmptyVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyVC.swift; sourceTree = "<group>"; };
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 = "<group>"; };
CDA4E03D20D3935B00CD2A0C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@ -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;
};

View File

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

View File

@ -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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

View File

@ -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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14835.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14835.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ttk-4w-IeX">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
@ -8,42 +8,39 @@
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<!--ProHUD-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Example" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" image="bg" translatesAutoresizingMaskIntoConstraints="NO" id="0oA-CL-uZS">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="PcH-Px-Vql">
<rect key="frame" x="51" y="120" width="46" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Button"/>
<connections>
<action selector="test:" destination="BYZ-38-t0r" eventType="touchUpInside" id="mff-h5-exz"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="0oA-CL-uZS" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="5mO-PC-le4"/>
<constraint firstItem="0oA-CL-uZS" firstAttribute="top" secondItem="8bC-Xf-vdC" secondAttribute="top" id="Fay-dw-LvJ"/>
<constraint firstAttribute="bottom" secondItem="0oA-CL-uZS" secondAttribute="bottom" id="TC7-O2-j1c"/>
<constraint firstAttribute="trailing" secondItem="0oA-CL-uZS" secondAttribute="trailing" id="U3A-8b-rnZ"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<navigationItem key="navigationItem" title="ProHUD" id="Yzl-Q6-9v7"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="142" y="134"/>
<point key="canvasLocation" x="1050.7246376811595" y="133.92857142857142"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="HjC-hd-4PZ">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="ttk-4w-IeX" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="juX-kz-8Ti">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="gyV-fc-5FK"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Lg9-xW-IRh" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="140.57971014492756" y="133.92857142857142"/>
</scene>
</scenes>
<resources>
<image name="bg" width="414" height="896"/>
</resources>
</document>

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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.
}
*/
}

View File

@ -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")
}
}

View File

@ -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 = "啊哈哈哈哈哈哈哈哈"
}
})
}
}
}

View File

@ -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()
}
}
}
}
}
}

View File

@ -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 = "啊哈哈哈哈哈哈哈哈"
}
})
}
}
}

View File

@ -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<UITouch>, 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: "")
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 KiB

View File

@ -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 = "<group>"; };
CD16490C22EF09B40077988C /* AlertConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertConfig.swift; sourceTree = "<group>"; };
CD16490E22EF09D50077988C /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = "<group>"; };
CD16491122EF0D900077988C /* HUDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDView.swift; sourceTree = "<group>"; };
CD16491322EF12220077988C /* ProHUD.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = ProHUD.xcassets; sourceTree = "<group>"; };
CD6CD86B22F1858F00F4FD4A /* ToastModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastModel.swift; sourceTree = "<group>"; };
CD6CD86D22F185A000F4FD4A /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
CD6CD86F22F185A700F4FD4A /* ToastController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastController.swift; sourceTree = "<group>"; };
CD6CD87122F185AF00F4FD4A /* ToastConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastConfig.swift; sourceTree = "<group>"; };
CD6CD87422F185C200F4FD4A /* GuardController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardController.swift; sourceTree = "<group>"; };
CD6CD87822F185D000F4FD4A /* GuardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardConfig.swift; sourceTree = "<group>"; };
CD6CD87A22F185D600F4FD4A /* GuardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardView.swift; sourceTree = "<group>"; };
CD95D21C22E72C4C007559A3 /* ProHUD.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ProHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CD95D21F22E72C4C007559A3 /* ProHUD.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProHUD.h; sourceTree = "<group>"; };
CD95D22022E72C4C007559A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -50,6 +45,7 @@
CD95D26A22E72DB3007559A3 /* ProHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProHUD.swift; sourceTree = "<group>"; };
CDB6A07A22EEF06500AF6CF0 /* HUDController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDController.swift; sourceTree = "<group>"; };
CDB6A07C22EEF19D00AF6CF0 /* HUDConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDConfig.swift; sourceTree = "<group>"; };
CDC39CFC22FD6DDF0070E914 /* GuardModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardModel.swift; sourceTree = "<group>"; };
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 = "<group>"; };
/* End PBXFileReference section */
@ -88,7 +84,6 @@
CD95D26822E72DA1007559A3 /* AlertController.swift */,
CD16490A22EF09AB0077988C /* AlertModel.swift */,
CD16490C22EF09B40077988C /* AlertConfig.swift */,
CD16490E22EF09D50077988C /* AlertView.swift */,
);
path = Alert;
sourceTree = "<group>";
@ -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 = "<group>";
@ -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;
};

View File

@ -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
/// viewmodeldurationnil
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
}
}
}()

View File

@ -19,7 +19,7 @@ public extension ProHUD {
internal static var alertWindow: UIWindow?
///
public var contentView = BlurView()
public var contentView = createBlurView()
/// icontextStackactionStack)
public var contentStack: StackContainer = {
@ -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 {

View File

@ -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)
}
}
}

View File

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

View File

@ -22,7 +22,6 @@ public extension ProHUD.Configuration {
///
public var padding = CGFloat(16)
// MARK:
///
public var tintColor: UIColor?
@ -41,12 +40,6 @@ public extension ProHUD.Configuration {
///
public var buttonCornerRadius = CGFloat(12)
///
/// - Parameter callback:
public mutating func loadSubviews(_ callback: @escaping (ProHUD.Guard) -> Void) {
privLoadSubviews = callback
}
///
/// - Parameter callback:
public mutating func reloadData(_ callback: @escaping (ProHUD.Guard) -> Void) {
@ -56,15 +49,14 @@ public extension ProHUD.Configuration {
}
}
// MARK: -
// MARK: -
internal extension ProHUD.Configuration.Guard {
var loadSubviews: (ProHUD.Guard) -> Void {
return privLoadSubviews
}
var reloadData: (ProHUD.Guard) -> Void {
return privReloadData
}
var reloadStack: (ProHUD.Guard) -> Void {
return { (vc) in
if vc.textStack.arrangedSubviews.count > 0 {
@ -82,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)
}
}()

View File

@ -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()
/// textStackactionStack)
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)
}
}

View File

@ -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)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -21,10 +21,13 @@ public extension ProHUD.Configuration {
public var padding = CGFloat(16)
// MARK:
/// default
public var tintColor: UIColor?
///
public var iconSize = CGSize(width: 48, height: 48)
///
/// - Parameter callback:
public func iconForScene(_ callback: @escaping (ProHUD.Toast.Scene) -> UIImage?) {
privIconForScene = callback
}
// MARK:
///
@ -37,71 +40,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
}
/// viewmodeldurationnil
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
}
}
}()

View File

@ -47,23 +47,16 @@ public extension ProHUD {
///
public var backgroundView: UIVisualEffectView = {
let vev = UIVisualEffectView()
if #available(iOS 13.0, *) {
// vev.effect = UIBlurEffect.init(style: .systemMaterial))
vev.effect = UIBlurEffect.init(style: .extraLight)
} else if #available(iOS 11.0, *) {
vev.effect = UIBlurEffect.init(style: .extraLight)
} else {
vev.effect = .none
vev.backgroundColor = .white
}
let vev = createBlurView()
vev.layer.masksToBounds = true
vev.layer.cornerRadius = cfg.toast.cornerRadius
return vev
}()
///
public var model = ViewModel()
public var vm = ViewModel()
internal var maxY = CGFloat(0)
// MARK:
@ -72,17 +65,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!)
}
}

View File

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

View File

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