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
|
||||
section.add(title: "顶部,默认滑入") {
|
||||
Capsule(.info("一条简短的消息"))
|
||||
list.add(title: "不同位置、不同动画,队列推送") { section in
|
||||
section.add(title: "顶部,默认动画") {
|
||||
Capsule(.info("一条简短的消息").queuedPush(true).duration(1))
|
||||
}
|
||||
section.add(title: "中间,默认缩放") {
|
||||
Capsule(.middle.info("一条简短的消息"))
|
||||
section.add(title: "中间,默认动画") {
|
||||
Capsule(.middle.queuedPush(true).info("一条简短的消息").duration(2))
|
||||
}
|
||||
section.add(title: "中间,黑底白字,透明渐变") {
|
||||
Capsule(.middle.info("一条简短的消息")) { capsule in
|
||||
Capsule(.middle.queuedPush(true).info("一条简短的消息").duration(1)) { capsule in
|
||||
capsule.config.tintColor = .white
|
||||
capsule.config.cardCornerRadius = 8
|
||||
capsule.config.contentViewMask { mask in
|
||||
|
@ -119,7 +119,7 @@ class DemoCapsuleVC: ListVC {
|
|||
}
|
||||
}
|
||||
section.add(title: "底部,渐变背景,默认回弹滑入") {
|
||||
Capsule(.bottom.enter("点击进入")) { capsule in
|
||||
Capsule(.bottom.queuedPush(true).enter("点击进入").duration(1)) { capsule in
|
||||
capsule.config.tintColor = .white
|
||||
capsule.config.cardEdgeInsets = .init(top: 12, left: 20, bottom: 12, right: 20)
|
||||
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)
|
||||
}
|
||||
|
||||
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: "图标 + 一段长文本") {
|
||||
Toast(.note.message(message))
|
||||
Toast(.note.message(message).duration(1))
|
||||
}
|
||||
section.add(title: "网络图标 + 一段文本") {
|
||||
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: "禁止手势移除") {
|
||||
let title = "这条消息很重要"
|
||||
let message = "向上滑动将不会移除消息,您必须手动处理,用于重要但非阻塞性的事件。(通过代码处理或者在点击事件处理)"
|
||||
Toast(.warning.title(title).message(message)) { toast in
|
||||
Toast(.warning.title(title).message(message).duration(.infinity)) { toast in
|
||||
toast.isRemovable = false
|
||||
toast.onTapped { toast in
|
||||
toast.pop()
|
||||
|
|
|
@ -30,8 +30,8 @@ public class AlertConfiguration: CommonConfiguration {
|
|||
customBackgroundViewMask = callback
|
||||
}
|
||||
|
||||
override var animateDurationForBuildInByDefault: CGFloat {
|
||||
animateDurationForBuildIn ?? 0.6
|
||||
override var animateDurationForBuildOutByDefault: CGFloat {
|
||||
animateDurationForBuildOut ?? 0.2
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -124,13 +124,6 @@ extension AlertTarget: DefaultLayout {
|
|||
}
|
||||
}
|
||||
|
||||
func updateTimeoutDuration() {
|
||||
// 设置持续时间
|
||||
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||
self?.pop()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AlertTarget {
|
||||
|
|
|
@ -16,7 +16,7 @@ extension AlertTarget {
|
|||
return
|
||||
}
|
||||
setDefaultAxis()
|
||||
view.transform = .init(scaleX: 1.2, y: 1.2)
|
||||
view.transform = .init(scaleX: 1.12, y: 1.12)
|
||||
view.alpha = 0
|
||||
navEvents[.onViewWillAppear]?(self)
|
||||
window.vc.addChild(self)
|
||||
|
@ -33,6 +33,7 @@ extension AlertTarget {
|
|||
window.backgroundView.alpha = 1
|
||||
} completion: { done in
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
self.updateTimeoutDuration()
|
||||
}
|
||||
window.alerts.append(self)
|
||||
AlertTarget.updateAlertsLayout(alerts: window.alerts)
|
||||
|
@ -42,9 +43,9 @@ extension AlertTarget {
|
|||
navEvents[.onViewWillDisappear]?(self)
|
||||
AlertTarget.removeAlert(alert: self)
|
||||
let duration = config.animateDurationForBuildOut ?? config.animateDurationForBuildOutByDefault
|
||||
UIView.animateEaseOut(duration: duration) {
|
||||
UIView.animateLinear(duration: duration) {
|
||||
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
|
||||
self.view.removeFromSuperview()
|
||||
self.removeFromParent()
|
||||
|
@ -55,7 +56,7 @@ extension AlertTarget {
|
|||
let count = window.alerts.count
|
||||
if count == 0 {
|
||||
AppContext.alertWindow[windowScene] = nil
|
||||
UIView.animateEaseOut(duration: duration) {
|
||||
UIView.animateLinear(duration: duration) {
|
||||
window.backgroundView.alpha = 0
|
||||
} completion: { done in
|
||||
// 这里设置一下window属性,会使window的生命周期被延长到此处,即动画执行过程中window不会被提前释放
|
||||
|
@ -74,6 +75,13 @@ extension AlertTarget {
|
|||
}
|
||||
}
|
||||
|
||||
func updateTimeoutDuration() {
|
||||
// 设置持续时间
|
||||
vm?.timeoutHandler = DispatchWorkItem(block: { [weak self] in
|
||||
self?.pop()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - layout
|
||||
|
|
|
@ -36,6 +36,6 @@ class AlertWindow: Window {
|
|||
|
||||
extension AlertTarget {
|
||||
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 animateDurationForBuildInByDefault: CGFloat {
|
||||
animateDurationForBuildIn ?? 0.8
|
||||
animateDurationForBuildIn ?? 0.64
|
||||
}
|
||||
|
||||
override var animateDurationForBuildOutByDefault: CGFloat {
|
||||
animateDurationForBuildOut ?? 0.8
|
||||
animateDurationForBuildOut ?? 0.32
|
||||
}
|
||||
|
||||
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() {
|
||||
// 移除动画
|
||||
stopRotate(animateLayer)
|
||||
|
|
|
@ -11,17 +11,38 @@ extension CapsuleTarget {
|
|||
|
||||
@objc open func push() {
|
||||
guard CapsuleConfiguration.isEnabled else { return }
|
||||
guard let windowScene = preferredWindowScene ?? AppContext.windowScene else { return }
|
||||
if windowScene != AppContext.windowScene {
|
||||
AppContext.windowScene = windowScene
|
||||
}
|
||||
|
||||
let isNew: Bool
|
||||
let window: CapsuleWindow
|
||||
let position = vm?.position ?? .top
|
||||
|
||||
if let w = AppContext.current?.capsuleWindows[position] {
|
||||
isNew = false
|
||||
window = w
|
||||
} else {
|
||||
window = CapsuleWindow(capsule: self)
|
||||
isNew = true
|
||||
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
|
||||
window = w
|
||||
}
|
||||
} else {
|
||||
// 空闲状态下推送一个新的
|
||||
isNew = true
|
||||
window = CapsuleWindow(capsule: self)
|
||||
windows[position] = nil
|
||||
}
|
||||
|
||||
// frame
|
||||
let cardEdgeInsetsByDefault = config.cardEdgeInsetsByDefault
|
||||
view.layoutIfNeeded()
|
||||
|
@ -53,22 +74,22 @@ extension CapsuleTarget {
|
|||
view.layer.cornerRadiusWithContinuous = config.cardCornerRadiusByDefault
|
||||
|
||||
window.rootViewController = self // 此时toast.view.frame.size会自动更新为window.frame.size
|
||||
if let s = AppContext.windowScene {
|
||||
if AppContext.capsuleWindows[s] == nil {
|
||||
AppContext.capsuleWindows[s] = [:]
|
||||
}
|
||||
AppContext.capsuleWindows[s]?[position] = window
|
||||
}
|
||||
|
||||
AppContext.capsuleWindows[windowScene]?[position] = window
|
||||
|
||||
navEvents[.onViewWillAppear]?(self)
|
||||
|
||||
// 更新toast防止重叠
|
||||
ToastWindow.updateToastWindowsLayout()
|
||||
if position == .top {
|
||||
// 更新toast防止重叠
|
||||
ToastWindow.updateToastWindowsLayout()
|
||||
}
|
||||
|
||||
func completion() {
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
self.updateTimeoutDuration()
|
||||
}
|
||||
if isNew {
|
||||
window.isHidden = false
|
||||
func completion() {
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
}
|
||||
if let animateBuildIn = config.animateBuildIn {
|
||||
animateBuildIn(window, completion)
|
||||
} else {
|
||||
|
@ -82,16 +103,14 @@ extension CapsuleTarget {
|
|||
completion()
|
||||
}
|
||||
case .middle:
|
||||
let d0 = duration * 0.2
|
||||
let d1 = duration
|
||||
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 = .init(translationX: 0, y: 24)
|
||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5) {
|
||||
window.transform = .identity
|
||||
} completion: { done in
|
||||
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
|
||||
}
|
||||
case .bottom:
|
||||
|
@ -103,8 +122,6 @@ extension CapsuleTarget {
|
|||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} else {
|
||||
view.layoutIfNeeded()
|
||||
|
@ -112,7 +129,7 @@ extension CapsuleTarget {
|
|||
window.frame = newFrame
|
||||
window.layoutIfNeeded()
|
||||
} completion: { done in
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,11 +137,13 @@ extension CapsuleTarget {
|
|||
|
||||
@objc open func pop() {
|
||||
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)
|
||||
// 更新toast防止重叠
|
||||
ToastWindow.updateToastWindowsLayout()
|
||||
|
||||
if position == .top {
|
||||
// 更新toast防止重叠
|
||||
ToastWindow.updateToastWindowsLayout()
|
||||
}
|
||||
func completion() {
|
||||
window.isHidden = true
|
||||
window.transform = .identity
|
||||
|
@ -135,31 +154,37 @@ extension CapsuleTarget {
|
|||
} else {
|
||||
let duration = config.animateDurationForBuildOutByDefault
|
||||
let oldFrame = window.frame
|
||||
switch vm?.position {
|
||||
case .top, .none:
|
||||
UIView.animateEaseOut(duration: duration) {
|
||||
switch position {
|
||||
case .top:
|
||||
UIView.animateEaseIn(duration: duration) {
|
||||
window.transform = .init(translationX: 0, y: -oldFrame.maxY - 20)
|
||||
} completion: { done in
|
||||
completion()
|
||||
}
|
||||
case .middle:
|
||||
UIView.animate(withDuration: duration * 0.6, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5) {
|
||||
window.transform = .init(scaleX: 0.001, y: 0.001)
|
||||
let duration = config.animateDurationForBuildInByDefault * 1
|
||||
UIView.animateEaseIn(duration: duration) {
|
||||
window.transform = .init(translationX: 0, y: -24)
|
||||
} completion: { done in
|
||||
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
|
||||
}
|
||||
case .bottom:
|
||||
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)
|
||||
} completion: { done in
|
||||
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: 唯一标识符
|
||||
/// - Returns: HUD实例
|
||||
@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 {
|
||||
arr.forEach({ $0.update(handler: handler) })
|
||||
}
|
||||
|
|
|
@ -17,24 +17,38 @@ import UIKit
|
|||
|
||||
@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
|
||||
return self
|
||||
}
|
||||
public static var top: Self {
|
||||
static var top: Self {
|
||||
let obj = Self.init()
|
||||
obj.position = .top
|
||||
return obj
|
||||
}
|
||||
public static var middle: Self {
|
||||
static var middle: Self {
|
||||
let obj = Self.init()
|
||||
obj.position = .middle
|
||||
return obj
|
||||
}
|
||||
public static var bottom: Self {
|
||||
static var bottom: Self {
|
||||
let obj = Self.init()
|
||||
obj.position = .bottom
|
||||
return obj
|
||||
}
|
||||
|
||||
func queuedPush(_ queuedPush: Bool) -> Self {
|
||||
self.queuedPush = queuedPush
|
||||
return self
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class CapsuleWindow: Window {
|
|||
windowLevel = .phCapsuleBottom
|
||||
}
|
||||
frame = .init(x: 0, y: 0, width: 128, height: 48)
|
||||
isHidden = false
|
||||
isHidden = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
|
@ -9,6 +9,9 @@ import UIKit
|
|||
|
||||
open class BaseController: UIViewController {
|
||||
|
||||
/// 需要显示到那个UIWindowScene上
|
||||
var preferredWindowScene: UIWindowScene?
|
||||
|
||||
/// ID标识
|
||||
public var identifier = String(Date().timeIntervalSince1970)
|
||||
|
||||
|
@ -23,6 +26,7 @@ open class BaseController: UIViewController {
|
|||
open var customView: UIView?
|
||||
|
||||
public internal(set) var isViewDisplayed = false
|
||||
|
||||
/// 按钮事件
|
||||
var buttonEvents = [UIView: () -> Void]()
|
||||
|
||||
|
|
|
@ -32,9 +32,7 @@ open class HUDProvider<ViewModel: HUDViewModelType, Target: HUDTargetType>: NSOb
|
|||
}
|
||||
var t = Target()
|
||||
initializer(t)
|
||||
DispatchQueue.main.async {
|
||||
t.push()
|
||||
}
|
||||
t.push()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ public struct AppContext {
|
|||
static var alertWindow: [UIWindowScene: AlertWindow] = [:]
|
||||
static var sheetWindows: [UIWindowScene: [SheetWindow]] = [:]
|
||||
static var capsuleWindows: [UIWindowScene: [CapsuleViewModel.Position: CapsuleWindow]] = [:]
|
||||
static var capsuleInQueue: [CapsuleTarget] = []
|
||||
|
||||
static var current: AppContext? {
|
||||
guard let windowScene = windowScene else { return nil }
|
||||
|
@ -123,5 +124,8 @@ extension AppContext {
|
|||
var capsuleWindows: [CapsuleViewModel.Position: CapsuleWindow] {
|
||||
Self.capsuleWindows[windowScene] ?? [:]
|
||||
}
|
||||
var alertWindow: AlertWindow? {
|
||||
Self.alertWindow[windowScene]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +9,14 @@ import UIKit
|
|||
|
||||
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) {
|
||||
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 animateDurationForBuildInByDefault: CGFloat {
|
||||
animateDurationForBuildIn ?? 0.5
|
||||
animateDurationForBuildIn ?? 0.38
|
||||
}
|
||||
|
||||
override var animateDurationForBuildOutByDefault: CGFloat {
|
||||
animateDurationForBuildOut ?? 0.5
|
||||
animateDurationForBuildOut ?? 0.24
|
||||
}
|
||||
|
||||
override var cardCornerRadiusByDefault: CGFloat { cardCornerRadius ?? 32 }
|
||||
|
|
|
@ -44,18 +44,16 @@ extension SheetTarget: DefaultLayout {
|
|||
// mask
|
||||
loadContentMaskViewIfNeeded()
|
||||
// layout
|
||||
let windowWidth = AppContext.appBounds.width
|
||||
let maxWidth = config.cardMaxWidthByDefault
|
||||
var width = AppContext.appBounds.width - config.windowEdgeInset * 2
|
||||
if width > maxWidth {
|
||||
// landscape iPhone or iPad
|
||||
width = maxWidth
|
||||
}
|
||||
let autoWidth = windowWidth - config.windowEdgeInset * 2
|
||||
let width = min(autoWidth, maxWidth)
|
||||
contentView.snp.remakeConstraints { make in
|
||||
if config.isFullScreen {
|
||||
make.edges.equalToSuperview()
|
||||
} else {
|
||||
make.centerX.equalToSuperview()
|
||||
if UIDevice.current.userInterfaceIdiom == .pad && width >= maxWidth {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad && width < autoWidth - 40 {
|
||||
// iPad且窗口宽度较宽时居中弹出
|
||||
make.centerY.equalToSuperview()
|
||||
} else {
|
||||
|
|
|
@ -27,6 +27,7 @@ extension SheetTarget {
|
|||
setContextWindows(windows)
|
||||
}
|
||||
if isNew {
|
||||
_translateOut()
|
||||
navEvents[.onViewWillAppear]?(self)
|
||||
window.sheet.translateIn { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
@ -89,7 +90,7 @@ extension SheetTarget {
|
|||
}
|
||||
|
||||
func translateOut(completion: (() -> Void)?) {
|
||||
UIView.animateEaseOut(duration: config.animateDurationForBuildOutByDefault) {
|
||||
UIView.animateLinear(duration: config.animateDurationForBuildOutByDefault) {
|
||||
self._translateOut()
|
||||
if self.config.stackDepthEffect {
|
||||
AppContext.appWindow?.transform = .identity
|
||||
|
|
|
@ -20,7 +20,6 @@ open class SheetTarget: BaseController, HUDTargetType {
|
|||
public lazy var backgroundView: UIView = {
|
||||
let v = UIView()
|
||||
v.backgroundColor = .init(white: 0, alpha: 0.5)
|
||||
v.alpha = 0
|
||||
return v
|
||||
}()
|
||||
|
||||
|
@ -64,8 +63,6 @@ open class SheetTarget: BaseController, HUDTargetType {
|
|||
|
||||
reloadData(animated: false)
|
||||
|
||||
_translateOut()
|
||||
|
||||
navEvents[.onViewDidLoad]?(self)
|
||||
|
||||
}
|
||||
|
|
|
@ -42,11 +42,11 @@ public class ToastConfiguration: CommonConfiguration {
|
|||
}
|
||||
|
||||
override var animateDurationForBuildInByDefault: CGFloat {
|
||||
animateDurationForBuildIn ?? 0.8
|
||||
animateDurationForBuildIn ?? 0.64
|
||||
}
|
||||
|
||||
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() {
|
||||
// 移除动画
|
||||
stopRotate(animateLayer)
|
||||
|
|
|
@ -66,16 +66,21 @@ extension ToastTarget {
|
|||
setContextWindows(windows)
|
||||
}
|
||||
ToastWindow.updateToastWindowsLayout(windows: windows)
|
||||
|
||||
func completion() {
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
self.updateTimeoutDuration()
|
||||
}
|
||||
if isNew {
|
||||
window.transform = .init(translationX: 0, y: -window.frame.maxY)
|
||||
UIView.animateEaseOut(duration: config.animateDurationForBuildInByDefault) {
|
||||
window.transform = .identity
|
||||
} completion: { done in
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
completion()
|
||||
}
|
||||
} else {
|
||||
view.layoutIfNeeded()
|
||||
self.navEvents[.onViewDidAppear]?(self)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +99,7 @@ extension ToastTarget {
|
|||
}
|
||||
vm?.duration = nil
|
||||
setContextWindows(windows)
|
||||
UIView.animateEaseOut(duration: config.animateDurationForBuildOutByDefault) {
|
||||
UIView.animateLinear(duration: config.animateDurationForBuildOutByDefault) {
|
||||
window.transform = .init(translationX: 0, y: 0-20-window.maxY)
|
||||
} completion: { done in
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue