订阅 SwiftUI View 联合收割机发布者的每次更新

Ben*_*ohn 2 swiftui combine

这是我想要采用的方法的简化示例,但我无法让这个简单的示例发挥作用。

\n

我有一个组合发布者,其主题是视图模型状态:

\n
struct State {\n    let a: Bool\n    let b: Bool\n    let transition: Transition?\n}\n
Run Code Online (Sandbox Code Playgroud)\n

国家包括transition财产。这描述了为了成为当前状态而做出的Transition事情。State

\n
enum Transition {\n    case onAChange, onBChange\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我想使用transition属性来驱动订阅发布者中的动画View,以便不同的过渡以特定的方式进行动画处理。

\n

查看代码

\n

这是视图的代码。您可以看到它如何尝试使用 选择transition要更新的动画。

\n
struct TestView: View {\n    \n    let model: TestViewModel\n\n    @State private var state: TestViewModel.State\n    private var cancel: AnyCancellable?\n\n    init(model: TestViewModel) {\n        self.model = model\n        self._state = State(initialValue: model.state.value)\n        self.cancel = model.state.sink(receiveValue: updateState(state:))\n    }\n    \n    var body: some View {\n        VStack(spacing: 20) {\n            Text("AAAAAAA").scaleEffect(state.a ? 2 : 1)\n            Text("BBBBBBB").scaleEffect(state.b ? 2 : 1)\n        }\n        .onTapGesture {\n            model.invert()\n        }\n    }\n    \n    private func updateState(state: TestViewModel.State) {\n        withAnimation(animation(for: state.transition)) {\n            self.state = state\n        }\n    }\n\n    private func animation(for transition: TestViewModel.Transition?) -> Animation? {\n        guard let transition = transition else { return nil }\n\n        switch transition {\n        case .onAChange: return .easeInOut(duration: 1)\n        case .onBChange: return .easeInOut(duration: 2)\n        }\n    }\n}\n\nstruct TestView_Previews: PreviewProvider {\n    static var previews: some View {\n        TestView(model: TestViewModel())\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

型号代码

\n
final class TestViewModel: ObservableObject {\n    \n    var state = CurrentValueSubject<State, Never>(State(a: false, b: false, transition: nil))\n    \n    struct State {\n        let a: Bool\n        let b: Bool\n        let transition: Transition?\n    }\n    \n    enum Transition {\n        case onAChange, onBChange\n    }\n\n    func invert() {\n        let oldState = state.value\n        setState(newState: .init(a: !oldState.a, b: oldState.b, transition: .onAChange))\n        setState(newState: .init(a: !oldState.a, b: !oldState.b, transition: .onBChange))\n    }\n    \n    private func setState(newState: State) {\n        state.value = newState\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在模型代码中可以看到,当invert()调用时,会发生两次状态变化。模型首先a使用.onAChange过渡进行切换,然后b使用.onBChange过渡进行切换。

\n

应该发生什么

\n

运行时应该发生的情况是,每次单击视图时,文本“AAAAAAA”和“BBBBBBB”都应该切换大小。但是,“AAAAAAA”文本应快速变化(1 秒),而“BBBBBBB”文本应缓慢变化(2 秒)。

\n

实际发生了什么

\n

但是,当我运行此命令并单击视图时,视图根本不更新。

\n

我可以从模型上onTapGesture { \xe2\x80\xa6 }调用和正在调用的调试器中看到。invert()updateState(state:)正被称为。但是,TestView屏幕上并没有发生变化,也body不会再次调用。

\n

我尝试过的其他事情

\n

使用回调

\n

我没有使用发布者将事件发送到视图,而是尝试在模型中将回调函数设置为视图的updateState(state:)函数。init我在视图中分配给了这个model.handleUpdate = self.update(state:)。同样,这不起作用。正如预期的那样,函数invert()update(state:)被调用,但视图实际上并没有改变。

\n

使用@ObservedObject

\n

我将模型更改ObservableObject为其state存在@Published@ObservedOject我设置了模型的视图。这样,视图确实会更新,但它使用相同的动画更新两段文本,这是我不想要的。似乎两个状态更新被压缩,它只看到最后一个,并使用其中的transition

\n

确实有效的东西 \xe2\x80\x93 之类的

\n

最后,我尝试直接将模型的invert()函数复制到视图的onTapGesture处理程序中,以便视图直接更新自己的状态。这确实有效!这很重要,但我不想将所有模型更新逻辑都放在我的视图中。

\n

问题

\n

如何让 SwiftUI 视图订阅模型通过其发布者发送的所有transition状态,以便它可以使用状态中的属性来控制用于该状态更改的动画?

\n

New*_*Dev 7

向发布者订阅视图的方式是使用.onRecieve(_:perform:),因此不要在 中保存可取消的内容init,而是执行以下操作:

var body: some View {
   VStack(spacing: 20) {
      Text("AAAAAAA").scaleEffect(state.a ? 2 : 1)
      Text("BBBBBBB").scaleEffect(state.b ? 2 : 1)
   }
   .onTapGesture {
      model.invert()
   }
   .onReceive(model.state, perform: updateState(state:)) // <- here
}
Run Code Online (Sandbox Code Playgroud)