20
9

More than 1 year has passed since last update.

SwiftUI4, iOS16以降で使用できる NavigationStackについて軽く調査した

Posted at

まえがき

WWDC2022にてNavigationStackが新しく発表されました。
iOS16からNavigationViewはdeprecatedで、NavigationStackを使わないといけないです。

SwiftUIは画面遷移周りがよくない・・・・!と、
SwiftUIを実戦投入していても、画面遷移まわりはUIKitを使用しているという話をチラホラききますし、
私が参画している現場においても画面遷移はUIKitで書いてます。

NavigationStackをどんなものか気になっていたので、軽く調査してみました。

Case 1, NavigationStackとNavigationLinkのみを使って遷移

NavigationStackの中にNavigationLinkを配置して遷移するだけ。
NavigationStackをdeprecatedされたNavigationViewのようです。

struct NavigationStackSample1: View {
    let parkList: [Park] = [.init(name: "メリケンパーク"), .init(name: "万博記念公園"), .init(name: "奈良公園"), .init(name: "代々木公園")]
    var body: some View {
        NavigationStack {
            List(parkList) { park in
                NavigationLink(park.name) {
                    ParkDetailView(park: park)
                }
            }
            .navigationTitle("公園リスト")
        }
    }
}

Case 2, NavigationStackとNavigationLinkとnavigationDestinationを使った例

struct NavigationStackSample2: View {
    let parkList: [Park] =  [.init(name: "メリケンパーク"), .init(name: "万博記念公園"), .init(name: "奈良公園"), .init(name: "代々木公園")]
   let fruitsList: [Fruits] =  [.init(name: "スイカ"), .init(name: "みかん"), .init(name: "りんご"), .init(name: "バナナ")]
    var body: some View {
        NavigationStack {
            List(parkList) { park in
                NavigationLink(park.name, value: park)
            }
            .navigationTitle("Park List")
            .navigationDestination(for: Park.self) { park in
                ParkDetailView(park: park)
            }
            .navigationDestination(for: Fruits.self) { fruits in
                FruitsDetailView(fruits: fruits)
            }
        }
    }
}
func navigationDestination<D, C>(
    for data: D.Type,
    destination: @escaping (D) -> C
) -> some View where D : Hashable, C : View

このnavigationDestinationはD.Typeを引数にとります。

NavigationLink(park.name, value: park)
   .navigationDestination(for: Park.self) { park in
        ParkDetailView(park: park)
    }

この例だと、
NavigationLinkのvalueにPark型のオブジェクトが渡ってくると、
このParkDetailViewに遷移してくださいという意味になります。
NavigationLinkのvalueに渡ってくる型によって、どの画面に遷移するかを決定しているので、
型の数だけnavigationDestinationを生やせばよいです。

Case 3, Routerをenumにして遷移する例

StructだけでなくEnumでも良いです。
enumでcaseごとに定義しておいて、
navigationDestination内で遷移を定義してもよいです。

navigationDestinationのクロージャの引数としてenumのRouteが渡ってくるので、
それをswitchでどの画面に遷移するか定義すればよいです。

enum Route: Hashable {
    case park(Park)
    case fruits(Fruits)
}

NavigationStack {
    List {
        Section("Fruits List") {
            ForEach(fruitsList) { fruits in
                NavigationLink(value: Route.fruits(fruits)) {
                    Text(fruits.name)
                }
            }
        }

        Section("Park List") {
            ForEach(parkList) { park in
                NavigationLink(value: Route.park(park)) {
                    Text(park.name)
                }
            }
        }
    }
    .navigationTitle("Fruits Park")
    .navigationDestination(for: Route.self) { route in
        switch route {
        case let .fruits(fruits):
            FruitsDetailView(fruits: fruits)
        case let .park(park):
            ParkDetailView(park: park)
        }
    }
}

Case 4, NavigationStackのpathを使った遷移

Stackのイメージ

NavigationStackは文字通りStack、つまり、Viewを上に重ねていっております。

