It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【SwiftUI】SwiftUIにおける基本的な画面遷移について

概要

SwiftUIにおける基本的な画面遷移の方法についてまとめてみました。

SwiftUIはぱっと1画面を試すのは非常にシンプルで便利なのですが、個人的にはまだ画面遷移の方法に慣れない部分もあり、整理してみました。

環境

① NavigationLink

https://developer.apple.com/documentation/swiftui/navigationlink はViewをスタック構造(push/pop)で管理して遷移する方法です。
UIKitにおけるUINavigationControllerによる遷移と同じ動きをするiOSの伝統的な画面遷移の方法になります。

NavigationLinkは、NavigationView の配下に設置したSubViewでのみ動作します。

実装例

struct RootView: View {
    var body: some View {
        NavigationView {
            // NOTE: NavigationLinkはNavigationViewの内側でなければならない
            NavigationLink(destination: SubView()) {
                // NOTE: Labelを指定すると遷移先へのリンクが自動的に生成される
                Text("Move to SubView")
            }
        }
    }
}

struct SubView: View {
    var body: some View {
        VStack {
            Text("SubView")
        }
        // NOTE: navigationBarに関連するModifierはNavigationViewのSubviewでなければならない
        .navigationBarTitle("SubView")
    }
}

NavigationLink を配置すると、引数に渡したクロージャによって返すLabelを使って画面遷移のボタンを自動的に生成してくれます。

コードによる自動的な遷移

例えばログイン後にHomeへ自動遷移する場合など、ボタンアクション以外で遷移したい場合は、前述のようにボタンの配置は必要ありません。
このような、NavigationLinkをつかって自動的に画面遷移を行いたい場合は、次のように記載します。

struct RootView: View {
    @State var isActiveSubView = false
    var body: some View {
        NavigationView {
            VStack {
                // NOTE: コード上のイベントで遷移したい場合は、LabelにEmptyViewを指定する
                NavigationLink(destination: SubView(),
                               isActive: $isActiveSubView) {
                                EmptyView()
                }
                Button(action: {
                    self.isActiveSubView.toggle()
                }) {
                    Text("画面遷移イベント擬似的に発火")
                }
            }
        }
    }
}

struct SubView: View {
    var body: some View {
        VStack {
            Text("SubView")
        }
        .navigationBarTitle("SubView")
    }
}

NavigationLinkのLabelに EmptyView() を指定することで画面には何も表示されなくなります。
あとは、isActiveで Binding<Bool> の値をイベントの発火で変更してあげることで、画面遷移を発生させます。
なにか強引な気もするのですが、今の所NavigationLinkにLabelなしのイニシャライザが存在しないため、僕はこの方法で実装しています。

② sheet

sheetは、画面下から表示されるモーダル画面による遷移です。
UIKitにおけるUIViewControllerのpresentによる画面遷移と同等の遷移方法になります。

実装例

struct RootView: View {
    @State var isPresentedSubView = false
    var body: some View {
        VStack {
            Button(action: {
                self.isPresentedSubView.toggle()
            }) {
                Text("モーダル画面を表示")
            }
            .sheet(isPresented: $isPresentedSubview) {
                SubView()
            }
        }
    }
}

struct SubView: View {
    var body: some View {
        VStack {
            Text("SubView")
        }
    }
}

.sheet というModifierを使うとモーダル表示を行うことができます。
isPresented にわたす Binding<Bool> の値を変更することで画面遷移を発生させられます。

③ それ以外

最後は①、②以外の方法で画面遷移を行う場合についてです。
これは特別なメソッドを使うわけではなく、単純にBoolのフラグによってSubViewをレンダリングするかしないかで制御します。
遷移時にアニメーションをつけたい場合は、ViewのAnimationを自前で実装します。

実装例

struct RootView: View {
    @State var isShowSubViw = false
    var body: some View {
        ZStack {
            // NOTE: 画面をレンダリングするかで画面遷移を発生する
            if isShowSubViw {
                SubView()
            } else {
                Button(action: {
                    withAnimation() {
                        self.isShowSubViw.toggle()
                    }
                }) {
                    Text("SubViewへ遷移")
                }
            }
        }
    }
}

struct SubView: View {
    var body: some View {
        // NOTE: 画面遷移アニメーションは自前で書く
        GeometryReader { geometory in
            ZStack {
                VStack {
                    Text("SubView")
                }
            }
            .frame(width: geometory.size.width,
                   height: geometory.size.height)
            .background(Color.green)
            .animation(.easeInOut(duration: 0.42))
        }
        .transition(.move(edge: .bottom))
    }
}

上記サンプルでは、画面したからアニメーションで表示される画面遷移を自前で実装しています。

所感

③のフラグで遷移する方法は、UIKItに慣れてきた分、個人的にはすこし違和感があり慣れません^^;。

ですが、NavigationLinkの画面遷移のみではNavigationBarの制御などがうまく行かないケースがありこの方法で画面遷移を行う箇所もあります。

ViewがレンダリングされるタイミングでOnAppearなどの呼び出しも発生するため、この方法でも他遷移と同じ挙動なのですが、原始的な感じもします。

もっとスマートな方法があるのでないかとも思っていますがどうなのでしょうか?