Данные обновления SwiftUI для родительского NavigationView

как я могу обновить данные в TextField, которые живут в деталях NavigationView? Я хочу, чтобы данные обновлялись в списке родительского представления. Вот мой код:

class Address: ObservableObject, Identifiable {
    let id = UUID()
    @Published var name: String
    @Published var age: String
    
    init(name: String, age: String) {
        self.name = name
        self.age = age
    }
}
class AddressBook: ObservableObject {
    @Published var addresses: [Address]

    init(addresses: [Address]) {
        self.addresses = addresses
    }
}
struct TestData {
    static let addressbook = AddressBook(addresses: [
        Address(name: "Person1", age: "39"),
        Address(name: "Person2", age: "22")
    ])
}
struct ContentView: View {
    @ObservedObject var addressbook = TestData.addressbook
    
    var body: some View {
        NavigationView {
            List (addressbook.addresses) {address in
                NavigationLink(destination: AddressDetail(address: address)) {
                    Text(address.name)
                }
            }
            .navigationBarTitle("Addressbook")
        }
        
    }
}

struct AddressDetail: View {
    @ObservedObject var address: Address
    
    var body: some View {
        Form {
            TextField("name", text: $address.name)
            
            TextField("age", text: $address.age)
        }
        
    }
}

Этот код не работает: если я перейду в AddressDetail-View, изменю значения TextField, а затем вернусь, изменения не обновятся в List-View.

Нико


person Nico    schedule 27.06.2020    source источник


Ответы (1)


Проблема в том, что Address является классом, поэтому, если его опубликованные свойства изменились, ссылка в addresses не изменится, но ContentView view наблюдает addressbook как контейнер адресов.

Вот демонстрация возможного подхода (протестировано и работает с Xcode 12b / iOS 14, а также с 11.4 / iOS 13.4)

List (addressbook.addresses) {address in
    NavigationLink(destination: AddressDetail(address: address)) {
        Text(address.name)
            .onReceive(address.objectWillChange) { _ in
                self.addressbook.objectWillChange.send()
            }
    }
}

Альтернативный вариант: на основе Binding (требуется гораздо больше изменений, поэтому, как мне кажется, менее предпочтителен, но стоит упомянуть

struct Address: Identifiable, Hashable {
    let id = UUID()
    var name: String
    var age: String

    init(name: String, age: String) {
        self.name = name
        self.age = age
    }
}
class AddressBook: ObservableObject {
    @Published var addresses: [Address]

    init(addresses: [Address]) {
        self.addresses = addresses
    }
}
struct TestData {
    static let addressbook = AddressBook(addresses: [
        Address(name: "Person1", age: "39"),
        Address(name: "Person2", age: "22")
    ])
}

struct ContentView: View {
    @ObservedObject var addressbook = TestData.addressbook

    var body: some View {
        NavigationView {
            List (Array(addressbook.addresses.enumerated()), id: \.element) { i, address in
                NavigationLink(destination: AddressDetail(address: self.$addressbook.addresses[i])) {
                    Text(address.name)
                }
            }
            .navigationBarTitle("Addressbook")
        }

    }
}

struct AddressDetail: View {
    @Binding var address: Address

    @State private var name: String
    @State private var age: String

    init(address: Binding<Address>) {
        _address = address
        _name = State(initialValue: _address.wrappedValue.name)
        _age = State(initialValue: _address.wrappedValue.age)
    }
    var body: some View {
        Form {
            TextField("name", text: $name)

            TextField("age", text: $age)
        }
        .onDisappear {
            self.address.name = self.name
            self.address.age = self.age
        }
    }
}
person Asperi    schedule 27.06.2020
comment
Спасибо, это работает! Но разве нет другого решения этой проблемы? Мне кажется, что форма редактирования за списком в навигационном представлении используется очень часто. Я хотел бы передать параметр адреса как что-то вроде $ address, но это не работает внутри замыкания. Как бы вы решили эту проблему с помощью @environmentObject? - person Nico; 27.06.2020
comment
@Nico, @EnvironmentObject ведет себя так же, как @ObservedObject, поэтому исходная проблема остается такой, как описано в первом абзаце. Альтернативой является использование @Binding - см. Обновленные сведения об использовании. - person Asperi; 27.06.2020