retlat's blog

SwiftUI の NavigationView で複数回遷移する時がある

初めての iOS アプリを SwiftUI で書いてみたら、 NavigationLink の遷移が複数回発生したのでメモ
対処はしてないので、読み進めても何も解決策は書いてません

環境は

  • Xcode 12.4
  • Simulator (iOS 14.4)

挙動の再現するコードはこんな感じ
よくある NavigationBar のボタンで画面遷移するやつ
Timer を使用しているところは、実際に作ったものだとネットワーク経由のデータ取得をしてる

import SwiftUI

class ViewModel: ObservableObject {
  @Published var message = "aaa"

  init(_ number: Int) {
    Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { _ in
      self.message = "bbb"
      print("timer end \(number)")
    }
  }
}

struct MainView: View {
  @ObservedObject var vm: ViewModel

  var body: some View {
    NavigationView {
      Text(message)
        .toolbar {
          ToolbarItem(placement: .navigationBarTrailing) {
            NavigationLink(destination: SubView()) {
              Text("Show")
            }
          }
        }
    }
  }
}

struct SubView: View {
  @ObservedObject var vm: ViewModel

  var body: some View {
    Text("Sub view")
  }
}

これで Timer.scheduledTimer の block が実行される前に SubView に遷移すると、 block が実行されるタイミングで再度遷移する
ページのスタックが MainView -> SubView -> SubView になってしまうので、 NavigationBar の戻るをタップしても SubView が出る

なぜこの動きになるのかは調べたいけど、 Jetpack Compose の State Hoisting みたいに 状態管理を基点の View かその上にまで持ち上げる設計に直す方が優先な気がする

ちなみに SubView から ObservedObject を消すと発生しない
あとはこんな感じで toolbar から外すと遷移は 1 回だけになる

  struct MainView: View {
    @ObservedObject var vm: ViewModel
  
    var body: some View {
      NavigationView {
-       Text(message)
-         .toolbar {
-           ToolbarItem(placement: .navigationBarTrailing) {
-             NavigationLink(destination: SubView()) {
-               Text("Show")
-             }
-           }
-         }
+       VStack {
+         Text(message)
+         NavigationLink(destination: SubView()) {
+           Text("Show")
+         }
+       }
      }
    }
  }
Post: 2021-04-03 Update: 2021-08-18
Tags: SwiftUI