mirror of https://github.com/xaoxuu/ProHUD
代码优化
This commit is contained in:
parent
a37cb52b79
commit
f68b0c3a9b
|
@ -0,0 +1,77 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1500"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "CD8EEF3628BC5C7200E660EA"
|
||||||
|
BuildableName = "PHDemo.app"
|
||||||
|
BlueprintName = "PHDemo"
|
||||||
|
ReferencedContainer = "container:PHDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "CD8EEF3628BC5C7200E660EA"
|
||||||
|
BuildableName = "PHDemo.app"
|
||||||
|
BlueprintName = "PHDemo"
|
||||||
|
ReferencedContainer = "container:PHDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "CD8EEF3628BC5C7200E660EA"
|
||||||
|
BuildableName = "PHDemo.app"
|
||||||
|
BlueprintName = "PHDemo"
|
||||||
|
ReferencedContainer = "container:PHDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -84,15 +84,15 @@ class DemoCapsuleVC: ListVC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
list.add(title: "不同位置、不同动画") { section in
|
list.add(title: "不同位置、不同动画,队列推送") { section in
|
||||||
section.add(title: "顶部,默认滑入") {
|
section.add(title: "顶部,默认动画") {
|
||||||
Capsule(.info("一条简短的消息"))
|
Capsule(.info("一条简短的消息").queuedPush(true).duration(1))
|
||||||
}
|
}
|
||||||
section.add(title: "中间,默认缩放") {
|
section.add(title: "中间,默认动画") {
|
||||||
Capsule(.middle.info("一条简短的消息"))
|
Capsule(.middle.queuedPush(true).info("一条简短的消息").duration(2))
|
||||||
}
|
}
|
||||||
section.add(title: "中间,黑底白字,透明渐变") {
|
section.add(title: "中间,黑底白字,透明渐变") {
|
||||||
Capsule(.middle.info("一条简短的消息")) { capsule in
|
Capsule(.middle.queuedPush(true).info("一条简短的消息").duration(1)) { capsule in
|
||||||
capsule.config.tintColor = .white
|
capsule.config.tintColor = .white
|
||||||
capsule.config.cardCornerRadius = 8
|
capsule.config.cardCornerRadius = 8
|
||||||
capsule.config.contentViewMask { mask in
|
capsule.config.contentViewMask { mask in
|
||||||
|
@ -119,7 +119,7 @@ class DemoCapsuleVC: ListVC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section.add(title: "底部,渐变背景,默认回弹滑入") {
|
section.add(title: "底部,渐变背景,默认回弹滑入") {
|
||||||
Capsule(.bottom.enter("点击进入")) { capsule in
|
Capsule(.bottom.queuedPush(true).enter("点击进入").duration(1)) { capsule in
|
||||||
capsule.config.tintColor = .white
|
capsule.config.tintColor = .white
|
||||||
capsule.config.cardEdgeInsets = .init(top: 12, left: 20, bottom: 12, right: 20)
|
capsule.config.cardEdgeInsets = .init(top: 12, left: 20, bottom: 12, right: 20)
|
||||||
capsule.config.customTextLabel { label in
|
capsule.config.customTextLabel { label in
|
||||||
|
@ -146,6 +146,25 @@ class DemoCapsuleVC: ListVC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
list.add(title: "lazy push") { section in
|
||||||
|
section.add(title: "id:1, text:1") {
|
||||||
|
Capsule(.test1("111:111"))
|
||||||
|
}
|
||||||
|
section.add(title: "id:1, text:2") {
|
||||||
|
Capsule(.test1("111:222"))
|
||||||
|
}
|
||||||
|
section.add(title: "id:2, text:1") {
|
||||||
|
Capsule(.test2("222:111"))
|
||||||
|
}
|
||||||
|
section.add(title: "id:2, text:2") {
|
||||||
|
Capsule(.test2("222:222"))
|
||||||
|
}
|
||||||
|
|
||||||
|
section.add(title: "id:2, text:2") {
|
||||||
|
Capsule(.test2("222:222"))
|
||||||
|
Capsule(.test2("222:111"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -179,4 +198,22 @@ extension CapsuleViewModel {
|
||||||
.message(text)
|
.message(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func test1(_ text: String) -> CapsuleViewModel {
|
||||||
|
.identifier("id:1")
|
||||||
|
.icon(.init(systemName: "video.circle.fill"))
|
||||||
|
.tintColor(.systemGreen)
|
||||||
|
.duration(1)
|
||||||
|
.queuedPush(true)
|
||||||
|
.message(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func test2(_ text: String) -> CapsuleViewModel {
|
||||||
|
.identifier("id:2")
|
||||||
|
.icon(.init(systemName: "mic.circle.fill"))
|
||||||
|
.tintColor(.systemOrange)
|
||||||
|
.duration(1)
|
||||||
|
.queuedPush(true)
|
||||||
|
.message(text)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ class DemoToastVC: ListVC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section.add(title: "图标 + 一段长文本") {
|
section.add(title: "图标 + 一段长文本") {
|
||||||
Toast(.note.message(message))
|
Toast(.note.message(message).duration(1))
|
||||||
}
|
}
|
||||||
section.add(title: "网络图标 + 一段文本") {
|
section.add(title: "网络图标 + 一段文本") {
|
||||||
Toast(.message("这是网络图标").icon(.init(string: "https://xaoxuu.com/assets/xaoxuu/avatar/rect-256@2x.png")))
|
Toast(.message("这是网络图标").icon(.init(string: "https://xaoxuu.com/assets/xaoxuu/avatar/rect-256@2x.png")))
|
||||||
|
@ -167,7 +167,7 @@ class DemoToastVC: ListVC {
|
||||||
section.add(title: "禁止手势移除") {
|
section.add(title: "禁止手势移除") {
|
||||||
let title = "这条消息很重要"
|
let title = "这条消息很重要"
|
||||||
let message = "向上滑动将不会移除消息,您必须手动处理,用于重要但非阻塞性的事件。(通过代码处理或者在点击事件处理)"
|
let message = "向上滑动将不会移除消息,您必须手动处理,用于重要但非阻塞性的事件。(通过代码处理或者在点击事件处理)"
|
||||||
Toast(.warning.title(title).message(message)) { toast in
|
Toast(.warning.title(title).message(message).duration(.infinity)) { toast in
|
||||||
toast.isRemovable = false
|
toast.isRemovable = false
|
||||||
toast.onTapped { toast in
|
toast.onTapped { toast in
|
||||||
toast.pop()
|
toast.pop()
|
||||||
|
|
|
@ -30,8 +30,8 @@ public class AlertConfiguration: CommonConfiguration {
|
||||||
customBackgroundViewMask = callback
|
customBackgroundViewMask = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
override var animateDurationForBuildInByDefault: CGFloat {
|
override var animateDurationForBuildOutByDefault: CGFloat {
|
||||||
animateDurationForBuildIn ?? 0.6
|
animateDurationForBuildOut ?? 0.2
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,13 +124,6 @@ extension AlertTarget: DefaultLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTimeoutDuration() {
|
|
||||||
// 设置持续时间
|
|
||||||
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
|
||||||
self?.pop()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AlertTarget {
|
extension AlertTarget {
|
||||||
|
|
|
@ -16,7 +16,7 @@ extension AlertTarget {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setDefaultAxis()
|
setDefaultAxis()
|
||||||
view.transform = .init(scaleX: 1.2, y: 1.2)
|
view.transform = .init(scaleX: 1.12, y: 1.12)
|
||||||
view.alpha = 0
|
view.alpha = 0
|
||||||
navEvents[.onViewWillAppear]?(self)
|
navEvents[.onViewWillAppear]?(self)
|
||||||
window.vc.addChild(self)
|
window.vc.addChild(self)
|
||||||
|
@ -33,6 +33,7 @@ extension AlertTarget {
|
||||||
window.backgroundView.alpha = 1
|
window.backgroundView.alpha = 1
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
self.navEvents[.onViewDidAppear]?(self)
|
self.navEvents[.onViewDidAppear]?(self)
|
||||||
|
self.updateTimeoutDuration()
|
||||||
}
|
}
|
||||||
window.alerts.append(self)
|
window.alerts.append(self)
|
||||||
AlertTarget.updateAlertsLayout(alerts: window.alerts)
|
AlertTarget.updateAlertsLayout(alerts: window.alerts)
|
||||||
|
@ -42,9 +43,9 @@ extension AlertTarget {
|
||||||
navEvents[.onViewWillDisappear]?(self)
|
navEvents[.onViewWillDisappear]?(self)
|
||||||
AlertTarget.removeAlert(alert: self)
|
AlertTarget.removeAlert(alert: self)
|
||||||
let duration = config.animateDurationForBuildOut ?? config.animateDurationForBuildOutByDefault
|
let duration = config.animateDurationForBuildOut ?? config.animateDurationForBuildOutByDefault
|
||||||
UIView.animateEaseOut(duration: duration) {
|
UIView.animateLinear(duration: duration) {
|
||||||
self.view.alpha = 0
|
self.view.alpha = 0
|
||||||
self.view.transform = .init(scaleX: 1.08, y: 1.08)
|
self.view.transform = .init(scaleX: 1.05, y: 1.05)
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
self.view.removeFromSuperview()
|
self.view.removeFromSuperview()
|
||||||
self.removeFromParent()
|
self.removeFromParent()
|
||||||
|
@ -55,7 +56,7 @@ extension AlertTarget {
|
||||||
let count = window.alerts.count
|
let count = window.alerts.count
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
AppContext.alertWindow[windowScene] = nil
|
AppContext.alertWindow[windowScene] = nil
|
||||||
UIView.animateEaseOut(duration: duration) {
|
UIView.animateLinear(duration: duration) {
|
||||||
window.backgroundView.alpha = 0
|
window.backgroundView.alpha = 0
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
// 这里设置一下window属性,会使window的生命周期被延长到此处,即动画执行过程中window不会被提前释放
|
// 这里设置一下window属性,会使window的生命周期被延长到此处,即动画执行过程中window不会被提前释放
|
||||||
|
@ -74,6 +75,13 @@ extension AlertTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateTimeoutDuration() {
|
||||||
|
// 设置持续时间
|
||||||
|
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||||
|
self?.pop()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - layout
|
// MARK: - layout
|
||||||
|
|
|
@ -36,6 +36,6 @@ class AlertWindow: Window {
|
||||||
|
|
||||||
extension AlertTarget {
|
extension AlertTarget {
|
||||||
var attachedWindow: AlertWindow? {
|
var attachedWindow: AlertWindow? {
|
||||||
view.window as? AlertWindow
|
view.window as? AlertWindow ?? AppContext.current?.alertWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,11 +35,11 @@ public class CapsuleConfiguration: CommonConfiguration {
|
||||||
override var cardMaxHeightByDefault: CGFloat { cardMaxHeight ?? 120 }
|
override var cardMaxHeightByDefault: CGFloat { cardMaxHeight ?? 120 }
|
||||||
|
|
||||||
override var animateDurationForBuildInByDefault: CGFloat {
|
override var animateDurationForBuildInByDefault: CGFloat {
|
||||||
animateDurationForBuildIn ?? 0.8
|
animateDurationForBuildIn ?? 0.64
|
||||||
}
|
}
|
||||||
|
|
||||||
override var animateDurationForBuildOutByDefault: CGFloat {
|
override var animateDurationForBuildOutByDefault: CGFloat {
|
||||||
animateDurationForBuildOut ?? 0.8
|
animateDurationForBuildOut ?? 0.32
|
||||||
}
|
}
|
||||||
|
|
||||||
var animateBuildIn: CustomAnimateHandler?
|
var animateBuildIn: CustomAnimateHandler?
|
||||||
|
|
|
@ -79,17 +79,6 @@ extension CapsuleTarget: DefaultLayout {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateTimeoutDuration() {
|
|
||||||
// 为空时使用默认规则
|
|
||||||
if vm?.duration == nil {
|
|
||||||
vm?.duration = config.defaultDuration
|
|
||||||
}
|
|
||||||
// 设置持续时间
|
|
||||||
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
|
||||||
self?.pop()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupImageView() {
|
private func setupImageView() {
|
||||||
// 移除动画
|
// 移除动画
|
||||||
stopRotate(animateLayer)
|
stopRotate(animateLayer)
|
||||||
|
|
|
@ -11,17 +11,38 @@ extension CapsuleTarget {
|
||||||
|
|
||||||
@objc open func push() {
|
@objc open func push() {
|
||||||
guard CapsuleConfiguration.isEnabled else { return }
|
guard CapsuleConfiguration.isEnabled else { return }
|
||||||
|
guard let windowScene = preferredWindowScene ?? AppContext.windowScene else { return }
|
||||||
|
if windowScene != AppContext.windowScene {
|
||||||
|
AppContext.windowScene = windowScene
|
||||||
|
}
|
||||||
|
|
||||||
let isNew: Bool
|
let isNew: Bool
|
||||||
let window: CapsuleWindow
|
let window: CapsuleWindow
|
||||||
let position = vm?.position ?? .top
|
let position = vm?.position ?? .top
|
||||||
|
|
||||||
if let w = AppContext.current?.capsuleWindows[position] {
|
if AppContext.capsuleWindows[windowScene] == nil {
|
||||||
|
AppContext.capsuleWindows[windowScene] = [:]
|
||||||
|
}
|
||||||
|
var windows = AppContext.capsuleWindows[windowScene] ?? [:]
|
||||||
|
if let w = windows[position], w.isHidden == false {
|
||||||
|
// 此时同一位置已有capsule在显示
|
||||||
|
if vm?.queuedPush == true {
|
||||||
|
// 加入队列
|
||||||
|
self.preferredWindowScene = windowScene
|
||||||
|
AppContext.capsuleInQueue.append(self)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// 直接覆盖
|
||||||
isNew = false
|
isNew = false
|
||||||
window = w
|
window = w
|
||||||
} else {
|
|
||||||
window = CapsuleWindow(capsule: self)
|
|
||||||
isNew = true
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 空闲状态下推送一个新的
|
||||||
|
isNew = true
|
||||||
|
window = CapsuleWindow(capsule: self)
|
||||||
|
windows[position] = nil
|
||||||
|
}
|
||||||
|
|
||||||
// frame
|
// frame
|
||||||
let cardEdgeInsetsByDefault = config.cardEdgeInsetsByDefault
|
let cardEdgeInsetsByDefault = config.cardEdgeInsetsByDefault
|
||||||
view.layoutIfNeeded()
|
view.layoutIfNeeded()
|
||||||
|
@ -53,22 +74,22 @@ extension CapsuleTarget {
|
||||||
view.layer.cornerRadiusWithContinuous = config.cardCornerRadiusByDefault
|
view.layer.cornerRadiusWithContinuous = config.cardCornerRadiusByDefault
|
||||||
|
|
||||||
window.rootViewController = self // 此时toast.view.frame.size会自动更新为window.frame.size
|
window.rootViewController = self // 此时toast.view.frame.size会自动更新为window.frame.size
|
||||||
if let s = AppContext.windowScene {
|
|
||||||
if AppContext.capsuleWindows[s] == nil {
|
AppContext.capsuleWindows[windowScene]?[position] = window
|
||||||
AppContext.capsuleWindows[s] = [:]
|
|
||||||
}
|
|
||||||
AppContext.capsuleWindows[s]?[position] = window
|
|
||||||
}
|
|
||||||
navEvents[.onViewWillAppear]?(self)
|
navEvents[.onViewWillAppear]?(self)
|
||||||
|
|
||||||
|
if position == .top {
|
||||||
// 更新toast防止重叠
|
// 更新toast防止重叠
|
||||||
ToastWindow.updateToastWindowsLayout()
|
ToastWindow.updateToastWindowsLayout()
|
||||||
|
}
|
||||||
|
|
||||||
if isNew {
|
|
||||||
window.isHidden = false
|
|
||||||
func completion() {
|
func completion() {
|
||||||
self.navEvents[.onViewDidAppear]?(self)
|
self.navEvents[.onViewDidAppear]?(self)
|
||||||
|
self.updateTimeoutDuration()
|
||||||
}
|
}
|
||||||
|
if isNew {
|
||||||
|
window.isHidden = false
|
||||||
if let animateBuildIn = config.animateBuildIn {
|
if let animateBuildIn = config.animateBuildIn {
|
||||||
animateBuildIn(window, completion)
|
animateBuildIn(window, completion)
|
||||||
} else {
|
} else {
|
||||||
|
@ -82,16 +103,14 @@ extension CapsuleTarget {
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
case .middle:
|
case .middle:
|
||||||
let d0 = duration * 0.2
|
window.transform = .init(translationX: 0, y: 24)
|
||||||
let d1 = duration
|
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5) {
|
||||||
window.transform = .init(scaleX: 0.001, y: 0.001)
|
|
||||||
window.alpha = 0
|
|
||||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5) {
|
|
||||||
window.transform = .identity
|
window.transform = .identity
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
UIView.animate(withDuration: duration * 0.4, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1) {
|
window.alpha = 0
|
||||||
|
UIView.animateLinear(duration: duration * 0.5) {
|
||||||
window.alpha = 1
|
window.alpha = 1
|
||||||
}
|
}
|
||||||
case .bottom:
|
case .bottom:
|
||||||
|
@ -103,8 +122,6 @@ extension CapsuleTarget {
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
view.layoutIfNeeded()
|
view.layoutIfNeeded()
|
||||||
|
@ -112,7 +129,7 @@ extension CapsuleTarget {
|
||||||
window.frame = newFrame
|
window.frame = newFrame
|
||||||
window.layoutIfNeeded()
|
window.layoutIfNeeded()
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
self.navEvents[.onViewDidAppear]?(self)
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,11 +137,13 @@ extension CapsuleTarget {
|
||||||
|
|
||||||
@objc open func pop() {
|
@objc open func pop() {
|
||||||
guard let window = attachedWindow, let windowScene = windowScene else { return }
|
guard let window = attachedWindow, let windowScene = windowScene else { return }
|
||||||
AppContext.capsuleWindows[windowScene]?[vm?.position ?? .top] = nil
|
let position = vm?.position ?? .top
|
||||||
|
AppContext.capsuleWindows[windowScene]?[position] = nil
|
||||||
navEvents[.onViewWillDisappear]?(self)
|
navEvents[.onViewWillDisappear]?(self)
|
||||||
|
if position == .top {
|
||||||
// 更新toast防止重叠
|
// 更新toast防止重叠
|
||||||
ToastWindow.updateToastWindowsLayout()
|
ToastWindow.updateToastWindowsLayout()
|
||||||
|
}
|
||||||
func completion() {
|
func completion() {
|
||||||
window.isHidden = true
|
window.isHidden = true
|
||||||
window.transform = .identity
|
window.transform = .identity
|
||||||
|
@ -135,31 +154,37 @@ extension CapsuleTarget {
|
||||||
} else {
|
} else {
|
||||||
let duration = config.animateDurationForBuildOutByDefault
|
let duration = config.animateDurationForBuildOutByDefault
|
||||||
let oldFrame = window.frame
|
let oldFrame = window.frame
|
||||||
switch vm?.position {
|
switch position {
|
||||||
case .top, .none:
|
case .top:
|
||||||
UIView.animateEaseOut(duration: duration) {
|
UIView.animateEaseIn(duration: duration) {
|
||||||
window.transform = .init(translationX: 0, y: -oldFrame.maxY - 20)
|
window.transform = .init(translationX: 0, y: -oldFrame.maxY - 20)
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
case .middle:
|
case .middle:
|
||||||
UIView.animate(withDuration: duration * 0.6, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5) {
|
let duration = config.animateDurationForBuildInByDefault * 1
|
||||||
window.transform = .init(scaleX: 0.001, y: 0.001)
|
UIView.animateEaseIn(duration: duration) {
|
||||||
|
window.transform = .init(translationX: 0, y: -24)
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
UIView.animate(withDuration: duration * 0.4, delay: duration * 0.2, usingSpringWithDamping: 1, initialSpringVelocity: 0.5) {
|
UIView.animateLinear(duration: duration * 0.5, delay: duration * 0.3) {
|
||||||
window.alpha = 0
|
window.alpha = 0
|
||||||
}
|
}
|
||||||
case .bottom:
|
case .bottom:
|
||||||
let offsetY = AppContext.appBounds.height - oldFrame.maxY + 100
|
let offsetY = AppContext.appBounds.height - oldFrame.maxY + 100
|
||||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0) {
|
UIView.animateEaseIn(duration: duration) {
|
||||||
window.transform = .init(translationX: 0, y: offsetY)
|
window.transform = .init(translationX: 0, y: offsetY)
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if let next = AppContext.capsuleInQueue.first(where: { $0.preferredWindowScene == windowScene && $0.vm?.position == position }) {
|
||||||
|
AppContext.capsuleInQueue.removeAll(where: { $0 == next })
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + config.animateDurationForBuildOutByDefault * 0.8) {
|
||||||
|
next.push()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,4 +199,15 @@ extension CapsuleTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateTimeoutDuration() {
|
||||||
|
// 为空时使用默认规则
|
||||||
|
if vm?.duration == nil {
|
||||||
|
vm?.duration = config.defaultDuration
|
||||||
|
}
|
||||||
|
// 设置持续时间
|
||||||
|
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||||
|
self?.pop()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,9 @@ open class CapsuleProvider: HUDProvider<CapsuleViewModel, CapsuleTarget> {
|
||||||
/// - Parameter identifier: 唯一标识符
|
/// - Parameter identifier: 唯一标识符
|
||||||
/// - Returns: HUD实例
|
/// - Returns: HUD实例
|
||||||
@discardableResult public static func find(identifier: String, update handler: ((_ capsule: CapsuleTarget) -> Void)? = nil) -> [CapsuleTarget] {
|
@discardableResult public static func find(identifier: String, update handler: ((_ capsule: CapsuleTarget) -> Void)? = nil) -> [CapsuleTarget] {
|
||||||
let arr = AppContext.capsuleWindows.values.flatMap({ $0.values }).compactMap({ $0.capsule }).filter({ $0.identifier == identifier })
|
let allPositions = AppContext.capsuleWindows.values.flatMap({ $0.values })
|
||||||
|
let allCapsules = allPositions.compactMap({ $0.capsule })
|
||||||
|
let arr = (allCapsules + AppContext.capsuleInQueue).filter({ $0.identifier == identifier })
|
||||||
if let handler = handler {
|
if let handler = handler {
|
||||||
arr.forEach({ $0.update(handler: handler) })
|
arr.forEach({ $0.update(handler: handler) })
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,24 +17,38 @@ import UIKit
|
||||||
|
|
||||||
@objc public var position: Position = .top
|
@objc public var position: Position = .top
|
||||||
|
|
||||||
public func position(position: Position) -> Self {
|
// Capsule 在一个位置最多只显示一个实例
|
||||||
|
// queuedPush: false 如果已有就直接覆盖
|
||||||
|
// queuedPush: true 如果已有就排队等待
|
||||||
|
@objc public var queuedPush: Bool = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension CapsuleViewModel {
|
||||||
|
|
||||||
|
func position(_ position: Position) -> Self {
|
||||||
self.position = position
|
self.position = position
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
public static var top: Self {
|
static var top: Self {
|
||||||
let obj = Self.init()
|
let obj = Self.init()
|
||||||
obj.position = .top
|
obj.position = .top
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
public static var middle: Self {
|
static var middle: Self {
|
||||||
let obj = Self.init()
|
let obj = Self.init()
|
||||||
obj.position = .middle
|
obj.position = .middle
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
public static var bottom: Self {
|
static var bottom: Self {
|
||||||
let obj = Self.init()
|
let obj = Self.init()
|
||||||
obj.position = .bottom
|
obj.position = .bottom
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func queuedPush(_ queuedPush: Bool) -> Self {
|
||||||
|
self.queuedPush = queuedPush
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ class CapsuleWindow: Window {
|
||||||
windowLevel = .phCapsuleBottom
|
windowLevel = .phCapsuleBottom
|
||||||
}
|
}
|
||||||
frame = .init(x: 0, y: 0, width: 128, height: 48)
|
frame = .init(x: 0, y: 0, width: 128, height: 48)
|
||||||
isHidden = false
|
isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
|
|
@ -9,6 +9,9 @@ import UIKit
|
||||||
|
|
||||||
open class BaseController: UIViewController {
|
open class BaseController: UIViewController {
|
||||||
|
|
||||||
|
/// 需要显示到那个UIWindowScene上
|
||||||
|
var preferredWindowScene: UIWindowScene?
|
||||||
|
|
||||||
/// ID标识
|
/// ID标识
|
||||||
public var identifier = String(Date().timeIntervalSince1970)
|
public var identifier = String(Date().timeIntervalSince1970)
|
||||||
|
|
||||||
|
@ -23,6 +26,7 @@ open class BaseController: UIViewController {
|
||||||
open var customView: UIView?
|
open var customView: UIView?
|
||||||
|
|
||||||
public internal(set) var isViewDisplayed = false
|
public internal(set) var isViewDisplayed = false
|
||||||
|
|
||||||
/// 按钮事件
|
/// 按钮事件
|
||||||
var buttonEvents = [UIView: () -> Void]()
|
var buttonEvents = [UIView: () -> Void]()
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,7 @@ open class HUDProvider<ViewModel: HUDViewModelType, Target: HUDTargetType>: NSOb
|
||||||
}
|
}
|
||||||
var t = Target()
|
var t = Target()
|
||||||
initializer(t)
|
initializer(t)
|
||||||
DispatchQueue.main.async {
|
|
||||||
t.push()
|
t.push()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ public struct AppContext {
|
||||||
static var alertWindow: [UIWindowScene: AlertWindow] = [:]
|
static var alertWindow: [UIWindowScene: AlertWindow] = [:]
|
||||||
static var sheetWindows: [UIWindowScene: [SheetWindow]] = [:]
|
static var sheetWindows: [UIWindowScene: [SheetWindow]] = [:]
|
||||||
static var capsuleWindows: [UIWindowScene: [CapsuleViewModel.Position: CapsuleWindow]] = [:]
|
static var capsuleWindows: [UIWindowScene: [CapsuleViewModel.Position: CapsuleWindow]] = [:]
|
||||||
|
static var capsuleInQueue: [CapsuleTarget] = []
|
||||||
|
|
||||||
static var current: AppContext? {
|
static var current: AppContext? {
|
||||||
guard let windowScene = windowScene else { return nil }
|
guard let windowScene = windowScene else { return nil }
|
||||||
|
@ -123,5 +124,8 @@ extension AppContext {
|
||||||
var capsuleWindows: [CapsuleViewModel.Position: CapsuleWindow] {
|
var capsuleWindows: [CapsuleViewModel.Position: CapsuleWindow] {
|
||||||
Self.capsuleWindows[windowScene] ?? [:]
|
Self.capsuleWindows[windowScene] ?? [:]
|
||||||
}
|
}
|
||||||
|
var alertWindow: AlertWindow? {
|
||||||
|
Self.alertWindow[windowScene]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,14 @@ import UIKit
|
||||||
|
|
||||||
extension UIView {
|
extension UIView {
|
||||||
|
|
||||||
|
static func animateLinear(duration: TimeInterval, delay: TimeInterval = 0, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) {
|
||||||
|
animate(withDuration: duration, delay: delay, options: [.allowUserInteraction], animations: animations, completion: completion)
|
||||||
|
}
|
||||||
|
static func animateEaseIn(duration: TimeInterval, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) {
|
||||||
|
animate(withDuration: duration, delay: 0, options: [.allowUserInteraction, .curveEaseIn], animations: animations, completion: completion)
|
||||||
|
}
|
||||||
static func animateEaseOut(duration: TimeInterval, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) {
|
static func animateEaseOut(duration: TimeInterval, animations: @escaping () -> Void, completion: ((_ done: Bool) -> Void)? = nil) {
|
||||||
animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.75, options: [.allowUserInteraction, .curveEaseOut], animations: animations, completion: completion)
|
animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseOut], animations: animations, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,11 +51,11 @@ public class SheetConfiguration: CommonConfiguration {
|
||||||
override var cardMaxHeightByDefault: CGFloat { cardMaxHeight ?? (AppContext.appBounds.height - 50) }
|
override var cardMaxHeightByDefault: CGFloat { cardMaxHeight ?? (AppContext.appBounds.height - 50) }
|
||||||
|
|
||||||
override var animateDurationForBuildInByDefault: CGFloat {
|
override var animateDurationForBuildInByDefault: CGFloat {
|
||||||
animateDurationForBuildIn ?? 0.5
|
animateDurationForBuildIn ?? 0.38
|
||||||
}
|
}
|
||||||
|
|
||||||
override var animateDurationForBuildOutByDefault: CGFloat {
|
override var animateDurationForBuildOutByDefault: CGFloat {
|
||||||
animateDurationForBuildOut ?? 0.5
|
animateDurationForBuildOut ?? 0.24
|
||||||
}
|
}
|
||||||
|
|
||||||
override var cardCornerRadiusByDefault: CGFloat { cardCornerRadius ?? 32 }
|
override var cardCornerRadiusByDefault: CGFloat { cardCornerRadius ?? 32 }
|
||||||
|
|
|
@ -44,18 +44,16 @@ extension SheetTarget: DefaultLayout {
|
||||||
// mask
|
// mask
|
||||||
loadContentMaskViewIfNeeded()
|
loadContentMaskViewIfNeeded()
|
||||||
// layout
|
// layout
|
||||||
|
let windowWidth = AppContext.appBounds.width
|
||||||
let maxWidth = config.cardMaxWidthByDefault
|
let maxWidth = config.cardMaxWidthByDefault
|
||||||
var width = AppContext.appBounds.width - config.windowEdgeInset * 2
|
let autoWidth = windowWidth - config.windowEdgeInset * 2
|
||||||
if width > maxWidth {
|
let width = min(autoWidth, maxWidth)
|
||||||
// landscape iPhone or iPad
|
|
||||||
width = maxWidth
|
|
||||||
}
|
|
||||||
contentView.snp.remakeConstraints { make in
|
contentView.snp.remakeConstraints { make in
|
||||||
if config.isFullScreen {
|
if config.isFullScreen {
|
||||||
make.edges.equalToSuperview()
|
make.edges.equalToSuperview()
|
||||||
} else {
|
} else {
|
||||||
make.centerX.equalToSuperview()
|
make.centerX.equalToSuperview()
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad && width >= maxWidth {
|
if UIDevice.current.userInterfaceIdiom == .pad && width < autoWidth - 40 {
|
||||||
// iPad且窗口宽度较宽时居中弹出
|
// iPad且窗口宽度较宽时居中弹出
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,6 +27,7 @@ extension SheetTarget {
|
||||||
setContextWindows(windows)
|
setContextWindows(windows)
|
||||||
}
|
}
|
||||||
if isNew {
|
if isNew {
|
||||||
|
_translateOut()
|
||||||
navEvents[.onViewWillAppear]?(self)
|
navEvents[.onViewWillAppear]?(self)
|
||||||
window.sheet.translateIn { [weak self] in
|
window.sheet.translateIn { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
@ -89,7 +90,7 @@ extension SheetTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
func translateOut(completion: (() -> Void)?) {
|
func translateOut(completion: (() -> Void)?) {
|
||||||
UIView.animateEaseOut(duration: config.animateDurationForBuildOutByDefault) {
|
UIView.animateLinear(duration: config.animateDurationForBuildOutByDefault) {
|
||||||
self._translateOut()
|
self._translateOut()
|
||||||
if self.config.stackDepthEffect {
|
if self.config.stackDepthEffect {
|
||||||
AppContext.appWindow?.transform = .identity
|
AppContext.appWindow?.transform = .identity
|
||||||
|
|
|
@ -20,7 +20,6 @@ open class SheetTarget: BaseController, HUDTargetType {
|
||||||
public lazy var backgroundView: UIView = {
|
public lazy var backgroundView: UIView = {
|
||||||
let v = UIView()
|
let v = UIView()
|
||||||
v.backgroundColor = .init(white: 0, alpha: 0.5)
|
v.backgroundColor = .init(white: 0, alpha: 0.5)
|
||||||
v.alpha = 0
|
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -64,8 +63,6 @@ open class SheetTarget: BaseController, HUDTargetType {
|
||||||
|
|
||||||
reloadData(animated: false)
|
reloadData(animated: false)
|
||||||
|
|
||||||
_translateOut()
|
|
||||||
|
|
||||||
navEvents[.onViewDidLoad]?(self)
|
navEvents[.onViewDidLoad]?(self)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,11 +42,11 @@ public class ToastConfiguration: CommonConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
override var animateDurationForBuildInByDefault: CGFloat {
|
override var animateDurationForBuildInByDefault: CGFloat {
|
||||||
animateDurationForBuildIn ?? 0.8
|
animateDurationForBuildIn ?? 0.64
|
||||||
}
|
}
|
||||||
|
|
||||||
override var animateDurationForBuildOutByDefault: CGFloat {
|
override var animateDurationForBuildOutByDefault: CGFloat {
|
||||||
animateDurationForBuildIn ?? 0.8
|
animateDurationForBuildIn ?? 0.32
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,17 +134,6 @@ extension ToastTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateTimeoutDuration() {
|
|
||||||
// 为空时使用默认规则
|
|
||||||
if vm?.duration == nil {
|
|
||||||
vm?.duration = config.defaultDuration
|
|
||||||
}
|
|
||||||
// 设置持续时间
|
|
||||||
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
|
||||||
self?.pop()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupImageView() {
|
func setupImageView() {
|
||||||
// 移除动画
|
// 移除动画
|
||||||
stopRotate(animateLayer)
|
stopRotate(animateLayer)
|
||||||
|
|
|
@ -66,16 +66,21 @@ extension ToastTarget {
|
||||||
setContextWindows(windows)
|
setContextWindows(windows)
|
||||||
}
|
}
|
||||||
ToastWindow.updateToastWindowsLayout(windows: windows)
|
ToastWindow.updateToastWindowsLayout(windows: windows)
|
||||||
|
|
||||||
|
func completion() {
|
||||||
|
self.navEvents[.onViewDidAppear]?(self)
|
||||||
|
self.updateTimeoutDuration()
|
||||||
|
}
|
||||||
if isNew {
|
if isNew {
|
||||||
window.transform = .init(translationX: 0, y: -window.frame.maxY)
|
window.transform = .init(translationX: 0, y: -window.frame.maxY)
|
||||||
UIView.animateEaseOut(duration: config.animateDurationForBuildInByDefault) {
|
UIView.animateEaseOut(duration: config.animateDurationForBuildInByDefault) {
|
||||||
window.transform = .identity
|
window.transform = .identity
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
self.navEvents[.onViewDidAppear]?(self)
|
completion()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
view.layoutIfNeeded()
|
view.layoutIfNeeded()
|
||||||
self.navEvents[.onViewDidAppear]?(self)
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +99,7 @@ extension ToastTarget {
|
||||||
}
|
}
|
||||||
vm?.duration = nil
|
vm?.duration = nil
|
||||||
setContextWindows(windows)
|
setContextWindows(windows)
|
||||||
UIView.animateEaseOut(duration: config.animateDurationForBuildOutByDefault) {
|
UIView.animateLinear(duration: config.animateDurationForBuildOutByDefault) {
|
||||||
window.transform = .init(translationX: 0, y: 0-20-window.maxY)
|
window.transform = .init(translationX: 0, y: 0-20-window.maxY)
|
||||||
} completion: { done in
|
} completion: { done in
|
||||||
self.view.removeFromSuperview()
|
self.view.removeFromSuperview()
|
||||||
|
@ -115,6 +120,17 @@ extension ToastTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateTimeoutDuration() {
|
||||||
|
// 为空时使用默认规则
|
||||||
|
if vm?.duration == nil {
|
||||||
|
vm?.duration = config.defaultDuration
|
||||||
|
}
|
||||||
|
// 设置持续时间
|
||||||
|
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||||
|
self?.pop()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - layout
|
// MARK: - layout
|
||||||
|
|
Loading…
Reference in New Issue