はじめに
SwiftUI で NavigationView
の content
で NavigationLink
による画面遷移をしている場合に特定の何かをした場合に
トップに戻したいという時があったとしたときにどう戻るんだっていうので実際に実装してみたことについてです.
↑今回の想定実装です.
前提
今回もサンプルアプリを BitBucket に上げてます.
SafeArea を無視する API を iOS13.0+ でも実行できるものに変更したら実行できるはずです.
UIKit だと?
UIKit だと UINavigationController
に popToRootViewController(animated:)
がありますよね.
これを実行すると popToRootViewController(animated:)
で遷移している場合,1番初めの遷移元まで戻れます.これを SwiftUI で実現します.
単純に NavigationLink で実装をしてみる
とりあえず今回は,Life Cycle を SwiftUI App
にしてやっています.
プロジェクト名は Sample
です.
起動時の画面 | 2つ目の画面 | 3つ目の画面 |
---|---|---|
SampleApp.swift
import SwiftUI @main struct SampleApp: App { var body: some Scene { WindowGroup { NavigationView { ContentView() } } } }
NavigationView
に入れてないと何も出来ないので
ContentView.swift
import SwiftUI struct ContentView: View { var body: some View { VStack { Spacer() Text("This is 1st view") .font(.title) .fontWeight(.black) Spacer() NavigationLink( destination: SecondContentView(), label: { Text("Go to 2nd view") .foregroundColor(.white) .fontWeight(.medium) .padding(.horizontal, 60) .frame(height: 50) .background(Color.black) .cornerRadius(25) }) } .navigationBarHidden(true) } }
SecondContentView.swift
struct SecondContentView: View { var body: some View { ZStack { Color.orange .ignoresSafeArea() VStack { Spacer() Text("This is 2nd View") .font(.title) .fontWeight(.black) .foregroundColor(.white) Spacer() NavigationLink( destination: ThirdContentView(), label: { Text("Go to 3rd view") .foregroundColor(.orange) .fontWeight(.medium) .padding(.horizontal, 60) .frame(height: 50) .background(Color.white) .cornerRadius(25) }) } } .navigationBarHidden(true) } }
ThirdContentView.swift
struct ThirdContentView: View { var body: some View { ZStack { Color.purple .ignoresSafeArea() VStack { Spacer() Text("This is 3rd view") .font(.title) .fontWeight(.black) .foregroundColor(.white) Spacer() Button(action: { // TODO: popToRoot }, label: { Text("Back to top") .foregroundColor(.purple) }) .padding(.horizontal, 60) .frame(height: 50) .background(Color.white) .cornerRadius(25) } } .navigationBarHidden(true) } }
とりあえず3つはこんな感じで実装してみました.
どうやって popToRoot を実現するか
簡単に書くと ContentView
で
@State var isActive = false
を持たせておいて, NavigationLink
の引数の1つである isActive
にバインドするという方法です.
ContentView
の画面遷移の実装は以下ように変化します.
NavigationLink( destination: SecondContentView(), isActive: $isActive, label: { EmptyView() }) Button( action: { isActive = true }, label: { Text("Go to 2nd view") .foregroundColor(.white) .fontWeight(.medium) .padding(.horizontal, 60) .frame(height: 50) .background(Color.black) .cornerRadius(25) })
NavigationLink の label
については,そのまま Button を設置しても良いかなと思ったのですが, ネスト深くなりすぎるのあんまり好きじゃないので EmptyView
を変わりにおいています.
SecondContentView
の画面遷移に関しては,今の実装のままでいけます.
ただ,プロパティに
@Binding var isActive: Bool
を追加します. ContentView
の SecondContentView
の実装の部分にも若干変更が生じます.
SecondContentView(isActive: $isActive)
また, ThirdContentView
のプロパティにも SecondContentView と同様に isActive
を追加します. Init の実装も変更されるので上記と同じように変更してください.
あとは ThirdContentView
の TODO の部分を変更するだけです!!
Button( action: { isActive = false }, label: { Text("Back to top") .foregroundColor(.purple) })
これで冒頭で埋め込んだ動画のような動きができるようになったはずです!
最後に
今回は SwiftUI で UIKit の popToRootViewController
の機能を実現する方法を書きました.
ここらへんの機能を Router とか使って実現したいんですけどまだ SwiftUI は人類には早すぎる雰囲気を感じています...