📖
[SwiftUI] Routeで管理する
背景
NavigationLinkで画面遷移をすると、
- 画面ごとに遷移状態のフラグを持たなければならない
- 毎回NavigationLinkを書かないといけない
のが非常に面倒だったので、 WebとかFlutterでもあるRoutingをEnvironmentObjectを使って実装しました。
実装
1. Routeを管理するクラスを用意
import Foundation
// Routeを管理するクラスを用意
class RouteState: ObservableObject {
@Published var appRoute: Route = .splash
@Published var routeHistory: [Route] = [.splash]
func moveToView(route: Route) {
routeHistory.append(appRoute)
appRoute = route
}
func moveToBack() {
appRoute = routeHistory.last ?? .splash
routeHistory.removeLast()
}
func popUntil(route: Route) {
let untilIndex = routeHistory.firstIndex(of: route) ?? 1
appRoute = routeHistory[untilIndex]
let lastIndex = routeHistory.endIndex
routeHistory.removeSubrange(untilIndex..<lastIndex)
}
}
2. EnvironmentObjectを紐付け
import SwiftUI
@main
struct RoutingSampleApp: App {
var body: some Scene {
WindowGroup {
RouteView()
.environmentObject(RouteState())
}
}
}
3. RouteViewでRouteを定義
import SwiftUI
enum Route {
case splash
case login
case home
}
struct RouteView: View {
@EnvironmentObject var routeState: RouteState
var body: some View {
switch routeState.appRoute {
case .splash:
Button(action: {
routeState.moveToView(route: .login)
}, label: {
Text("ログイン画面へ")
})
case .login:
Button(action: {
routeState.moveToView(route: .home)
}, label: {
Text("ホーム画面へ")
})
case .home:
VStack {
Button(action: {
routeState.moveToBack()
}, label: {
Text("ログイン画面へ")
})
Button(action: {
routeState.popUntil(route: .splash)
}, label: {
Text("スプラッシュ画面へ")
})
}
}
}
}
メリット
- NavigationLinkを各画面で定義しなくてよくなる
- 画面遷移時はrouteStateのappRouteを変更するだけ
- SpinnerやAlertなどをRouteViewに定義して使いまわせる
- SpinnerStateやAlertStateなどを定義して、RouteViewに.sheetなどを定義する
デメリット
- 画面遷移の遷移方法を設定できない(pushやmodalなど)
- RouteViewで表示を切り替えているだけなので、今のままだとできない(RouteViewでNavigationLinkと組み合わせればできるかも・・)
- 画面の値渡しがEnvironmentObjectを使わないといけない(もっといいやり方ありそう)
- 画面の値渡し用の環境変数を用意して、前画面で代入、次画面で環境変数を参照という感じ
全体コード
Discussion