Reputation: 4320
I'm trying to automatically display a sequence of images in a SwiftUI view. I can create a fixed timer with a predetermined "every" value and trigger the view update in an .onReceive closure but I have not been able to construct a timer with a property for the interval that can be adjusted by the user with a slider. The code below works for the fixed timer. The subscription fires appropriately but I cannot seem to find an equivalent to .onReceive for a subscription to update the image. I also tried creating my own TimerPublisher but I had the same issue - I could not initialize it without specifying a fixed value.
My view:
struct ImageStream2: View {
@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(fetchRequest: DermPhoto.getAllDermPhotos()) var dermPhotos: FetchedResults<DermPhoto>
@State private var activeImageIndex = 0
@State private var animateSpeed: Double = 1.0
@State private var startTimer = false
let timer = Timer.publish(every: 1.0, on: .main, in: .default).autoconnect()
@State private var mst: MyTimerSubscription!
var body: some View {
GeometryReader { geo in
VStack {
Text("Time Sequence")
.foregroundColor(.blue)
.font(.system(size: 25))
Image(uiImage: UIImage(data: self.dermPhotos[self.activeImageIndex].myImage!)!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width - 20, height: geo.size.width - 20, alignment: .center)
.cornerRadius(20)
.shadow(radius: 10, x: 10, y: 10)
.onReceive(self.timer) { t in
print("in fixed timer")
if self.startTimer {
self.activeImageIndex = (self.activeImageIndex + 1) % self.dermPhotos.count
}
}
Group {
HStack {
Text("0.5").foregroundColor(.blue)
Slider(value: self.$animateSpeed, in: 0.5...5.0, step: 0.5)
Text("5.0").foregroundColor(.blue)
}
.padding()
Text("Replay at: " + "\(self.animateSpeed) " + (self.animateSpeed == 1.0 ? "second" : "seconds") + " per frame")
.foregroundColor(.blue)
HStack {
Button(action: {
self.startTimer.toggle()
if self.startTimer {
self.mst = MyTimerSubscription(interval: 5.0)
} else {
self.mst.subscription.cancel()
}
}) {
ZStack {
RoundedRectangle(cornerRadius: 20)
.fill(Color.yellow)
.frame(width: 200, height: 40)
Text(self.startTimer ? "Stop" : "Start").font(.headline)
}
}
}
.padding()
}//group
}//top VStack
}.onDisappear{ self.startTimer = false }
}
}
And the subscription file:
class MyTimerSubscription {
let subscription: AnyCancellable
let interval: Double
init(interval: Double) {
subscription =
Timer.publish(every:interval, on:.main, in:.default)
.autoconnect()
.sink { _ in
print("in MyTimerSubscription print")
}
self.interval = interval
}
deinit {
subscription.cancel()
}
}
Any guidance would be appreciated. Xcode 11.3 (11C29)
Upvotes: 0
Views: 1312
Reputation: 4320
Create an Observable Timer:
class TimerWrapper : ObservableObject {
let willChange = PassthroughSubject<TimerWrapper, Never>()
@Published var timer : Timer!
func start(withTimeInterval interval: Double) {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
guard let self = self else { return }
self.willChange.send(self)
}
}
}
Then with the view, add:
@State var timer = Timer.publish(every: 1.0, on: .main, in: .default).autoconnect()
@State private var animateSpeed: Double = 1.0
Then with the Image:
.onReceive(self.timer) { t in
if self.startTimer {
if self.timerToggle {
self.activeImageIndex = (self.activeImageIndex + 1) % self.dermPhotos.count
}
self.goTimerAnimation.toggle() //changes the shape of the frame
self.timerToggle.toggle()
self.pct.toggle() //changes the size of the frame
}
}//onReceive
And in the button action:
self.timer = Timer.publish(every: self.animateSpeed, on: .main, in: .default).autoconnect()
Upvotes: 1