使用 Swift Combine 创建一个 Timer Publisher
Posted
技术标签:
【中文标题】使用 Swift Combine 创建一个 Timer Publisher【英文标题】:Create a Timer Publisher using Swift Combine 【发布时间】:2019-12-03 14:40:10 【问题描述】:我一直在看Data Flow Through SwiftUI WWDC talk。他们有一张带有示例代码的幻灯片,其中他们使用连接到 SwiftUI 视图的 Timer 发布者,并随时间更新 UI。
我正在编写一些我想做完全相同的事情的代码,但无法弄清楚这个PodcastPlayer.currentTimePublisher
是如何实现的,然后连接到 UI 结构。我也看过所有关于 Combine 的视频。
我怎样才能做到这一点?
示例代码:
struct PlayerView : View
let episode: Episode
@State private var isPlaying: Bool = true
@State private var currentTime: TimeInterval = 0.0
var body: some View
VStack // ...
Text("\(playhead, formatter: currentTimeFormatter)")
.onReceive(PodcastPlayer.currentTimePublisher) newCurrentTime in
self.currentTime = newCurrentTime
【问题讨论】:
【参考方案1】:这里有一个组合计时器的示例。我使用的是全局变量,但您当然应该使用适用于您的场景的任何变量(环境对象、状态等)。
import SwiftUI
import Combine
class MyTimer
let currentTimePublisher = Timer.TimerPublisher(interval: 1.0, runLoop: .main, mode: .default)
let cancellable: AnyCancellable?
init()
self.cancellable = currentTimePublisher.connect() as? AnyCancellable
deinit
self.cancellable?.cancel()
let timer = MyTimer()
struct Clock : View
@State private var currentTime: Date = Date()
var body: some View
VStack
Text("\(currentTime)")
.onReceive(timer.currentTimePublisher) newCurrentTime in
self.currentTime = newCurrentTime
【讨论】:
太棒了,谢谢。所以我猜在示例中他们将currentTimePublisher
作为静态类变量。
看起来像。我没有详细看那个练习。我发布的答案只是使用组合创建计时器的一种方法。也许还有其他人......
是的。而且效果很好。将它作为他们和我的情况的静态变量是有意义的,因为它应该只存在一个计时器。
我认为 deinit 不需要,因为取消初始化时会自动调用 cancel()【参考方案2】:
使用ObservableObject
使用 Swift Combine 创建一个 Timer Publisher
class TimeCounter: ObservableObject
@Published var time = 0
lazy var timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) _ in self.time += 1
init() timer.fire()
就是这样!现在你只需要观察变化:
struct ContentView: View
@StateObject var timeCounter = TimeCounter()
var body: some View
Text("\(timeCounter.time)")
【讨论】:
这和Timer.TimerPublisher(interval: 1.0, runLoop: .main, mode: .default)
有什么区别?【参考方案3】:
我实现了一个带有新功能的组合计时器,允许您在不同的时间间隔之间切换。
class CombineTimer
private let intervalSubject: CurrentValueSubject<TimeInterval, Never>
var interval: TimeInterval
get
intervalSubject.value
set
intervalSubject.send(newValue)
var publisher: AnyPublisher<Date, Never>
intervalSubject
.map
Timer.TimerPublisher(interval: $0, runLoop: .main, mode: .default).autoconnect()
.switchToLatest()
.eraseToAnyPublisher()
init(interval: TimeInterval = 1.0)
intervalSubject = CurrentValueSubject<TimeInterval, Never>(interval)
要启动计时器,只需订阅publisher
属性。
SomeView()
.onReceive(combineTimer.publisher) date in
// ...
您可以通过更改interval
属性切换到具有不同间隔的新计时器。
combineTimer.interval = someNewInterval
【讨论】:
如何取消您的 CombineTimer?【参考方案4】:从 0 到 9 运行的计时器。
struct PlayerView : View
@State private var currentTime: TimeInterval = 0.0
@ObservedObject var player = PodcastPlayer()
var body: some View
Text("\(Int(currentTime))")
.font(.largeTitle)
.onReceive(player.$currentTimePublisher.filter $0 < 10.0 ) newCurrentTime in
self.currentTime = newCurrentTime
class PodcastPlayer: ObservableObject
@Published var currentTimePublisher: TimeInterval = 0.0
init()
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) _ in
self.currentTimePublisher += 1
【讨论】:
以上是关于使用 Swift Combine 创建一个 Timer Publisher的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Combine + Swift 复制 PromiseKit 风格的链式异步流
Swift Combine - @Published 属性数组
如何在 Swift 中使用 Combine 读取 JSON 错误对象的属性值?