Есть идеи, как помочь этой форме ForEach SwiftUI view обновить назначение навигационной ссылки?

#swiftui #swiftui-list #swiftui-navigationlink #swiftui-form

#swiftui #swiftui-list #swiftui-navigationlink #swiftui-form

Вопрос:

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

Ожидаемое поведение: галочки должны обновляться при выборе другого блюда.
Наблюдаемое поведение: вы можете видеть, как любимая еда меняется за пределами пункта назначения NavigationLink, но не внутри.

Эта настройка отражает динамическое приложение, в котором ForEach используется для отображения различных навигационных ссылок в форме на основе родительских данных. Как ни странно, это работает, если вы заменяете Form на VStack, поэтому мне любопытно, почему это не обновляется.

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

Пример проекта # 1 с привязкой — Dropbox
Пример проекта # 2 без привязки — Dropbox

Код # 1:

 //
//  ContentView.swift
//  Form Updating Example
//
//  Created by Sahand Nayebaziz on 12/10/20.
//

import SwiftUI

struct ContentView: View {
    @State var isPresentingMainView = false
    @State var favoriteFood: FoodType = .bagel
    
    var body: some View {
        VStack {
            Button(action: { isPresentingMainView = true }, label: {
                Text("Present Main View")
            })
        }
        .fullScreenCover(isPresented: $isPresentingMainView) {
            MainView(favoriteFood: $favoriteFood)
        }
    }
}

struct MainView: View {
    @Binding var favoriteFood: FoodType
    
    var body: some View {
        NavigationView {
            HStack {
                Spacer()
                Text(favoriteFood.emoji)
                    .font(.title)
                    .foregroundColor(.secondary)
                Spacer()
                NavigationView {
                    Form {
                        List {
                            ForEach(["SomethingRepresentingShowingFood"], id: .self) { _ in
                                NavigationLink(
                                    destination: makeDetail(),
                                    label: {
                                        Text("Food Randomizer")
                                    })
                            }
                        }
                    }
                }
                .navigationViewStyle(StackNavigationViewStyle())
                .frame(maxWidth: 350)
            }
            .navigationTitle("Main")
            .navigationBarTitleDisplayMode(.inline)
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    func makeDetail() -> some View {
        Form {
            ForEach(FoodType.allCases) { foodType in
                Button(action: { favoriteFood = foodType }, label: {
                    HStack {
                        Text(foodType.emoji)
                        Spacer()
                        if favoriteFood == foodType {
                            Image(systemName: "checkmark")
                        }
                    }
                })
            }
        }
    }
}

enum FoodType: String, Identifiable, CaseIterable {
    case bagel, pizza, broccoli
    
    var id: String { rawValue }
    
    var emoji: String {
        switch self {
        case .bagel: return "🥯"
        case .pizza: return "🍕"
        case .broccoli: return "🥦"
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
            MainView(favoriteFood: .constant(.bagel))
            MainView(favoriteFood: .constant(.bagel))
                .makeDetail()
        }
    }
}
 

Код # 2:

 //
//  ContentView.swift
//  Form Updating Example
//
//  Created by Sahand Nayebaziz on 12/10/20.
//

import SwiftUI

struct ContentView: View {
    @State var isPresentingMainView = false
    @State var favoriteFood: FoodType = .bagel
    
    var body: some View {
        VStack {
            Button(action: { isPresentingMainView = true }, label: {
                Text("Present Main View")
            })
        }
        .fullScreenCover(isPresented: $isPresentingMainView) {
            MainView(currentFavoriteFood: favoriteFood, onUpdateFavoriteFood: { favoriteFood = $0 })
        }
    }
}

struct MainView: View {
    let currentFavoriteFood: FoodType
    let onUpdateFavoriteFood: (FoodType) -> Void
    
    var body: some View {
        NavigationView {
            HStack {
                Spacer()
                Text(currentFavoriteFood.emoji)
                    .font(.title)
                    .foregroundColor(.secondary)
                Spacer()
                NavigationView {
                    Form {
                        List {
                            ForEach(["SomethingRepresentingShowingFood"], id: .self) { _ in
                                NavigationLink(
                                    destination: makeDetail(),
                                    label: {
                                        Text("Food Randomizer")
                                    })
                            }
                        }
                    }
                }
                .navigationViewStyle(StackNavigationViewStyle())
                .frame(maxWidth: 350)
            }
            .navigationTitle("Main")
            .navigationBarTitleDisplayMode(.inline)
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    func makeDetail() -> some View {
        Form {
            ForEach(FoodType.allCases) { foodType in
                Button(action: { onUpdateFavoriteFood(foodType) }, label: {
                    HStack {
                        Text(foodType.emoji)
                        Spacer()
                        if currentFavoriteFood == foodType {
                            Image(systemName: "checkmark")
                        }
                    }
                })
            }
        }
    }
}

enum FoodType: String, Identifiable, CaseIterable {
    case bagel, pizza, broccoli
    
    var id: String { rawValue }
    
    var emoji: String {
        switch self {
        case .bagel: return "🥯"
        case .pizza: return "🍕"
        case .broccoli: return "🥦"
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
            MainView(currentFavoriteFood: .bagel, onUpdateFavoriteFood: { _ in })
            MainView(currentFavoriteFood: .bagel, onUpdateFavoriteFood: { _ in })
                .makeDetail()
        }
    }
}
 

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

1. Привязка плохо работает в иерархиях глубокого просмотра или между разными иерархиями. Я рекомендую использовать модель представления с шаблоном ObservableObject / ObservedObject вместо состояния / привязки для сценариев, подобных вашему.

2. Тем не менее, я все еще ищу дополнительную информацию об этом. Потому что это работает, если вы меняете форму на VStack, а состояние / привязка теоретически должны работать, не так ли?

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