【SwiftUI】NavigationSplitView誤用しててonAppear呼ばれなかった件
個人開発でハマった件を書きます。
前提
Pieces of Paperはサイドバー方式で、ノート一覧を表示しています。
このようなUIを実現するためには、SwiftUIの List
を使うんですが、
サイドバーで画面を切り替えても onAppear が呼ばれない、という現象が起きていました。
ちゃんとドキュメント読んだ人だったら、当然回避できている問題かもしれませんが、共有で書いておきます。
NavigationSplitViewへの移行
iOS 16からSwiftUIのNavigationまわりが大幅変更されまして、本アプリも移行しました。
NavigationView
がdeprecatedとなり、 NavigationSplitView
を使うことになりました。
具体的には、このような変更をかけました。
var body: some View {
if #available(iOS 16.0, *) {
NavigationSplitView() {
list
} detail: {
Notes(viewModel: inboxNoteViewModel)
}
} else {
NavigationView {
list
}
}
}
var list: some View {
List {
Section(header: Text("Folder")) {
NavigationLink(destination: Notes(viewModel: inboxNoteViewModel),
isActive: $isActive) {
Label("Inbox", systemImage: "tray")
}
// …
}
// …
}
.navigationTitle("Pieces of Paper")
}
この時点で NavigationSplitView
を誤用していました。
正しい使い方
マイグレーションガイドをナナメ読みして変更かけたんですが、誤解していました。
サンプルコードだとこうなっています。
let colors: [Color] = [.purple, .pink, .orange]
@State private var selection: Color? = nil // Nothing selected by default.
var body: some View {
NavigationSplitView {
List(colors, id: \.self, selection: $selection) { color in
NavigationLink(color.description, value: color)
}
} detail: {
if let color = selection {
ColorDetail(color: color)
} else {
Text("Pick a color")
}
}
}
State変数 selection
を持っていて、第二引数detailのクロージャー中で参照します。
第一引数はあくまでサイドバーで表示するコンテンツと、State変数の更新だけを行います。
不具合を仕込んだ箇所
さて、最初のケースに戻りましょう。
NavigationSplitView() {
list
} detail: {
Notes(viewModel: inboxNoteViewModel)
}
var list: some View {
List {
Section(header: Text("Folder")) {
NavigationLink(destination: Notes(viewModel: inboxNoteViewModel),
isActive: $isActive) {
Label("Inbox", systemImage: "tray")
}
// …
}
}
この指定だと、detailには最初に表示されるView、その後サイドバーの操作で切り替える際は NavigationLink(destination:)
を使っていました。
厄介なのはこの指定でもページは切り替わります。
ただonAppearは呼ばれません。
ListのUIが、iOSとiPadOSで変化するので、iOSだと呼ばれたような気がします。
学び
NavigationSplitView x NavigationLink(destination:)はなんとなく動作しますが、正しい指定ではなさそうなので避けましょう。
(了)
Discussion