Привязка к свойству только для чтения в SwiftUI - PullRequest
0 голосов
/ 07 января 2020

У меня есть тип модели, который выглядит следующим образом:

enum State {
    case loading
    case loaded([String])
    case failed(Error)

    var strings: [String]? {
        switch self {
        case .loaded(let strings): return strings
        default: return nil
        }
    }
}

class MyApi: ObservableObject {
    private(set) var state: State = .loading

    func fetch() {
        ... some time later ...
        self.state = .loaded(["Hello", "World"])
    }
}

, и я пытаюсь использовать это для управления представлением SwiftUI.

struct WordListView: View {
    @EnvironmentObject var api: MyApi

    var body: some View {
        ZStack {
            List($api.state.strings) {
                Text($0)
            }
        }
    }
}

Это примерно то, что мой предположения не верны. Я пытаюсь получить список строк для отображения в моем List, когда они загружены, но он не скомпилируется.

Ошибка компилятора Generic parameter 'Subject' could not be inferred, которая после небольшого поиска в Google говорит мне, что привязки являются двусторонними, поэтому не будут работать как с моим private(set), так и с переменной enum State, доступной только для чтения.

Это, кажется, не имеет никакого смысла - есть Ни в коем случае представление не должно сообщать API, загружается он или нет, но это определенно должен быть односторонний поток данных!

Я думаю, мой вопрос: либо

  1. Есть ли способ получить одностороннюю привязку в SwiftUI - т.е. некоторые пользовательские интерфейсы будут обновляться на основе значения, которое он не может изменить.

или

Как я должен был спроектировать этот код! Весьма вероятно, что я пишу код в стиле, который не работает со SwiftUI, но все учебники, которые я вижу в Интернете, аккуратно игнорируют такие вещи, как состояния загрузки / ошибки.

1 Ответ

4 голосов
/ 07 января 2020

Вам на самом деле не нужна привязка для этого.

Интуитивно понятный способ решить, нужна ли вам привязка или нет, заключается в следующем:

Нужно ли в этом представлении изменять переданное значение?

В вашем случае ответ - нет. List не нужно изменять api.state (в отличие от текстового поля или ползунка, например), ему просто нужно его текущее значение в любой момент. Это то, что @State для , но , поскольку состояние не является чем-то, что принадлежит представлению (помните, Apple говорит, что каждое состояние должно быть частным для представления), вы правильно используя некоторую форму ObservableObject (через Среду).

Последний отсутствующий элемент - пометить любое из ваших свойств, которое должно вызвать обновление, с помощью @Published, что удобно для запуска objectWillChange сигнализирует и дает указание любому наблюдательному виду пересчитать свое тело.

Итак, что-то вроде этого поможет:

class MyApi: ObservableObject {
    @Published private(set) var state: State = .loading

    func fetch() {
        self.state = .loaded(["Hello", "World"])
    }
}

struct WordListView: View {
    @EnvironmentObject var api: MyApi

    var body: some View {
        ZStack {
            List(api.state.strings ?? [], id: \.self) {
                Text($0)
            }
        }
    }
}
...