在SwiftUI

时间:2019-07-01 20:08:49

标签: swift list swiftui combine

我尝试在SwiftUI中创建自定义类的列表,但是UI更新一直存在问题。我使我的类符合BindableObject并在属性更改时正确调用didChange.send()函数,但更改似乎未传递给数组,因为当视图更改时视图不会更新我在数组中更改了自定义类的属性。

这是我的意思的基本示例:

class Media: BindableObject, Identifiable {
    typealias PublisherType = PassthroughSubject<Void, Never>
    var didChange = PublisherType()

    var id: Int {
        didSet {
            didChange.send()
        }
    }
    var name: String {
        didSet {
            print("Name changed from \(oldValue) to \(self.name)")
            didChange.send()
        }
    }

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

struct ContentView : View {
    @State private var media = [
        Media(id: 0, name: "Name 0"),
        Media(id: 1, name: "Name 1"),
        Media(id: 2, name: "Name 2"),
        Media(id: 3, name: "Name 3"),
    ]

    var body: some View {
        VStack {
            List(media) { media in
                Text(media.name)
            }
            HStack {
                Button(action: {
                    self.media.first!.name = "Name \(Int.random(in: 100...199))"
                }) {
                    Text("Change")
                }
                Button(action: {
                    self.media.append(Media(id: 4, name: "Name 4"))
                }) {
                    Text("Add")
                }
            }
        }
    }
}

当按下“更改”按钮时,它仅更改数组中第一个媒体对象的名称,而不会导致视图的重新呈现。当我按下“添加”按钮时,他将一个对象添加到数组,因此重新渲染UI并显示第一个对象的更改名称(按“更改”)。

现在我的问题是,是否有一种方法可以将Media的发布者链接到Array<Media>的发布者,以便在Media发布者被解雇时,{{1} }发布商也会触发,因此导致视图重新呈现。

当我在单独的视图中移动Array<Media>(将媒体作为Text(media.name)属性保存时,它会按预期工作,因为子视图本身要求重新渲染。

但是,假设我不想使用自定义视图,而只是一个简单的@State视图,有什么方法可以做到这一点?

1 个答案:

答案 0 :(得分:3)

您正在混合两个独立的事物。 @State用于视图内部的任何内容。 @BindableObject是用于在视图外部保存某些内容(即您的数据模型)。定义@BindableObject时,不使用@State引用它,而是使用@ObjectBinding引用。

我不知道是什么情况,但是如果您打算使用@State,则实现应该是这样的:

struct Media {

    var id: Int
    var name: String

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

struct ContentView : View {
    @State var media = [
        Media(id: 0, name: "Name 0"),
        Media(id: 1, name: "Name 1"),
        Media(id: 2, name: "Name 2"),
        Media(id: 3, name: "Name 3"),
    ]

    var body: some View {
        VStack {
            List(media.identified(by: \.id)) { media in
                Text(media.name)
            }

            HStack {
                Button(action: {
                    self.media[0].name = "Name \(Int.random(in: 100...199))"
                }) {
                    Text("Change")
                }
                Button(action: {
                    self.media.append(Media(id: 4, name: "Name 4"))
                }) {
                    Text("Add")
                }
            }
        }
    }
}

您通常将@State用于视图的某些内部状态,同时也用于原型设计。您对Media对象的意图是什么?


更新

要使用@ObjectBinding来实现它:

假设您要在SceneDelegate中实例化CustomView,则需要将其传递给库:

        if let windowScene = scene as? UIWindowScene {

            let window = UIWindow(windowScene: windowScene)

            let library = MediaLibrary(store: [Media(id: 0, name: "Name 0"),
            Media(id: 1, name: "Name 1"),
            Media(id: 2, name: "Name 2"),
            Media(id: 3, name: "Name 3")])

            window.rootViewController = UIHostingController(rootView: ContentView(library: library))
            self.window = window
            window.makeKeyAndVisible()
        }

执行:

struct Media {
    let id: Int
    var name: String

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

class MediaLibrary: BindableObject {
    typealias PublisherType = PassthroughSubject<Void, Never>
    var didChange = PublisherType()

    var store: [Media] {
        didSet {
            didChange.send()
        }
    }

    init(store: [Media]) {
        self.store = store
    }
}

struct ContentView : View {
    @ObjectBinding var library: MediaLibrary

    var body: some View {
        VStack {
            List(self.library.store.identified(by: \.id)) { media in
                Text(media.name)
            }
            HStack {
                Button(action: {
                    self.library.store[0].name = "Name \(Int.random(in: 100...199))"
                }) {
                    Text("Change")
                }
                Button(action: {
                    self.library.store.append(Media(id: 4, name: "Name 4"))
                }) {
                    Text("Add")
                }
            }
        }
    }
}