[英]Animate view on property change SwiftUI
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.我希望它在属性
color
更改时具有填充动画。 I want to animate padding from 10 to 0.我想动画填充从 10 到 0。
I've tried to use onAppear
我试过使用
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.但是当视图出现时它只工作一次(按预期),我想每次当属性
color
改变时都这样做。 Basically, each time color
property changes, I want to animate padding from 10
to 0
.基本上,每次
color
属性更改时,我都希望将 padding 从10
为0
。 Could you please tell if there is a way to do this?你能告诉我是否有办法做到这一点?
As you noticed in the other answer, you cannot update state from within body
.正如您在另一个答案中注意到的那样,您无法从
body
内部更新状态。 You also cannot use didSet
on a @Binding
(at least as of Beta 4) the way you can with @State
.你也不能使用
didSet
在@Binding
(至少在测试版4)与方式,您可以@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.我能想出的最佳解决方案是使用
BindableObject
和sink/onReceive
以便在每次color
更改时更新padding
。 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.此示例不会在 Beta 4 上的 Xcode 画布中设置动画。在模拟器或设备上运行它。
As of Xcode 12, Swift 5从 Xcode 12 开始,Swift 5
One way to achieve the desired outcome could be to move the currently selected index into an ObservableObject
.实现预期结果的一种方法是将当前选定的索引移动到
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;然后,您的
CellView
可以使用.onReceive(_:)
修饰符对索引的这种变化做出反应; accessing the Publisher
provided by the @Published
property wrapper using the $
prefix.使用
$
前缀访问由@Published
属性包装器提供的Publisher
者。
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.请注意,此处的 Parent 不需要其 viewModel 属性为
@ObservedObject
。
You could use Computed Properties to get this working.您可以使用 Computed Properties 使其正常工作。 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())
}
}
每当颜色属性发生单个增量更改时,这将在 10 和 0 之间切换填充
padding = color % 2 == 0 ? 10 : 0
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.