|
|
@@ -1,16 +1,69 @@
|
|
|
//
|
|
|
-// CountDownManager.swift
|
|
|
+// QSLCountdownManager.swift
|
|
|
// QuickSearchLocation
|
|
|
//
|
|
|
-// Created by Destiny on 2025/9/24.
|
|
|
+// Created by Trae AI on 2024-06-30.
|
|
|
//
|
|
|
|
|
|
-import UIKit
|
|
|
+import Foundation
|
|
|
|
|
|
+/// 倒计时类型
|
|
|
+enum QSLCountdownType {
|
|
|
+ case homeProduct // 首页商品倒计时
|
|
|
+ case trial // 试用倒计时
|
|
|
+}
|
|
|
|
|
|
+/// 倒计时管理器代理协议
|
|
|
+protocol QSLCountdownManagerDelegate: AnyObject {
|
|
|
+ /// 倒计时更新
|
|
|
+ /// - Parameters:
|
|
|
+ /// - manager: 倒计时管理器
|
|
|
+ /// - type: 倒计时类型
|
|
|
+ /// - remainingSeconds: 剩余秒数
|
|
|
+ func countdownManager(_ manager: QSLCountdownManager, didUpdateCountdown type: QSLCountdownType, remainingSeconds: Int)
|
|
|
+
|
|
|
+ /// 倒计时结束
|
|
|
+ /// - Parameters:
|
|
|
+ /// - manager: 倒计时管理器
|
|
|
+ /// - type: 倒计时类型
|
|
|
+ func countdownManager(_ manager: QSLCountdownManager, didFinishCountdown type: QSLCountdownType)
|
|
|
+}
|
|
|
+
|
|
|
+// 使代理方法可选实现
|
|
|
+extension QSLCountdownManagerDelegate {
|
|
|
+ func countdownManager(_ manager: QSLCountdownManager, didUpdateCountdown type: QSLCountdownType, remainingSeconds: Int) {}
|
|
|
+ func countdownManager(_ manager: QSLCountdownManager, didFinishCountdown type: QSLCountdownType) {}
|
|
|
+}
|
|
|
+
|
|
|
+/// 倒计时管理器
|
|
|
class QSLCountdownManager {
|
|
|
+
|
|
|
+ // MARK: - 单例
|
|
|
static let shared = QSLCountdownManager()
|
|
|
+ private init() {}
|
|
|
+
|
|
|
+ // MARK: - 属性
|
|
|
private var timer: Timer?
|
|
|
+ private var isHomeProductCountdownActive = false
|
|
|
+ private var isTrialCountdownActive = false
|
|
|
+
|
|
|
+ // 倒计时剩余时间(秒)
|
|
|
+ private var homeProductRemainingSeconds: Int = 0
|
|
|
+ private var trialRemainingSeconds: Int = 0
|
|
|
+
|
|
|
+ // 代理集合
|
|
|
+ private var delegates = NSHashTable<AnyObject>.weakObjects()
|
|
|
+
|
|
|
+ // 倒计时回调(保留原有回调机制作为备选)
|
|
|
+ typealias CountdownCallback = (Int) -> Void
|
|
|
+ private var homeProductCallback: CountdownCallback?
|
|
|
+ private var trialCallback: CountdownCallback?
|
|
|
+
|
|
|
+ // 倒计时结束回调(保留原有回调机制作为备选)
|
|
|
+ typealias CountdownFinishCallback = () -> Void
|
|
|
+ private var homeProductFinishCallback: CountdownFinishCallback?
|
|
|
+ private var trialFinishCallback: CountdownFinishCallback?
|
|
|
+
|
|
|
var selectGood: QSLGoodModel?{
|
|
|
didSet{
|
|
|
if(oldValue != nil){
|
|
|
@@ -35,78 +88,236 @@ class QSLCountdownManager {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- var remainingSeconds: Int = 900 * 1000 // 15分钟 = 900秒 (正常套餐活动倒计时)
|
|
|
- var trialSeconds: Int = -999 // 15分钟 = 900秒 (试用活动倒计时)
|
|
|
- var updateHandler: ((String) -> Void)?
|
|
|
- var updateHandler1: ((String) -> Void)?
|
|
|
- var updateTrialHandler: ((String) -> Void)?
|
|
|
-
|
|
|
- var finishHandler: (() -> Void)?
|
|
|
+ // MARK: - 代理管理
|
|
|
|
|
|
- func startCountdown() {
|
|
|
- timer?.invalidate()
|
|
|
- timer = nil
|
|
|
-
|
|
|
- remainingSeconds = 900 * 1000
|
|
|
-
|
|
|
- timer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true)
|
|
|
- RunLoop.current.add(timer!, forMode: .common)
|
|
|
+ /// 添加代理
|
|
|
+ /// - Parameter delegate: 代理对象
|
|
|
+ func addDelegate(_ delegate: QSLCountdownManagerDelegate) {
|
|
|
+ delegates.add(delegate)
|
|
|
}
|
|
|
|
|
|
- func checkIsCountDown() -> Bool{
|
|
|
- if let curTimer = timer {
|
|
|
- return true
|
|
|
- }else{
|
|
|
- return false
|
|
|
- }
|
|
|
+ /// 移除代理
|
|
|
+ /// - Parameter delegate: 代理对象
|
|
|
+ func removeDelegate(_ delegate: QSLCountdownManagerDelegate) {
|
|
|
+ delegates.remove(delegate)
|
|
|
}
|
|
|
|
|
|
- func stopCountdown() {
|
|
|
- finishHandler?()
|
|
|
- timer?.invalidate()
|
|
|
- timer = nil
|
|
|
+ /// 移除所有代理
|
|
|
+ func removeAllDelegates() {
|
|
|
+ delegates.removeAllObjects()
|
|
|
}
|
|
|
|
|
|
- @objc private func updateCountdown() {
|
|
|
+ // MARK: - 公共方法
|
|
|
+
|
|
|
+ /// 开始倒计时
|
|
|
+ /// - Parameters:
|
|
|
+ /// - type: 倒计时类型
|
|
|
+ /// - seconds: 倒计时秒数
|
|
|
+ /// - callback: 倒计时回调,返回剩余秒数
|
|
|
+ /// - finishCallback: 倒计时结束回调
|
|
|
+ /// - Returns: 是否成功开启倒计时
|
|
|
+ @discardableResult
|
|
|
+ func startCountdown(type: QSLCountdownType, seconds: Int, callback: @escaping CountdownCallback, finishCallback: CountdownFinishCallback? = nil) -> Bool {
|
|
|
+ // 检查该类型倒计时是否已结束,如果已结束则不能再开启
|
|
|
|
|
|
+ switch type {
|
|
|
+ case .homeProduct:
|
|
|
+ if isHomeProductCountdownActive {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ homeProductRemainingSeconds = seconds
|
|
|
+ homeProductCallback = callback
|
|
|
+ homeProductFinishCallback = finishCallback
|
|
|
+ isHomeProductCountdownActive = true
|
|
|
+ case .trial:
|
|
|
+ if isTrialCountdownActive {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ trialRemainingSeconds = seconds
|
|
|
+ trialCallback = callback
|
|
|
+ trialFinishCallback = finishCallback
|
|
|
+ isTrialCountdownActive = true
|
|
|
+ }
|
|
|
|
|
|
- remainingSeconds -= 1
|
|
|
- trialSeconds -= 1
|
|
|
+ // 如果timer不存在,创建timer
|
|
|
+ if timer == nil {
|
|
|
+ timer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true)
|
|
|
+ RunLoop.current.add(timer!, forMode: .common)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 立即执行一次回调,更新UI
|
|
|
+ executeCallback(for: type)
|
|
|
+
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 暂停倒计时
|
|
|
+ /// - Parameter type: 倒计时类型
|
|
|
+ func pauseCountdown(type: QSLCountdownType) {
|
|
|
+ switch type {
|
|
|
+ case .homeProduct:
|
|
|
+ isHomeProductCountdownActive = false
|
|
|
+ case .trial:
|
|
|
+ isTrialCountdownActive = false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果两种倒计时都不活跃,则销毁timer
|
|
|
+ if !isHomeProductCountdownActive && !isTrialCountdownActive {
|
|
|
+ invalidateTimer()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 恢复倒计时
|
|
|
+ /// - Parameter type: 倒计时类型
|
|
|
+ /// - Returns: 是否成功恢复倒计时
|
|
|
+ @discardableResult
|
|
|
+ func resumeCountdown(type: QSLCountdownType) -> Bool {
|
|
|
+ switch type {
|
|
|
+ case .homeProduct:
|
|
|
+ if homeProductRemainingSeconds <= 0 {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ isHomeProductCountdownActive = true
|
|
|
+ case .trial:
|
|
|
+ if trialRemainingSeconds <= 0 {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ isTrialCountdownActive = true
|
|
|
+ }
|
|
|
|
|
|
+ // 如果timer不存在,创建timer
|
|
|
+ if timer == nil {
|
|
|
+ timer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true)
|
|
|
+ RunLoop.current.add(timer!, forMode: .common)
|
|
|
+ }
|
|
|
|
|
|
- if remainingSeconds <= 0 && trialSeconds <= 0{
|
|
|
- stopCountdown()
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 停止倒计时
|
|
|
+ /// - Parameter type: 倒计时类型
|
|
|
+ func stopCountdown(type: QSLCountdownType) {
|
|
|
+ switch type {
|
|
|
+ case .homeProduct:
|
|
|
+ isHomeProductCountdownActive = false
|
|
|
+ homeProductRemainingSeconds = 0
|
|
|
+ homeProductCallback = nil
|
|
|
+ homeProductFinishCallback = nil
|
|
|
+ case .trial:
|
|
|
+ isTrialCountdownActive = false
|
|
|
+ trialRemainingSeconds = 0
|
|
|
+ trialCallback = nil
|
|
|
+ trialFinishCallback = nil
|
|
|
}
|
|
|
|
|
|
- if remainingSeconds > 0 {
|
|
|
- let totalSeconds = remainingSeconds / 1000
|
|
|
- let minutes = totalSeconds / 60
|
|
|
- let seconds = totalSeconds % 60
|
|
|
- let milliseconds = remainingSeconds % 1000
|
|
|
+ // 如果两种倒计时都不活跃,则销毁timer
|
|
|
+ if !isHomeProductCountdownActive && !isTrialCountdownActive {
|
|
|
+ invalidateTimer()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 获取剩余时间
|
|
|
+ /// - Parameter type: 倒计时类型
|
|
|
+ /// - Returns: 剩余秒数
|
|
|
+ func getRemainingSeconds(for type: QSLCountdownType) -> Int {
|
|
|
+ switch type {
|
|
|
+ case .homeProduct:
|
|
|
+ return homeProductRemainingSeconds
|
|
|
+ case .trial:
|
|
|
+ return trialRemainingSeconds
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 是否正在倒计时
|
|
|
+ /// - Parameter type: 倒计时类型
|
|
|
+ /// - Returns: 是否正在倒计时
|
|
|
+ func isCountdownActive(for type: QSLCountdownType) -> Bool {
|
|
|
+ switch type {
|
|
|
+ case .homeProduct:
|
|
|
+ return isHomeProductCountdownActive
|
|
|
+ case .trial:
|
|
|
+ return isTrialCountdownActive
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: - 私有方法
|
|
|
+
|
|
|
+ /// 更新倒计时
|
|
|
+ @objc private func updateCountdown() {
|
|
|
+ // 更新首页商品倒计时
|
|
|
+ if isHomeProductCountdownActive {
|
|
|
+ if homeProductRemainingSeconds > 0 {
|
|
|
+ homeProductRemainingSeconds -= 1
|
|
|
+ executeCallback(for: .homeProduct)
|
|
|
+ notifyDelegatesCountdownUpdated(type: .homeProduct, remainingSeconds: homeProductRemainingSeconds)
|
|
|
+ }
|
|
|
|
|
|
- // 格式:MM:SS:SSS(例如 "14:59:500")
|
|
|
- let timeString = String(format: "%02d : %02d : %03d", minutes, seconds, milliseconds)
|
|
|
- updateHandler?(timeString)
|
|
|
- updateHandler1?(timeString)
|
|
|
- }else{
|
|
|
- remainingSeconds = 0
|
|
|
+ // 检查是否结束
|
|
|
+ if homeProductRemainingSeconds <= 0 {
|
|
|
+ isHomeProductCountdownActive = false
|
|
|
+ homeProductFinishCallback?()
|
|
|
+ homeProductCallback = nil
|
|
|
+ homeProductFinishCallback = nil
|
|
|
+ notifyDelegatesCountdownFinished(type: .homeProduct)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- if trialSeconds > 0 {
|
|
|
- let totalSeconds = trialSeconds / 1000
|
|
|
- let minutes = totalSeconds / 60
|
|
|
- let seconds = totalSeconds % 60
|
|
|
- let milliseconds = trialSeconds % 1000
|
|
|
+ // 更新试用倒计时
|
|
|
+ if isTrialCountdownActive {
|
|
|
+ if trialRemainingSeconds > 0 {
|
|
|
+ trialRemainingSeconds -= 1
|
|
|
+ executeCallback(for: .trial)
|
|
|
+ notifyDelegatesCountdownUpdated(type: .trial, remainingSeconds: trialRemainingSeconds)
|
|
|
+ }
|
|
|
|
|
|
- // 格式:MM:SS:SSS(例如 "14:59:500")
|
|
|
- let timeString = String(format: "%02d : %02d : %03d", minutes, seconds, milliseconds)
|
|
|
- updateTrialHandler?(timeString)
|
|
|
- }else{
|
|
|
- trialSeconds = 0
|
|
|
+ // 检查是否结束
|
|
|
+ if trialRemainingSeconds <= 0 {
|
|
|
+ isTrialCountdownActive = false
|
|
|
+ trialFinishCallback?()
|
|
|
+ trialCallback = nil
|
|
|
+ trialFinishCallback = nil
|
|
|
+ notifyDelegatesCountdownFinished(type: .trial)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果两种倒计时都不活跃,则销毁timer
|
|
|
+ if !isHomeProductCountdownActive && !isTrialCountdownActive {
|
|
|
+ invalidateTimer()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 执行倒计时回调
|
|
|
+ /// - Parameter type: 倒计时类型
|
|
|
+ private func executeCallback(for type: QSLCountdownType) {
|
|
|
+ switch type {
|
|
|
+ case .homeProduct:
|
|
|
+ homeProductCallback?(homeProductRemainingSeconds)
|
|
|
+ case .trial:
|
|
|
+ trialCallback?(trialRemainingSeconds)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- deinit {
|
|
|
- stopCountdown()
|
|
|
+ /// 通知代理倒计时更新
|
|
|
+ /// - Parameters:
|
|
|
+ /// - type: 倒计时类型
|
|
|
+ /// - remainingSeconds: 剩余秒数
|
|
|
+ private func notifyDelegatesCountdownUpdated(type: QSLCountdownType, remainingSeconds: Int) {
|
|
|
+ for case let delegate as QSLCountdownManagerDelegate in delegates.allObjects {
|
|
|
+ delegate.countdownManager(self, didUpdateCountdown: type, remainingSeconds: remainingSeconds)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 通知代理倒计时结束
|
|
|
+ /// - Parameter type: 倒计时类型
|
|
|
+ private func notifyDelegatesCountdownFinished(type: QSLCountdownType) {
|
|
|
+ for case let delegate as QSLCountdownManagerDelegate in delegates.allObjects {
|
|
|
+ delegate.countdownManager(self, didFinishCountdown: type)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 销毁定时器
|
|
|
+ private func invalidateTimer() {
|
|
|
+ timer?.invalidate()
|
|
|
+ timer = nil
|
|
|
}
|
|
|
}
|