NavigationLink скрывает представление назначения или вызывает бесконечные обновления представления

Рассмотрим ситуацию, когда у вас есть ContentView и DestinationView. Оба они зависят от некоторых общих данных, которые обычно находятся внутри @ObservedObject var viewModel, которые вы передаете от родителя к потомку либо через @EnvironmentObject, либо непосредственно внутри init(). DestinationView в этом случае хочет обогатить viewModel, получая некоторый дополнительный контент внутри .onAppear.

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

При использовании List вы явно устанавливаете идентификаторы строк, и поэтому представление не изменяется, но если NavigationLink нет в списке, он обновит все представление, сбрасывая его состояние и скрывая DestinationView.

Возникает вопрос: как сделать NavigationLink обновление / перерисовку только при необходимости?


person Lonkly    schedule 29.09.2020    source источник


Ответы (1)


В SwiftUI механизм обновления сравнивает структуры View, чтобы определить, нужно ли их обновлять. Я пробовал много вариантов, например, создав ViewModel Hashable, Equatable и Identifiable, заставляя его обновляться только при необходимости, но ни один из них не работал.

В этом случае единственным рабочим решением является создание NavigationLink оболочки, предоставление ей id для проверки равенства и использование вместо нее.

struct NavigationLinkWrapper<DestinationView: View, LabelView: View>: View, Identifiable, Equatable {
    static func == (lhs: NavigationLinkWrapper, rhs: NavigationLinkWrapper) -> Bool {
        lhs.id == rhs.id
    }
    
    let id: Int
    let label: LabelView
    let destination: DestinationView // or LazyView<DestinationView>
    
    var body: some View {
        NavigationLink(destination: destination) {
            label
        }
    }
}

Затем в ContentView используйте его с .equatable()

NavigationLinkWrapper(id: self.viewModel.hashValue,
                   label: myOrdersLabel,
             destination: DestinationView(viewModel: self.viewModel)
).equatable()

Полезный совет:

Если ваш ContentView также выполняет некоторые обновления, которые могут повлиять на DestinationView, можно использовать LazyView, чтобы предотвратить повторную инициализацию Destination еще до того, как он появится на экране.

struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    var body: Content {
        build()
    }
}

P.S: Apple, похоже, исправила эту проблему в iOS14, так что это только проблема, связанная с iOS13.

person Lonkly    schedule 29.09.2020
comment
Вместо использования AnyView вы можете сделать свой NavigationLinkWrapper общим (как вы сделали с LazyView). - person pawello2222; 29.09.2020
comment
спасибо, @ pawello2222, я так привык к AnyView при написании пользовательского интерфейса, что забыл о моих любимых дженериках, которые совершенно необходимы в отдельном компоненте. - person Lonkly; 29.09.2020