TL;DR:
I want to trigger an action when (parent) state changes. This seems difficult in a declarative context.
Remarks
The challenge here is that I am not trying to make a property of one view dependent on another. That's well-covered territory. I could (and have) read all day about sharing state changes. But this is an event.
The best I have come across is by arsenius. His approach does work; I am wondering if there is a more Reactive way to do it. Maybe a one-shot Publisher
? Seems sketchy.
Code
"Event" is not always a dirty word in FRP. I can start a View
's animation by handling an event in the same View
:
import SwiftUI
struct MyReusableSubview : View {
@State private var offs = CGFloat.zero // Animate this.
var body: some View {
Rectangle().foregroundColor(.green).offset(y: self.offs)
// A local event triggers the action...
.onTapGesture { self.simplifiedAnimation() }
// ...but we want to animate when parent view says so.
}
private func simplifiedAnimation() {
self.offs = 200
withAnimation { self.offs = 0 }
}
}
But I want this View
to be composable and reusable. It seems reasonable to want to plug this into a larger hierarchy, which will have its own idea of when to run the animation. All my "solutions" either change state during View
update, or won't even compile.
struct ContentView: View {
var body: some View {
VStack {
Button(action: {
// Want this to trigger subview's animation.
}) {
Text("Tap me")
}
MyReusableSubview()
}.background(Color.gray)
}
}
Surely SwiftUI is not going to force me not to decompose my hierarchy?
Solution
Here is arsenius' suggestion. Is there a more Swifty-UI way?
struct MyReusableSubview : View {
@Binding var doIt : Bool // Bound to parent
// ... as before...
var body: some View {
Group {
if self.doIt {
ZStack { EmptyView() }
.onAppear { self.simplifiedAnimation() }
// And call DispatchQueue to clear the doIt flag.
}
Rectangle()
.foregroundColor(.green)
.offset(y: self.offs)
}
}
}