はじめに
こんにちは。CX事業本部の平屋です。
本記事では、iOS 16以降で利用できるSwiftUIのコンポーネント「NavigationStack」を扱います。
「NavigationView(iOS 16.1で非推奨)」と「NavigationStack(iOS 16〜利用可能)」を比較しながら、その違いを見ていきたいと思います。
「NavigationView」と「NavigationStack」は、どちらもPush/Pop遷移のナビゲーションスタックを管理するためのコンポーネントです。
検証環境
- macOS Monterey 12.6
- Xcode Version 14.0.1
静的な画面遷移の場合
固定のデータをリストに表示し、項目タップで詳細画面に遷移するケースを扱います。
移行前 (NavigationViewを使用)
struct OldSingleColumnNavigationView: View {
let contents = ["A", "B", "C"]
var body: some View {
// NavigationViewを使用
NavigationView {
List {
ForEach(contents, id: \.self) { content in
NavigationLink(destination: Text(content)) {
Text(content)
}
}
}
.navigationTitle("Top")
}
}
}
移行後 (NavigationStackを使用)
単純にNavigationView
をNavigationStack
に置き換えます。
struct NewSingleColumnNavigationView: View {
// ...
var body: some View {
// NavigationStackを使用
NavigationStack {
// ...
}
}
}
// ...
動的な画面遷移の場合
リストの項目タップで、非同期でデータを取得し、取得完了後に詳細画面に遷移するケースを扱います。
移行前 (NavigationViewを使用)
こちらのサンプルコードでは、isActive
フラグを指定できるイニシャライザ(init(destination:isActive:label:)
、iOS 16で非推奨)で非表示のNavigationLinkを作成し、遷移のタイミングでフラグをtrue
にするという、ちょっと回りくどい実装によって実現してます。
struct OldProgrammaticNavigationView: View {
@State private var isActive = false
@State private var content = ""
var body: some View {
NavigationView {
ZStack {
// 画面に表示されないNavigationLink
NavigationLink(destination: Text(content), isActive: $isActive) {
EmptyView()
}
// 画面に表示されるリスト
List {
Button {
// 非同期データ取得の模擬実装
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// データを適用し、フラグをtrueにする
content = "received content"
isActive = true
}
} label: {
Text("Get Content")
}
}
}
.navigationTitle("Top")
}
}
}
移行後 (NavigationStackを使用)
移行後の場合、以下の実装で実現できます。移行前と比べると直感的な実装になっているかと思います。
- (1) 遷移先画面に紐づくデータの入れ物を用意
- (2) (1)と共に
NavigationStack
を作成 - (3)
navigationDestination
モディファイアでデータに対する遷移先を指定 - (4) 遷移させたいタイミングで(1)にデータを追加
struct NewProgrammaticNavigationView: View {
// (1)
@State private var path: [String] = []
var body: some View {
// (2)
NavigationStack(path: $path) {
List {
Button {
// 非同期データ取得の模擬実装
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// (4)
path.append("received content")
}
} label: {
Text("Get Content")
}
}
// (3)
.navigationDestination(for: String.self) { string in
Text(string)
}
.navigationTitle("Top")
}
}
}
また、pathに複数のデータを与えてナビゲーションスタックに複数の画面をセットしたり、pathを空にすることによってルートにPop遷移させることもできるようです。
func updatePath() {
path = ["content1", "content2"]
}
func popToRoot() {
path.removeAll()
}