I have the following computed property (currentTime
) in my Observable
class which I want to observe in SwiftUI view. Problem is changes in the property are not published as it does not rely on stored properties. I have been using Combine
publishers previously to solve the issue but I wonder if there is a better way to solve this in the new observation framework introduced in iOS 17?
@available(iOS 17.0, *)
@Observable
class VideoPlayerVM {
private(set) public var player: AVPlayer = AVPlayer()
var currentTime:CMTime {
let time = self.player?.currentItem?.currentTime()
if time.isValid {
return time
}
return .invalid
}
}
Whatever Combine publisher you were using before should also work with @Observable
. You just need to sink
that publisher and assign its values to a stored property in the @Observable
class.
That said, I'm not sure why you'd be using a Combine publisher in the first place, if you want to observe the current time of an AVPlayer
. There is a dedicated method, addPeriodicTimeObserver
, that allows you to observe the current time at regular intervals.
You can use it like this:
@Observable
@MainActor
class VideoPlayerVM {
private(set) public var player: AVPlayer = AVPlayer()
var currentTime: CMTime = .zero
private var observation: Any?
func startObservingCurrentTime() {
guard observation == nil else { return }
observation = player.addPeriodicTimeObserver(
forInterval: .init(
seconds: 0.5,
preferredTimescale: CMTimeScale(NSEC_PER_SEC)
),
queue: nil
) { time in
MainActor.assumeIsolated {
self.currentTime = time
}
}
}
func stopObservingCurrentTime() {
if let observation {
player.removeTimeObserver(observation)
observation = nil
}
}
}
You should call stopObservingCurrentTime
in onDisappear
of the view that owns the VideoPlayerVM
.
Note that I used MainActor.assumeIsolated
in the closure for addPeriodicTimeObserver
. This is safe because queue: nil
means that the closure will be called on the main dispatch queue, which is the serial executor of the main actor.