Reputation: 1726
I am building an animating progress button that shows download progress and then expands to show button text once the download is complete (a bit like the App Store buttons).
I have a custom button view that takes a @Binding to an external progress CGFloat value. I know that I can set the view's progress value via init but this doesn't seem to get me closer. Progress is of course mutated outside of the custom button view. I'd like to be able to have the button automatically handle progress value changes and expand automatically when progress hits 1.0.
I'm using explicit animation in order to animate the button shape (by enclosing a couple of private @State property changes inside withAnimation). The problem is that I need to be able to call the code that sets those @State properties when the @Binding progress value changes. I can't see any way to do this without having the explicit withAnimation calls happen outside the button view. I don't want to use implicit animation because this causes problems when the button's origin changes. The demos I've found all just set the states from within the button, via .onTap.
Ideally, there'd be a way to call a function on @Binding value change. I've seen something about using a publisher but this seems like overkill? If it's the only option, then I'm wondering if I can somehow do that privately inside the button view and still just pass $progress to the button (for maximum reusability)?
Upvotes: 7
Views: 932
Reputation: 1292
Here's how you do it. Create a func that returns the binding object (with set animation):
func textBinding(text: Binding<String>) -> Binding<String> {
return Binding<String>(
get: { text.wrappedValue },
set: { newValue in
withAnimation(.easeOut) {
text.wrappedValue = newValue
}
}
)
}
In the calling view, pass the state variable into the binding object func:
struct CallingView: View {
@State var text = ""
var body: some View {
HStack {
FooView(text: textBinding(text: $text))
}
}
}
struct FooView: View {
@Binding var text: String
...
}
I've not had any luck creating the binding with var
so func was my only bet. Good luck!
Upvotes: 0
Reputation: 575
Not sure if this helps but in case you didn't know you can create your own Binding and customize the setter and getter, and for example put withAnimation and whatever logic you want there:
@State private var fooValue: String // Use fooBinding instead of $fooValue
private var fooBinding: Binding<String> {
Binding<String>(
get: { return self.fooValue },
set: { updatedValue in
withAnimation {
self.fooValue = updatedValue
}
}
)
}
In this example things that change as a result of this binding should get animated.
Upvotes: 0
Reputation: 257729
There is API for this purpose
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension Binding { ... /// Create a new Binding that will apply `animation` to any changes. public func animation(_ animation: Animation? = .default) -> Binding<Value> }
so it is possible to do something like
struct ContentView: View {
@State private var value = 0
var body: some View {
// ...
ChildView(value: $value.animation(.linear))
// ...
}
}
Upvotes: 1