swift - 在 SwiftUI 中创建一个包含自定义类数组的列表

标签 swift list swiftui combine

我尝试在 SwiftUI 中创建自定义类列表,但在 UI 更新方面一直存在问题。我让我的类(class)符合 BindableObject它调用didChange.send()当属性更改时功能正常,但更改似乎没有传递到数组,因为当我更改数组中自定义类的属性时 View 没有更新。

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

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

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

现在我的问题是,是否有办法链接 Media 的发布者?给 Array<Media> 的出版商,所以当 Media出版商开火,Array<Media>发布者也触发,因此导致 View 重新呈现。

当我移动 Text(media.name)在一个单独的 View 中(它将媒体作为 @State 属性保存,它按预期工作,因为 subview 本身请求重新渲染。

但假设我不想使用自定义 View 而只是一个简单的 Text看看,有什么办法可以做到这一点吗?

最佳答案

您正在混合两种不同的东西。 @State 旨在用于 View 内部的任何内容。而 @BindableObject 是用来保存 View 外部的东西(即你的数据模型)。定义 @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")
                }
            }
        }
    }
}

您通常会在 View 的某些内部状态中使用@State,但在制作原型(prototype)时也会使用。您对 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")
                }
            }
        }
    }
}

关于swift - 在 SwiftUI 中创建一个包含自定义类数组的列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56841842/

相关文章:

ios - 将 PDF 传递给 UIActivityViewController

ios - Google map 中的折线点击

java - 如何将这个列表翻译成Java?

swift - 将变量或常量标记为私有(private)在性能或其他方面有什么缺点吗?

swift - 如何在没有固定宽度的情况下让文本包裹在 UILabel(通过 UIViewRepresentable)中?

ios - 动画 UILabel 进度百分比 swift

android - 如何在 "Add widget"对话框中对小部件进行分组

python从列表中的字符串中删除空格

iOS 14 菜单选择器样式的标签在更改时有时会变暗

ios - Facebook 分享行动 Swift