4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[SwiftUI 3.0] 条件でNavigationLinkの遷移先Viewを変更するアレコレ [@ViewBuilder]

Posted at

はじめに

意外とそのまま検索しても出なかったので、NavigationLinkで遷移先となるdestinationを条件で変更する方法について書いておきたいと思います。

iOS14、15での違い

その前に、NavigationLinkのイニシャライザでiOS15とiOS14以前で、destinationが取る型に変更があり、推奨される記法が異なっています。

■ iOS14以前の定義は非推奨となった

■ iOS15

上記は、NavigationLink Apple公式ドキュメントより、代表的なイニシャライザを抜粋しました。

iOS14以前では destination 単にViewインスタンスを渡す形でしたが、iOS15以降向けでは ViewBuilder を利用したclosureを引数に取れるようになっています。なので、次のその1の項目で示すサンプルコードにバージョンで違いがあります。

その1: クロージャを利用する方法

サンプルコードは[再開する]といったボタンがある、といったアプリを想定しています。

iOS15は現時点でbetaですので、画像はiOS14での実行キャプチャーです。

iOS15以後向け
struct RootView: View {
  ...
    var body: some View {
        NavigationView {
            // iOS15
            NavigationLink("再開する", destination: {
                if isLoggedIn {
                    MainView()
                } else {
                    LoginView()
                }
            })
            .buttonStyle(.bordered) // これもiOS15以降で利用可能
        }
    }
}
iOS14以前
        // iOS14
        NavigationLink("再開する", destination: {
            VStack { // コンテナUIに入れ解決させる
                if isLoggedIn {
                    MainView()
                } else {
                    LoginView()
                }
            }
        }())

        もしくは

        NavigationLink("再開する", destination: 
            VStack {
                if isLoggedIn {
                    MainView()
                } else {
                    LoginView()
                }
            }
        )

iOS14以前の例では、@ViewBuilder属性があるコンテナUIで囲むことでViewインスタンスが解決されるようにします。

その2: Viewを返すメソッドを作る方法

条件分岐の処理が大きくなって、それをメソッドに括りだしたくなる場合もあると思います。下記のようにnextView()メソッドに処理を括り出せます。
関数には@ViewBuilder属性を付け、戻り値をsome Viewにします。

destinationの取得をnextView()関数に変更
    var body: some View {
        NavigationView {
            NavigationLink("再開する", destination: nextView())
        }
    }

    @ViewBuilder
    private func nextView() -> some View { 
        if isLoggedIn {
            MainView()
        } else {
            LoginView()
        }
    }

ちなみに、次の例のように(先ほどのクロージャの例のメソッド版ですが)コンテナUIで囲むことで some View となるインスタンスを返すこともできます。(書き方としては@ViewBuilderが明示されている先の例の方が良いと思います。)

コンテナ系のUIコンポーネントを用いて下記のようにも書けます
    private func nextView() -> some View {
        VStack { // @ViewBuilderの付加なしで some Viewとなるものを返してくれる
            if isLoggedIn {
                MainView()
            } else {
                LoginView()
            }
        }
    }

余談ですが、Viewプロトコルのvar body: some Viewも、実装時のシグニチャは@ViewBuilder属性が省略されていますので、宣言型UIの記法やViewを用いるメソッドには@ViewBuilderを付加する、ということですね。

また、以下のようにAnyViewを利用することも出来ます。(おすすめされない記法です)

AnyViewを使う例(非推奨)
    private func nextView() -> AnyView { 
        if isLoggedIn {
            return AnyView(MainView())
        } else {
            return AnyView(LoginView())
        }
    }

AnyViewに関しては、WWDC21のセッションにて解説があり、
要旨を抜粋すると、
・一般的には、可能な限りAnyViewを避けることがお勧めです。
・可能であればAnyViewでなく、型情報を保持する為ジェネリックを使用してください。
(理由1)あまりにも多くのAnyViewがあると、コードが読みにくく理解しにくくなります。
(理由2)必要のないときにAnyViewを使用すると、パフォーマンスが低下します。
ーーー
とのこと。確かにAnyViewを利用したくなる時は命令型っぽい記述にもなりがちに思うので、(現在時点の見方では)避ける方法があれば利用しない方がパフォーマンス的に良いようです。

その3: カスタムビューに切り出す方法

最後は、先ほどの例の関数部分を、別のView(DestinationView)として切り出しました。

struct RootView: View {
    ...
    var body: some View {
        NavigationView {
            NavigationLink("再開する", destination: DestinationView(isLoggedIn: isLoggedIn))
        }
    }
}

struct DestinationView: View {

    let isLoggedIn: Bool

    var body: some View {
        if isLoggedIn {
            MainView()
        } else {
            LoginView()
        }
    }
}

このDestinationViewは、NavigationLinkも含めたカスタムビューにするなどの案も考えられます。
WWDC19 Introducing SwiftUIより↓
スクリーンショット 2021-07-18 9.28.44.png

おわりに

NavigationLinkでの例を題材に、様々な書き方と@ViewBuilder周りの利用との考察についてまとめました。

iOSあるあるですが、SwiftUIが発表されて3年目となり、今後業務での採用例も増えてくると思われ、本腰を入れ始めた方も多いと思います。コード例は一長一短があると思いますので、宣言型UIの表現方法を探りつつ、アプリのロジックに合わせてスッキリすると思う方法で書けると良いのではと思います。
自分が試していて、上手くいった例を紹介させていただきました。

4
5
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
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?