关于属性更改SwiftUI的动画视图

时间:2019-07-29 20:08:18

标签: swift swiftui

我有一个看法

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())
    }
}

我希望它在属性color更改时具有填充动画。我想将填充动画从10设置为0。

我尝试使用onAppear

    ...onAppear {
      self.padding = 0
    }

但是它仅在视图出现时(按预期)工作一次,并且我每次属性color更改时都希望这样做。基本上,每次color属性更改时,我都希望为从100的填充设置动画。你能告诉我是否有办法吗?

4 个答案:

答案 0 :(得分:1)

您在另一个答案中注意到,您无法从body内部更新状态。您也无法像使用didSet那样在@Binding上使用@State(至少从Beta 4开始)。

我能想到的最好的解决方案是使用BindableObjectsink/onReceive,以便在每次padding更改时更新color。我还需要添加一个延迟,以便填充动画结束。

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
                    }
                }
    }
}


此示例未在Beta 4的Xcode画布上进行动画处理。

答案 1 :(得分:1)

从Xcode 12开始,Swift 5

达到预期结果的一种方法是将当前选定的索引移到ObservableObject中。

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

然后,您的CellView可以使用.onReceive(_:)修饰符对索引的这种变化做出反应;使用Publisher前缀访问@Published属性包装器提供的$

然后可以使用此修饰符提供的闭包来更新填充并为更改添加动画。

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
                }
            }
    }
}

这是一个用于演示的示例父视图:

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

请注意,此处Parent不需要将其viewModel属性设置为@ObservedObject

答案 2 :(得分:0)

您可以使用计算属性来使此工作。下面的代码是一个示例示例。

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())
    }
}

答案 3 :(得分:0)

只要color属性发生单个增量变化,就会在10到0之间切换填充

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