この記事では、SwiftUIでのNavigationView画面とTabView画面間の遷移方法について解説します。
ログイン認証機能を実装しているアプリの場合、ユーザー登録やログインなどの認証機能をNavigationViewを利用して関係画面に遷移、ログイン後はTabViewで関係画面を遷移する方法を取ることが多いと思います。
◆動作検証環境
・XCode:12.1
・SwiftUI:2.0
NavigationViewとTabViewの画面遷移の実装
サンプルアプリの仕様
今回サンプルとして作成するアプリの仕様は以下のとおりです。
認証機能に使用する以下3つのビューをNavigationViewで遷移するように作成します。
- ユーザー登録画面
機能:ログイン画面への遷移 - ログイン画面
機能:ユーザー登録画面への遷移
ログイン処理(ホーム画面へ遷移)
パスワードリセット画面への遷移 - パスワードリセット画面
機能:ログイン画面への遷移
ログイン完了後に使用する以下2つのビューはTabViewで遷移するように作成します。
- ホーム画面
- ログイアウト画面
機能:ログアウト処理(ログイン画面(NavigationView)に遷移)
サンプルアプリのコーディング
AppState.swift
1 2 3 4 5 6 7 8 9 10 | import Foundation class AppState: ObservableObject { @Published var isNavigateToLoginView = false @Published var isNavigateToPasswordResetView = false @Published var isLogin = false } |
上記コーディングポイント
5,6行目:
NavigationLinkで仕様するアプリ共通のフラグを定義します。
8行目:
ログイン状態を判断するアプリ共通のフラグを定義します。
SignUpView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | import SwiftUI struct SignUpView: View { @EnvironmentObject var appState: AppState var body: some View { NavigationView{ VStack{ Text("ユーザー登録画面") .padding() Button(action: { self.appState.isNavigateToLoginView = true }) { Text("ログイン画面へ") } NavigationLink(destination: LoginView(), isActive: $appState.isNavigateToLoginView){ EmptyView() } NavigationLink(destination: PasswordResetView(), isActive: $appState.isNavigateToPasswordResetView){ EmptyView() } } } } } struct SignUpView_Previews: PreviewProvider { static var previews: some View { SignUpView() } } |
上記コーディングポイント
9行目:
NavigationViewの親クラス(ログイン画面、パスワードリセット画面の)として定義
LoginView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import SwiftUI struct LoginView: View { @EnvironmentObject var appState: AppState var body: some View { VStack{ Text("ログイン画面") .padding() Button(action: { self.appState.isLogin = true }) { Text("ログインする") .padding() } Button(action: { self.appState.isNavigateToPasswordResetView = true }) { Text("パスワードリセット") } } } } struct LoginView_Previews: PreviewProvider { static var previews: some View { LoginView() } } |
上記コーディングポイント
12行目:
ログイン処理が成功し、ホーム画面に遷移する方法として@EnvironmentObjectで扱うアプリ共通のフラグを利用する。
値が変化(false→true)する事で、後述するRootViewが再描画される形となり、TabView画面へ遷移する。
*ここでNavigationViewを使い、ログイン画面→ホーム画面とすると遷移はするが、その後の動きがおかしくなる(NavigationViewの中にさらにNavigationViewが作られるなど)
PasswordResetView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import SwiftUI struct PasswordResetView: View { @EnvironmentObject var appState: AppState var body: some View { Text("パスワードリセット画面") } } struct PasswordResetView_Previews: PreviewProvider { static var previews: some View { PasswordResetView() } } |
ContentView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import SwiftUI struct ContentView: View { var body: some View { TabView{ HomeView() .tabItem{ Text("HOME") } LogoutView() .tabItem{ Text("LOGOUT") } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } |
TabView設定のクラス
HomeView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import SwiftUI struct HomeView: View { var body: some View { Text("ホーム画面") } } struct HomeView_Previews: PreviewProvider { static var previews: some View { HomeView() } } |
LogoutView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import SwiftUI struct LogoutView: View { @EnvironmentObject var appState: AppState var body: some View { VStack{ Text("ログアウト画面") .padding() Button(action: { self.appState.isLogin = false }) { Text("ログアウトする") .padding() } } } } struct LogoutView_Previews: PreviewProvider { static var previews: some View { LogoutView() } } |
上記コーディングポイント
14行目:
ログイン処理と同様にフラグを変化させることで、ログアウト後の遷移を行う。
*ここでNavigationViewを使うと遷移はするが、その後の動きがおかしくなる(NavigationViewの中にさらにNavigationViewが作られるなど)
RootView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import SwiftUI struct RootView: View { @EnvironmentObject var appState: AppState var body: some View { if appState.isLogin{ ContentView() }else{ SignUpView() } } } struct RootView_Previews: PreviewProvider { static var previews: some View { RootView() } } |
上記コーディングポイント
7〜10行目:
アプリ共通のログイン状態を管理するフラグで、アプリ起動時に遷移させる画面をコントロールする。
条件として使うフラグは、@EnvironmentObjectで扱うアプリ共通となるため、変化する度にif文以下の条件分岐が行われる
アクティブ⇔バックグラウンドの遷移であれば、view表示に使用する@EnvironmentObjectのフラグのデータは保持されますが、アプリを再起動した際はデータが消えます。
そのため、再起動前後でUserDefaultsを利用してデバイスにログイン状態を保持し、次回のアプリ立ち上げ時に備えます。
上記のコード内容では、RootViewが呼ばれる時には、UserDefaultsから@EnvironmentObjectのフラグのデータに値が渡されている必要があるため、NSObjectProtocol を利用する方法などを利用します。
詳しい方法はこちらの記事を参考にしてください。
SwiftUIでButtonなどのアクションを利用せずにUserDefaultsで値を制御する
SceneDelegate.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import UIKit import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? @ObservedObject var appState = AppState() func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let contentView = RootView().environmentObject(appState) if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: contentView) self.window = window window.makeKeyAndVisible() } } ... ... |
上記コーディングポイント
9、13行目
RootView.swiftをウェルカムページにするため、SceneDelegate.swiftを以下のように編集します。
以上、SwiftUIでのNavigationView画面とTabView画面間の遷移方法について解説しました。