pathに構造体Parkが遷移されればNavigationStackのpathで
遷移を操作していく。

そのPathからremoveLastすれば1つ前に戻り、
removeLast(2)すれば、2つ前に戻る。

以下のshowFirstParkを実行すれば、

奈良公園の遷移して、代々木公園に遷移する。

@State private var path: [Park] = []

var body: some View {
    NavigationStack(path: $path) {
        List(parkList) { park in
            NavigationLink(value: park) {
                Text(park.name)
            }
        }
        .navigationDestination(for: Park.self) { park in
            ParkDetailView(park: park)
        }
        .navigationTitle("公園リスト")
    }
    .onChange(of: path) { newValue in
        print(newValue)
    }
    .onAppear {
        showFirstPark()
    }
}

func showFirstPark() {
    path = [.init(name: "奈良公園"), .init(name: "代々木公園")]
}

Case 5, NavigationPathのpathを使った遷移

pathには、非同期処理できてBindingできる@Stateないしは@Publishedのものを設定する。

pathに設定するのは、画面遷移するときに使用する構造体やEnumよいです

enum Route: Hashable {
    case park(Park)
    case fruits(Fruits)
}

final class RouterEnum: ObservableObject {
    @Published var path: [Route] = []
}

routerはNavigationPathでも良いし、enumにして定義してもよいです。

NavigationPathというのもあり、
前の例だとpathは構造体Parkに限定していましたが、
navigationDestinationで定義されているものであれば、遷移が可能となります。

@Environmentプロパティーラッパーを使えば、子の環境すべてに伝搬できるので、

final class RouterNavigationPath: ObservableObject {
    @Published var path =  NavigationPath()
}

struct NavigationStackSample6: View {
    @EnvironmentObject var router: RouterNavigationPath
    let parkList: [Park] = [Park(name: "メリケンパーク"), .init(name: "万博記念公園"), .init(name: "奈良公園"), .init(name: "代々木公園")]

    var body: some View {
        NavigationStack(path: $router.path) {
            List(parkList) { park in
                NavigationLink(value: park) {
                    Text(park.name)
                }
            }
            .navigationDestination(for: Park.self) { park in
                ParkDetailView2(park: park)
            }
            .navigationTitle("公園リスト")
        }
        .environmentObject(router)
    }
}

遷移先の画面にも
@EnvironmentObject var router: RouterNavigationPath
と書くことで、遷移先のParkDetailView2のrouterからremoveLast()すれば、
戻ることができます。

struct ParkDetailView2: View {
    let park: Park
    @EnvironmentObject var router: RouterNavigationPath

    var body: some View {
        Group {
            Text("\(park.name)にようこそ")
            Button("戻る"){
                router.path.removeLast()
            }
        }
        .onAppear {
            print("router", router)
        }
    }
}

あとがき

かなり自由度が上がっていて書きやすそうなのも良いですね、
がっつりNavigationStackを使って書いてみようとおもいます。

リファレンス

Youtubeのコードまとめて 調査する

First Look at the Navigation API in SwiftUI
https://www.youtube.com/watch?v=3Ur6kUStKRo&t

SwiftUI 4: Navigation Stacks (WWDC 2022) – iOS
https://www.youtube.com/watch?v=dZdHJQT7Z4Y

New Programmatic Navigation In SwiftUI 4.0 - Xcode 13 - WWDC 2022
https://www.youtube.com/watch?v=rm7-DkqKZCo&t

NavigationStack
https://developer.apple.com/documentation/swiftui/navigationstack

A First Look at SwiftUI NavigationStack, NavigationPath, and “navigationDestination”
https://betterprogramming.pub/a-first-look-at-swiftui-navigationstack-navigationpath-and-navigationdestination-3a9bbb300e08

Mastering NavigationStack in SwiftUI. Navigator Pattern.
https://swiftwithmajid.com/2022/06/15/mastering-navigationstack-in-swiftui-navigator-pattern/

20
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
9