订阅合并发布服务器的每个更新的SwiftUI视图

订阅合并发布服务器的每个更新的SwiftUI视图,swiftui,combine,Swiftui,Combine,下面是一个我想采用的方法的简化示例,但我无法让这个简单的示例起作用 我有一个联合发布者,其主题是视图模型状态: struct State { let a: Bool let b: Bool let transition: Transition? } 状态包括一个转换属性。这描述了状态为了成为当前状态而进行的转换 enum Transition { case onAChange, onBChange } 我想使用transition属性在订阅给发布者的视图中驱动

下面是一个我想采用的方法的简化示例,但我无法让这个简单的示例起作用

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

struct State {
    let a: Bool
    let b: Bool
    let transition: Transition?
}
状态包括一个
转换
属性。这描述了
状态
为了成为当前状态而进行的
转换

enum Transition {
    case onAChange, onBChange
}
我想使用
transition
属性在订阅给发布者的
视图中驱动动画,以便不同的转场以特定的方式进行动画制作

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

struct TestView: View {
    
    let model: TestViewModel

    @State private var state: TestViewModel.State
    private var cancel: AnyCancellable?

    init(model: TestViewModel) {
        self.model = model
        self._state = State(initialValue: model.state.value)
        self.cancel = model.state.sink(receiveValue: updateState(state:))
    }
    
    var body: some View {
        VStack(spacing: 20) {
            Text("AAAAAAA").scaleEffect(state.a ? 2 : 1)
            Text("BBBBBBB").scaleEffect(state.b ? 2 : 1)
        }
        .onTapGesture {
            model.invert()
        }
    }
    
    private func updateState(state: TestViewModel.State) {
        withAnimation(animation(for: state.transition)) {
            self.state = state
        }
    }

    private func animation(for transition: TestViewModel.Transition?) -> Animation? {
        guard let transition = transition else { return nil }

        switch transition {
        case .onAChange: return .easeInOut(duration: 1)
        case .onBChange: return .easeInOut(duration: 2)
        }
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView(model: TestViewModel())
    }
}
模型代码
final类TestViewModel:ObserveObject{
var状态=CurrentValueSubject(状态(a:false,b:false,转换:nil))
结构状态{
让a:布尔
b:布尔
让过渡:过渡?
}
枚举转换{
改变的情况
}
func invert(){
让oldState=state.value
setState(newState:.init(a:!oldState.a,b:oldState.b,转换:.onChange))
setState(newState:.init(a:!oldState.a,b:!oldState.b,转换:.onBChange))
}
私有函数集状态(新闻状态:状态){
state.value=newState
}
}
您可以在模型代码中看到,当调用
invert()
时,会发生两种状态更改。模型首先使用
.onchange
转换切换
a
,然后使用
.onBChange
转换切换
b

该怎么办 运行此操作时,每次单击视图时,文本“AAAAAAA”和“BBBBBBB”应切换大小。但是,“AAAAAAA”文本应快速更改(1秒),而“BBBBBBB”文本应缓慢更改(2秒)

到底发生了什么 但是,当我运行此命令并单击视图时,视图根本不会更新

我可以从调试器中看到,在模型上调用了
ontapposition{…}
,并且调用了
invert()
。还将调用
updateState(状态:)
。但是,
TestView
不会在屏幕上更改,并且不会再次调用
body

我试过的其他东西 使用回调 我没有使用发布者将事件发送到视图,而是在模型集中尝试了一个回调函数,该函数设置为视图的
updateState(state:)
函数。我用
model.handleUpdate=self.update(状态:)
在视图的
init
中对此进行了分配。同样,这没有起作用。函数
invert()
update(state:)
按预期被调用,但视图实际上没有改变

使用
@ObservedObject
我将模型更改为
可观察对象
,其
状态
@已发布
。我将视图设置为具有模型的
@observedObject
。这样,视图确实会更新,但它会使用相同的动画更新两段文本,这是我不想要的。这两个状态更新似乎被压缩了,它只看到最后一个状态更新,并从中使用
转换

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

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

订阅发布服务器视图的方式是使用
.onReceive(uuu2;: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
}
var主体:一些视图{
VStack(间距:20){
文本(“AAAAAAA”).scaleEffect(state.a?2:1)
文本(“bbb”).scaleEffect(state.b?2:1)
}
.ontapsigne{
model.invert()
}

.onReceive(model.state,perform:updateState(state:)/向发布者订阅视图的方式是使用
。onReceive(uquo: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
}
var主体:一些视图{
VStack(间距:20){
文本(“AAAAAAA”).scaleEffect(state.a?2:1)
文本(“bbb”).scaleEffect(state.b?2:1)
}
.ontapsigne{
model.invert()
}

.onReceive(model.state,perform:updateState(state:)//这很管用!棒极了–我完全以为我试过了,结果发现它不管用(它只更新了最终状态,但只使用了一个动画).谢谢你…你能扩展你的答案来解释为什么其他方法不起作用吗?要么什么都不做,要么只更新最终状态?我还没有深入分析你的其他方法,但你应该记住,视图只是一个结构,SwiftUI可以复制它并重新初始化它,不管它想要多少次,所以在init中完成的订阅可能会发生在
TestView
的某个副本上,而不是视图层次结构中的副本。在init之后(或直接使用
self.\u prop
)必须将值分配给
@State
属性。这很有效!太棒了–我完全认为我已经尝试过了,但发现它不起作用(它只更新了如此更改的最终状态,但只使用了一个动画).谢谢你…你能扩展你的答案来解释为什么其他方法不起作用吗?要么什么都不做,要么只更新最终状态?我还没有深入分析你的其他方法,但你应该记住,视图只是一个结构,SwiftUI可以复制它并重新初始化它,不管它想要多少次,所以订阅完成