使用 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
}
}
我一直在看 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
}
}