NavigationLink срабатывает более одного раза в NavigationStack

Я столкнулся с проблемой, когдаNavTestChildViewзвонил еще один раз. Я не понимаю, что происходит не так. Я тестировал на реальном устройстве с iOS 16.0.3 и эмулятором Xcode 14.0.1.

Я заменил исходный код, чтобы дать больше информации об архитектуре, почему я создаюNavTestServiceвnavigationDestination.

      enum NavTestRoute: Hashable {
    case child(Int)
}

class NavTestService: ObservableObject {
    let num: Int
    
    init(num: Int) {
        self.num = num
        print("[init][NavTestService]")
    }

    deinit {
        print("[deinit][NavTestService]")
    }
}

struct NavTestChildView: View {
    @EnvironmentObject var service: NavTestService

    init() {
        print("[init][NavTestChildView]")
    }

    var body: some View {
        Text("NavTestChildView \(service.num)")
    }
}

struct NavTestMainView2: View {
    var body: some View {
        VStack {
            ForEach(1..<10, id: \.self) { num in
                NavigationLink(value: NavTestRoute.child(num)) {
                    Text("Open child \(num)")
                }
            }
        }
    }
}

struct NavTestMainView: View {
    var body: some View {
        NavigationStack {
            NavTestMainView2()
                .navigationDestination(for: NavTestRoute.self) { route in
                    switch route {
                    case let .child(num):
                        NavTestChildView().environmentObject(NavTestService(num: num))
                    }
                }
        }
    }
}

журналы:

      [init][NavTestChildView]
[init][NavTestService]
[deinit][NavTestService]
[init][NavTestChildView]
[init][NavTestService]

2 ответа

Похоже, есть период, когда экземплярNavTestServiceникем не удерживается и уходит из кучи. На практике это вряд ли произойдет, потому что.environmentObjectvars обычно хранятся где-то вверх по иерархии. Если вы изменитеNavTestMainViewсоответственно:

      struct NavTestMainView: View {
    let navTestService = NavTestService()
    var body: some View {
        NavigationStack {
            NavigationLink(value: NavTestRoute.child) {
                Text("Open child")
            }
            .navigationDestination(for: NavTestRoute.self) { route in
                switch route {
                case .child:
                    NavTestChildView().environmentObject(navTestService)
                }
            }
        }
    }
}

... вы не получитеdeinitс и без лишнегоinitтакже. Консоль выведет:

      [init()][NavTestService]
[init()][NavTestChildView]
[init()][NavTestChildView]

Также обратите внимание, что если вы закомментируетеlet navTestService = NavTestService()и завернутьNavTestChildView().environmentObject(NavTestService())в LazyView вы получите следующий вывод:

      [init()][NavTestChildView]
[init()][NavTestService]

ГдеLazyViewявляется:

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

Это не "выстрел", это просто несколько раз запускает структуру View, что совершенно нормально и практически не требует дополнительных затрат, поскольку структуры View являются типами значений. Обычно это происходит из-за того, что дизайн UIKit, управляемый событиями, плохо согласуется с дизайном SwiftUI, управляемым состоянием.

Вы можете упростить свой код, заменив оператор перечисления / случая маршрутизатора несколькимиnavigationDestinationдля каждого типа модели.

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