SwiftUI 如何将@Published 分配给另一个@Published

SwiftUI How to assign @Published to another @Published

我有一个视图(MainView),里面有另一个视图(FooGroupView)。每次我单击 FooGroupView 中的项目,然后将值更改传递给 MainView

如下所示:

白色视图为MainView,红色视图为FooGroupView。如果单击 foo1,则文本应为 Current:foo1,如果单击 foo2,则文本应为 Current:foo2

我的代码在这里:

Test.Playground

import SwiftUI
import PlaygroundSupport

struct FooRowView: View {

    var foo: Foo

    var body: some View {
        VStack {
            Color(.red)
            Text(foo.name)
        }
    }
}

class Foo: Identifiable, ObservableObject {
    var name: String
    var id: String

    init(name: String, id: String) {
        self.name = name
        self.id = id
    }
}

final class FooGroupViewModel: ObservableObject {

    var foos: [Foo] {
        didSet {
            self.selectedFoo = foos.first
        }
    }

    @Published var selectedFoo: Foo? {
        didSet {
            print("You selected: \(selectedFoo?.name)")
        }
    }

    init(foos: [Foo] = []) {
        self.foos = foos
        self.selectedFoo = foos.first
    }
}

struct FooGroupView: View {

    var viewModel: FooGroupViewModel

    var body: some View {
        ScrollView(.horizontal) {
            HStack(alignment: .center, spacing: 32, content: {

                // Error: error: Execution was interrupted, reason: signal SIGABRT.
                //The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

                /*ForEach(viewModel.foos) { foo in
                    Text(foo.name)
                }*/


                Text(viewModel.foos[0].name).onTapGesture {
                    viewModel.selectedFoo = viewModel.foos[0]
                }
                Text(viewModel.foos[1].name).onTapGesture {
                    viewModel.selectedFoo = viewModel.foos[1]
                }
            })
        }
    }
}

final class MainViewModel: ObservableObject {
    @ObservedObject var fooGroupViewModel: FooGroupViewModel

    @Published var currentFoo: Foo?

    init() {
        fooGroupViewModel = FooGroupViewModel(foos: [Foo(name: "foo1", id: "1"), Foo(name: "foo2", id: "2")])
        currentFoo = self.fooGroupViewModel.selectedFoo

    }
}

struct MainView: View {

    @ObservedObject var viewModel = MainViewModel()

    var body: some View {
        VStack {
            FooGroupView(viewModel: viewModel.fooGroupViewModel)
                .background(Color.red)
            Spacer()
            Text("Current:\(viewModel.currentFoo!.name)")
        }.frame(width: 200, height: 200)

    }
}


PlaygroundPage.current.liveView = UIHostingController(rootView: MainView())

MainViewModel中如果currentFoo有变化,那么UI应该更新。

当点击 foo1foo2 时,FooGroupViewModel 中的 selectedFoo 已更新,然后 MainViewModel 中应该得到更改(如 @ObservedObject var fooGroupViewModel: FooGroupViewModel)

我的问题是如何让currentFoo知道fooGroupViewModelselectedFoo的变化?我以为这条线可以观察 selectedFoo 的变化,如果有任何更新,则触发 currentFoo 变化并更新 UI.

currentFoo = self.fooGroupViewModel.selectedFoo

但实际上是行不通的

有什么帮助吗?谢谢!

我还有一个问题,就是上面代码中的注释(如果将我的代码粘贴到Playground中,第56行)

ForEach(viewModel.foos) { foo in
                    Text(foo.name)
                }

此代码使 playground 错误:

// Error: error: Execution was interrupted, reason: signal SIGABRT.
                //The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

控制台中没有任何其他信息。不知道为什么。感谢您的帮助。

只做 currentFoo = self.fooGroupViewModel.selectedFoo 绝对行不通 - 它只是将 selectedFoo 当时的内容分配给 currentFoo

根据您的设置,您需要订阅 fooGroupViewModel.selectedFoo 中的更改,这可以通过 Published 发布者完成,因为它是 @Published 属性 .

此外,请记住 @ObservedObject 仅在视图内部有意义,因此我将其删除。

import Combine

// ...
 
final class MainViewModel: ObservableObject {

    var fooGroupViewModel: FooGroupViewModel
    @Published var currentFoo: Foo?

    private var cancellables: Set<AnyCancellable> = []

    init() {
        fooGroupViewModel = FooGroupViewModel(foos: 
                     [Foo(name: "foo1", id: "1"), Foo(name: "foo2", id: "2")])
        
        fooGroupViewModel.$selectedFoo
           .assign(to: \.currentFoo, on: self)
           .store(in: &cancellables)

    }
}