Создать список с массивом пользовательских классов в SwiftUI - PullRequest
0 голосов
/ 01 июля 2019

Я попытался создать список пользовательских классов в SwiftUI, но у меня по-прежнему возникают проблемы с обновлениями пользовательского интерфейса.Я сделал свой класс соответствующим 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")
                }
            }
        }
    }
}

При нажатии кнопки «Изменить», он просто меняет имя первого носителяобъект в массиве, который не приводит к повторной визуализации представления.Когда я нажимаю кнопку «Добавить», он добавляет объект в массив, поэтому повторно отображает пользовательский интерфейс, а также отображает измененное имя первого объекта (после нажатия «Изменить»).

Теперь мой вопросесть, если есть способ связать издателя Media с издателем Array<Media>, так что при запуске Media Publisher тоже запускается Array<Media> Publisher, что приводит к повторной визуализации представления.

Когда я перемещаю Text(media.name) в отдельном представлении (которое хранит носитель как свойство @State, оно работает как задумано, поскольку само подпредставление запрашивает повторную визуализацию.

Но если предположить, что я не хочу использовать пользовательское представление, а просто простое представление Text, есть ли способ сделать это?

1 Ответ

3 голосов
/ 01 июля 2019

Вы смешиваете две разные вещи. @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?


UPDATE

Чтобы реализовать его с помощью @ObjectBinding:

Предположим, что вы создаете экземпляр CustomView в SceneDelegate, вам нужно передать его библиотеке:

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