使用 Swift Combine 创建一个定时器发布器

Create a Timer Publisher using Swift Combine

我一直在看 Data Flow Through SwiftUI WWDC talk。他们有一张带有示例代码的幻灯片,其中他们使用连接到 SwiftUI 视图的计时器发布器,并使用时间更新 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
    }
  }
}

这里有一个 Combine 定时器的例子。我使用的是全局的,但当然你应该使用适用于你的场景的任何东西(environmentObject、State 等)。

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

我实现了一个 Combine 计时器,它有一个新功能,允许您在不同的时间间隔之间切换。

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: [=10=], 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

使用ObservableObject

使用 Swift Combine

创建计时器发布器
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)")
    }
}

一个从 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 { [=10=] < 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
        }
    }
}

来自“kontiki”的优秀示例的更通用代码中的 2 美分

    struct ContentView : View {
    @State private var currentTime: TimeInterval = 0.0
    @ObservedObject var counter = Counter(Δt: 1)
    var body: some View {
        VStack{
        Text("\(Int(currentTime))")
            .font(.largeTitle)
            .onReceive(counter.$currentTimePublisher) { newCurrentTime in
                self.currentTime = newCurrentTime
            }
        }
        Spacer().frame(height: 30)
        Button (action: { counter.reset() } ){
            Text("reset")
        }
        Spacer().frame(height: 30)
        Button (action: { counter.kill() } ){
            Text("KILL")
        }
        Spacer().frame(height: 30)
        Button (action: { counter.restart(Δt: 0.1) } ){
            Text("Restart")
        }

    }
}


class Counter: ObservableObject {

    @Published var currentTimePublisher: TimeInterval = 0.0
    
    private var timer: Timer?
    
    init(Δt: Double) {
        self.timer = Timer.scheduledTimer(withTimeInterval: Δt, repeats: true) { _ in
            self.currentTimePublisher += 1
        }
    }
    
    func restart(Δt: Double){
        kill()
        self.timer = Timer.scheduledTimer(withTimeInterval: Δt, repeats: true) { _ in
            self.currentTimePublisher += 1
        }
    }
    
    func kill(){
        self.timer?.invalidate()
        self.timer = nil
    }
    
    func reset(){
        currentTimePublisher = 0
    }
}