2

I have a view

struct CellView: View { @Binding var color: Int @State var padding : Length = 10 let colors = [Color.yellow, Color.red, Color.blue, Color.green] var body: some View { colors[color] .cornerRadius(20) .padding(padding) .animation(.spring()) } } 

And I want it to have padding animation when property color changes. I want to animate padding from 10 to 0.

I've tried to use onAppear

 ...onAppear { self.padding = 0 } 

But it work only once when view appears(as intended), and I want to do this each time when property color changes. Basically, each time color property changes, I want to animate padding from 10 to 0. Could you please tell if there is a way to do this?

4 Answers 4

2

As you noticed in the other answer, you cannot update state from within body. You also cannot use didSet on a @Binding (at least as of Beta 4) the way you can with @State.

The best solution I could come up with was to use a BindableObject and sink/onReceive in order to update padding on each color change. I also needed to add a delay in order for the padding animation to finish.

class IndexBinding: BindableObject { let willChange = PassthroughSubject<Void, Never>() var index: Int = 0 { didSet { self.willChange.send() } } } struct ParentView: View { @State var index = IndexBinding() var body: some View { CellView(index: self.index) .gesture(TapGesture().onEnded { _ in self.index.index += 1 }) } } struct CellView: View { @ObjectBinding var index: IndexBinding @State private var padding: CGFloat = 0.0 var body: some View { Color.red .cornerRadius(20.0) .padding(self.padding + 20.0) .animation(.spring()) .onReceive(self.index.willChange) { self.padding = 10.0 DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { self.padding = 0.0 } } } } 

This example doesn't animate in the Xcode canvas on Beta 4. Run it on the simulator or a device.

Sign up to request clarification or add additional context in comments.

1 Comment

Binding attribute color is changed in parent view. I understand how to animate property which is based on color value, but I want to update padding one, which is not dependent on color property.
2

As of Xcode 12, Swift 5

One way to achieve the desired outcome could be to move the currently selected index into an ObservableObject.

final class CellViewModel: ObservableObject { @Published var index: Int init(index: Int = 0) { self.index = index } } 

Your CellView can then react to this change in index using the .onReceive(_:) modifier; accessing the Publisher provided by the @Published property wrapper using the $ prefix.

You can then use the closure provided by this modifier to update the padding and animate the change.

struct CellView: View { @ObservedObject var viewModel: CellViewModel @State private var padding : CGFloat = 10 let colors: [Color] = [.yellow, .red, .blue, .green] var body: some View { colors[viewModel.index] .cornerRadius(20) .padding(padding) .onReceive(viewModel.$index) { _ in padding = 10 withAnimation(.spring()) { padding = 0 } } } } 

And here's an example parent view for demonstration:

struct ParentView: View { let viewModel: CellViewModel var body: some View { VStack { CellView(viewModel: viewModel) .frame(width: 200, height: 200) HStack { ForEach(0..<4) { i in Button(action: { viewModel.index = i }) { Text("\(i)") .padding() .frame(maxWidth: .infinity) .background(Color(.secondarySystemFill)) } } } } } } 

Note that the Parent does not need its viewModel property to be @ObservedObject here.

Comments

0

You could use Computed Properties to get this working. The code below is an example how it could be done.

import SwiftUI struct ColorChanges: View { @State var color: Float = 0 var body: some View { VStack { Slider(value: $color, from: 0, through: 3, by: 1) CellView(color: Int(color)) } } } struct CellView: View { var color: Int @State var colorOld: Int = 0 var padding: CGFloat { if color != colorOld { colorOld = color return 40 } else { return 0 } } let colors = [Color.yellow, Color.red, Color.blue, Color.green] var body: some View { colors[color] .cornerRadius(20) .padding(padding) .animation(.spring()) } } 

2 Comments

Xcode has notification message Modifying state during view update, this will cause undefined behavior.. When I am trying to change variable inside computed property
I am sorry for the wrong answer. In the Preview it was working but I did not try it on a device :-(. I will check if I can find another solution. Maybe to extract the State in an external object.
0

Whenever there is a single incremental change in the color property this will toggle the padding between 10 and 0

padding = color % 2 == 0 ? 10 : 0 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.