🙌

[SwiftUI]Routerを実装

2021/07/07に公開

SwiftUIで遷移先をまとめるクラスRouterという考え方で実装方法をまとめます。

2種類の遷移方法

画面遷移方法としては主に以下の2つになります。

  • sheetを使ってモーダルで表示
  • NavigationViewNavigationLinkを使ってプッシュ表示

動的にモーダル表示

sheetを使う場合、基本的には1つのViewのみ表示することが多いですが、複数表示したい場合があります。
動的にモーダル表示する際は、以下を使用します。

public func sheet<Item, Content>(item: Binding<Item?>, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping (Item) -> Content) -> some View where Item : Identifiable, Content : View

ちなみに、iOS14以下では.sheetは1つのView要素に1つしか追加できません。

Routerの実装

extension Identifiable where Self: Hashable {
    typealias ID = Self
    var id: Self { self }
}

enum ContentRouter: View, Hashable, Identifiable {
    
    case first
    case second(text: String)
    
    var body: some View {
        switch self {
        case .first: return AnyView(FirstView())
        case .second(let text): return AnyView(SecondView(text: text))
        }
    }
    
}

ContentRouterViewHashableIdentifiableを継承しています。そうすることにより上記のsheetが使えます。
また、enumとすることにより、1つのRouterで複数の画面遷移先を定義することができます。SecondViewについては、値の受け渡しもできるようにしました。

ContentViewの実装

struct ContentView: View {
    
    @State var router: ContentRouter?
    
    var body: some View {
        
        NavigationView {
            VStack(spacing: 16) {
                Button.init("First") {
                    self.router = .first
                }
                Button.init("Second") {
                    self.router = .second(text: "Second")
                }
  
            }
        }.sheet(item: self.$router) { $0 }
        
    }
}

router: ContentRouter?に値を入れることで、.sheetが呼ばれます。さらに、ContentRouterViewを継承しているので、そのまま$0として返却します。

結果

NavigationLinkを使って表示

動的に遷移先を変えることはできそうですが、実装が複雑になってしまうのでNavigationLinkを複数用意することが簡単です。

動的に変更する場合の参考はこちらになります。
https://developer.apple.com/documentation/swiftui/navigationlink/init(_:destination:tag:selection:)-6unyq
https://qiita.com/noppefoxwolf/items/5c952f6d39568b990b8e

上記のContentRouterはそのまま使います。

struct ContentView: View {
    
    @State var router: ContentRouter?
    
    var body: some View {
        
        NavigationView {
            VStack(spacing: 16) {
                Button.init("First") {
                    self.router = .first
                }
                Button.init("Second") {
                    self.router = .second(text: "Second")
                }
  
                NavigationLink("First Navigation", destination: ContentRouter.first)
                NavigationLink(
                    destination: ContentRouter.second(text: "Second from Navi"),
                    label: {
                        Label.init("Second Navigation", systemImage: "moon")
                    })
                
            }
        }.sheet(item: self.$router) { $0 }
        
    }
}

NavigationLinkdestinationとして、ContentRouter.firstなどとしました。ContentRouterとして遷移先を定義しているので、実装がスッキリしています。

結果

サンプル

今回作ったサンプルはこちらです。
https://github.com/usk-sample/swiftui-router-sample

参考

https://developer.apple.com/documentation/swiftui/view/sheet(item:ondismiss:content:)
https://developer.apple.com/documentation/swiftui/navigationlink
https://qiita.com/noppefoxwolf/items/5c952f6d39568b990b8e

Discussion