Animators in SwiftUI
Really exciting that we're able to use Wave in SwiftUI. Couple of questions:
- Should animators be held via
@Stateto avoid instantiating new instances each time views are rendered? https://github.com/jtrivedi/Wave/blob/ecf541bf5debd75a20e1d85c97d41e5ef506c12e/Sample%20App/Wave-Sample/Wave-Sample/SwitUIViewController.swift#L14 - Seems like this works by updating
boxOffsetand triggering a state change in SwiftUI - I wonder if there's a more performant way to do this?TimelineViewperhaps? https://github.com/jtrivedi/Wave/blob/ecf541bf5debd75a20e1d85c97d41e5ef506c12e/Sample%20App/Wave-Sample/Wave-Sample/SwitUIViewController.swift#L16
Hey @matthewcheok, thanks for posting this! Glad you're excited about SwiftUI + Wave.
I'm definitely not a SwiftUI expert, so I have a few questions:
-
Did you see multiple
SpringAnimatorinstances being created in the SwiftUI sample app, when not marked with@State? I didn't see this behavior with or without@State. Perhaps I'm missing something? -
I'm not too familiar with
TimelineView. Can you help me understand what the benefit of using it would be? It's also worth noting that Wave's animation updates are already tied to the display's render cycle via a display link, so we shouldn't be re-rendering more than necessary.
Did you see multiple SpringAnimator instances being created in the SwiftUI sample app, when not marked with @State? I didn't see this behavior with or without @State. Perhaps I'm missing something?
I don't think its an issue in this specific instance but in general SwiftUI views are meant to be light-weight and could be instantiated many times. @State is used to persist values between render cycles. This could be observed if SwiftUIView was embedded in a parent view that has changing state, for example.
I'm not too familiar with TimelineView. Can you help me understand what the benefit of using it would be? It's also worth noting that Wave's animation updates are already tied to the display's render cycle via a display link, so we shouldn't be re-rendering more than necessary.
AFAIK TimelineView allows views to update with an explicit cadence (one of which is the animation schedule which perhaps works like CADisplayLink) vending a context (with the current date) which could be used to drive an animation. I think the nice thing about this is that the content that is driven by this view seems well encapsulated whereas the animator changing state could potentially trigger updates in an arbitrarily large view hierarchy.
Another potential alternative way to drive the animation is to make the animator an ObservableObject with the value being @Published - this avoids the consumer having to hold on to their own @State that mirrors the value property of the animator or having to configure the update handler. The view might then look something like this:
struct SwiftUIView: View {
@StateObject var animator = SpringAnimator(...)
var body: some View {
ZStack { ... }
.offset(x: animator.value.x, y: animator.value.y)
}
}
While neither are problems in your particular code example, I'd suggest the first change in terms of best practices.