Вычисленное свойство из дочерней структуры Не обновляется в SwiftUI

#swift #swiftui #combine #observableobject

Вопрос:

У меня в приложении SwiftUI есть модель данных, которая выглядит примерно так:

 struct Entry: Identifiable{
  var id = UUID().uuidString
  var name = ""

  var duration: Int{
    //An SQLite query that returns the total of the "duration" column
    let total = try! dbc.scalar(tableFlight.filter(db.entry == id).select(db.duration.total))
    return Int(total)
  }
}

struct Flight: Identifiable{
  var id = UUID().uuidString
  var duration = 0
  var entry: String?
}
 

У меня есть ObservableObject модель представления, которая создает записи, подобные этой:

 class EntryModel: ObservableObject{
  static let shared = EntryModel()
  @Published var entries = [Entry]()

  init(){
    get()
  }

  func get(){
    //Stuff to fetch the entries
    entries = //SQLite query that returns an array of Entry objects
  }
}
 

Затем, наконец , в моем View , я перечисляю все имена записей и связанные duration с ними, как это:

 ForEach(modelEntry.entries){ entry in
  VStack{
    Text(entry.name) //<-- Updates fine
    Text(entry.duration) //<-- Gets set initially, but no updates
  }
}
 

Проблема , с которой я сталкиваюсь, заключается в том, что, когда я обновляю a Flight для этого Entry , duration на мой взгляд, он не обновляется. Я знаю, что этого не произойдет, потому что только entries они будут перерисовываться, когда они будут изменены.

Но даже если я вручную вызову get() функцию в моем EntryModel , связанная функция все duration равно не обновляется.

Есть ли лучший способ сделать это? Как заставить вычисленные свойства родителя пересчитываться при обновлении его дочернего элемента?

Комментарии:

1. Как насчет того, чтобы позвонить objectWillChange.send() после get() ? Я не уверен, сработает это или нет, но попробовать стоит-по крайней мере, это даст сигнал представлению попытаться обновиться.

2. Чего я не понимаю, так это того, что ваша продолжительность даже не зависит от чего-либо в вашей модели входа. Итак, что именно вы обновляете в своей модели входа?. Потому что dbc нет в вашей модели. Я могу только предположить (и опасаться), что это какая-то глобальная переменная (или вы использовали нижний регистр для класса, чтобы запутать читателя).

3. Как указано в комментарии под duration свойством, это запрос SQLite, который возвращает связанные данные. dbc представляет собой синглтон базы данных. Однако на самом деле это не имеет отношения к рассматриваемому вопросу.

4. Извините, но это просто кодирование против типов значений. Тип значения не должен иметь скрытых зависимостей, подобных этому, и особенно иметь возможность вести себя по-разному, если он используется в разные моменты времени. Эта функция должна быть глобальной функцией или, что еще лучше, частью класса, который раскрывает dbc зависимость.

5. Я понимаю. Не делай этого. Это причинит тебе боль. Поместите этот запрос в инициализатор. Ваш запрос будет выполняться каждый раз, когда представление будет повторно отображено не только для описания, но и для чего угодно.

Ответ №1:

Я все понял. В моем реальном коде использовался дочерний View элемент внутри ForEach where the VStack is. Я просто передавал entry ему значение, так что значения устанавливались только изначально и, следовательно, не реагировали.

Изменив это entry на Binding «а», это работает:

 ForEach($modelEntry.entries){ $entry in
  ChildView(entry: $entry)
}
 

Обратите внимание, что $ на $modelEntry и возврат $entry -это функция Xcode 13 (но обратно совместимая с iOS 14 и macOS 11.0).