Представление не обновляется, если обновляется исходный массив

Представьте, что у вас есть struct лайк

struct TheObject: Identifiable, Hashable {
    var id: String
    var title: String
}

Теперь вы храните массив этого типа внутри ViewModel.

class Model: ObservableObject {
    @Published var objects = [TheObject(id: "id", title: "title")]
}

Вы создаете NavigationLink для каждого элемента objects. Эти навигационные ссылки содержат кнопку, которая заменяет массив objects тем же объектом (по идентификатору), но с другим свойством.

 @StateObject var model = Model()

 NavigationStack {
     List(model.objects) { object in
         NavigationLink("Text", value: object)
     }
     .navigationDestination(for: TheObject.self) { object in
         Text(object.title)
             .onTapGesture {
                 model.objects = [TheObject(id: "id", title: "title changed")]
             }
     }
 }

Чего я ожидал?
Я ожидал, что моя открытая навигационная ссылка будет обновлена ​​новым заголовком.
Что я получил взамен?
Вид не изменился. Вместо этого мне приходится открыть представление и снова открыть его, чтобы увидеть изменения.

Как я могу заставить ожидаемое поведение работать и почему оно в настоящее время ведет себя именно так?

похоже ты забыл List

malhal 27.06.2024 20:59

Верно, но это, к сожалению, не имеет отношения к делу.

Meyssam 27.06.2024 21:09

возможно тебя не хватает @EnvironmentObject var model: Model

malhal 27.06.2024 21:23

Это тоже не то. В этом примере я бы получил ошибку компилятора. Я продолжу и добавлю эти детали, чтобы стало более понятно.

Meyssam 27.06.2024 21:27
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
65
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Когда вы используете Text(object.title), переданный object является let и не является наблюдается за изменениями в представлении, поэтому оно не изменится при изменении модели в onTapGesture.

Но вид изменится, если вы используете модель (которая наблюдается при изменениях), как показано в примере кода.

struct TheObject: Identifiable, Hashable {
    var id: String
    var title: String
}

class Model: ObservableObject {
    @Published var objects = [TheObject(id: "id", title: "title")]
}

struct ContentView: View {
    @StateObject private var model = Model()
    
    var body: some View {
        NavigationStack {
            List(model.objects) { object in
                NavigationLink(object.title, value: object) // <--- for testing
            }
            .navigationDestination(for: TheObject.self) { object in
                if let obj = model.objects.first(where: {$0.id == object.id}) {
                    Text(obj.title) // <--- here
                        .onTapGesture {
                            model.objects = [TheObject(id: "id", title: "title changed")]
                        }
                }

            // alternative
//                if let ndx = model.objects.firstIndex(where: {$0.id == object.id}) {
//                    Text(model.objects[ndx].title)
//                        .onTapGesture {
//                            model.objects[ndx].title = "title changed"
//                        }
//                }


            }
        }
    }
}

РЕДАКТИРОВАТЬ-1:

Альтернативой достижению желаемого является использование Observable, более надежная замена ObservableObject class.

Например:

@Observable class TheObject: Identifiable, Hashable {
    
    static func == (lhs: TheObject, rhs: TheObject) -> Bool {
        lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
          hasher.combine(id)
          hasher.combine(title)
      }
    
    var id: String
    var title: String
    
    init(id: String, title: String) {
        self.id = id
        self.title = title
    }
}

@Observable class Model {
    var objects = [TheObject(id: "id", title: "title")]
}

struct ContentView: View {
    @State private var model = Model()
    
    var body: some View {
        NavigationStack {
            List(model.objects) { object in
                NavigationLink(object.title, value: object)
            }
            .navigationDestination(for: TheObject.self) { object in
                Text(object.title)
                    .onTapGesture {
                        object.title = "title changed"
                    }
            }
        }
    }
}

Ваше решение отлично работает! Однако я использую Codable Structs с Firebase, поэтому думаю, что @Observable для меня не может быть и речи. Возможно, это будет полезно другим, кто наткнулся на это. Помимо всего этого, мне интересно, зачем мне вообще нужно использовать этот «обходной путь». Я думал, что соответствие Equatable и Hashable только с помощью id сработает, но это не так. Не могли бы вы это уточнить?

Meyssam 28.06.2024 09:33

Извините, но я не понимаю ваших вопросов, ...why I even need to use this "workaround"... о каком «обходном пути» вы говорите. Аналогично, что вы подразумеваете под ...using id would do the job, which it didn't.

workingdog support Ukraine 28.06.2024 09:41

Под обходным решением я подразумеваю использование оператора first(where: ) вместо прямого доступа к нему. Я идентифицирую TheObject через и только через удостоверение личности. Я ожидал, что компилятор интерпретирует обновленный TheObject как «старый», но с измененным свойством (а затем покажет изменение). По крайней мере, это должно произойти, когда я реализую Equatable, чтобы просто проверить равенство свойства ID. По сути, мой вопрос: почему он думает, что это другой объект?

Meyssam 28.06.2024 09:56

object в navigationDestination — это всего лишь копия неизменяемого struct TheObject. Это let, его нельзя изменить. Чтобы изменить модель, вам необходимо обратиться к исходной модели в class Model: ObservableObject.... Потому что это observed вид, когда вы заявляете @StateObject private var model = Model(). Чтобы сослаться на этот исходный объект, используйте его id, чтобы найти его. Это не выход, это нормально. См. Данные мониторинга, чтобы узнать, как управлять данными в вашем приложении.

workingdog support Ukraine 28.06.2024 10:10

Спасибо за разъяснения, я ценю это!

Meyssam 28.06.2024 10:11

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

 .navigationDestination(for: TheObject.self) { _ in
            Text(model.objects[0].title)
                .onTapGesture {
                    model.objects = [TheObject(id: "id", title: "title changed")]
                }
        }

Спасибо за ваш ответ. В моем реальном приложении я получаю mode.objects из своего облака, поэтому мне нужно заменить весь массив. Также внутри массива может быть более одного значения.

Meyssam 28.06.2024 07:34

Другие вопросы по теме