3
votes

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)?

1

1 Answers

1
votes

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))
    // ...
  }
}