Luis
Luis

Reputation: 1009

How to Animate Binding of Child View

A bit confused as to why this code does not work as expected.

Some generic animatable view:

struct SomeAnimatableView: View, Animatable {
    var percent: Double

    var body: some View {
        GeometryReader { geo in
            ZStack {
                Rectangle()
                    .fill(Color.blue)
                    .frame(height: 120)
                Rectangle()
                    .fill(Color.red)
                    .frame(width: geo.size.width * CGFloat(self.percent), height: 116)
            }
            .frame(width: geo.size.width, height: geo.size.height)
        }
    }

    var animatableData: Double {
        set { percent = newValue }
        get { percent }
    }
}

A view which wraps said animatable view:

struct ProgressRectangle: View {
    @Binding var percent: Double

    var body: some View {
        SomeAnimatableView(percent: percent)
    }
}

Finally another view, consider it parent:

struct ContentView: View {
    @State var percent: Double

    var body: some View {
        VStack {
            ProgressRectangle(percent: $percent.animation())
            Text("Percent: \(percent * 100)")
        }
        .onTapGesture {
            self.percent = 1
        }
    }
}

Issue ⚠️ why doesn't the progress animate? $percent.animation() isn't that supposed cause animations? Lots of questions...

A fix 💡:

    .onTapGesture {
        withAnimation {
            self.percent = 1
        }
    }

However, this doesn't actually fix the issue or answer the question to why that binding doesn't animate. Because this also seems to animate the entire view, notice the button animating its width in this example:

An animated image of a progress rectangle growing, starting at the center and expanding outwards towards the right and left. At the bottom, a label with the words: Percent. Followed by the current percent in the animation. The animation loops back to the start when reaching the edges.

Thoughts, ideas?

Upvotes: 1

Views: 136

Answers (1)

Asperi
Asperi

Reputation: 257711

Update: additional clarification

Animation to Binding is a mechanism to transfer animation inside child so when bound variable is changed internally it would be changed with animation.

Thus for considered scenario ProgressRectangle would look like

struct ProgressRectangle: View {
    @Binding var percent: Double

    var body: some View {
        SomeAnimatableView(percent: percent)
            .onTapGesture {
                withAnimation(_percent.transaction.animation) { // << here !!
                    self.percent = 0 == self.percent ? 0.8 : 0
                }
            }
    }
}

without onTapGesture in ContentView (similarly as would you have Toggle which changes value internally).

Test module on GitHub

Original (still valid and works)

Here is working solution. Tested with Xcode 11.4 / iOS 13.4

demo

@State var percent: Double = .zero
var body: some View {
    VStack {
        ProgressRectangle(percent: $percent)
            .animation(.default)            // << place here !!
            .onTapGesture {
                    self.percent = self.percent == .zero ? 1 : .zero
            }
        Text("Percent: \(percent * 100)")
    }
}

Upvotes: 2

Related Questions