【Swift UI】dismissの使い方と注意点!子ビューと親ビュー

この記事からわかること
- Swift UIでnavigationViewの画面遷移をコントロールする方法
- dismissの使い方と注意点
- 画面を戻る際に子ビューだけで無く親ビューまで閉じる原因
index
[open]
\ アプリをリリースしました /
Swift UIでnavigationView
の戻るボタンを実装する際に使用するdismiss
の使い方と注意点をまとめていきます。
またNavigationViewの使い方に関しては 以下の記事を参考にしてください。
dismissとは?
dismissはNavigationView内でNavigationLinkで遷移したビューから戻る処理を実装するために使用できるプロパティです。dismissを翻訳すると「却下」や「解散」などの意味になります。
dismiss自体はロケールやカラースキームなどの環境値を保持するEnvironmentValues
構造体のインスタンスプロパティとして定義されています。
var dismiss: DismissAction { get }
DismissAction構造体とは?
dismiss
プロパティにはDismissAction
構造体が格納されます。つまりdismiss
自体は単なるプロパティ名であり正体はDismissAction構造体ということになります。
DismissAction
構造体はインスタンスかされる際に暗黙的に自身が持つcallAsFunction
メソッドを呼び出し実行します。このメソッドが現在表示されているビューを閉じるメソッドになるようです。
使い方
dismissを使用する方法をみていきます。使用用途は「NavigationLinkで遷移したビューから戻る処理を実装」する際です。
使用する流れ
- 子供側に実装
- 環境変数dismissを使用可能にする
- アクション部分でdismiss()を実行
環境値を保持するEnvironmentValues
構造体の値は@Environment(\.プロパティ名)
で取得できます。
struct ChildView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Button(action: {
dismiss()
}, label: {
Text("戻るボタン")
})
}
}
階層になっている場合のdismiss
ビュー構造が「親ビュー>子ビュー>子ビュー2」のように階層になっている場合でもdismiss
は正常に動作します。この場合はアクティブになっているビューのみが閉じられていきます。
struct ContentView: View {
var body: some View {
NavigationView{
NavigationLink(destination: {
ChildView()
}, label: {
Text("子ビュー")
})
}
}
}
struct ChildView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
VStack{
NavigationLink(destination: {
Child2View()
}, label: {
Text("子ビュー2")
})
Button(action: {
dismiss()
}, label: {
Text("dismissボタン")
})
}
}
}
struct Child2View: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Button(action: {
dismiss()
}, label: {
Text("dismissボタン")
})
}
}
孫Viewから親まで一気に戻る方法
孫Viewから親に一気に戻りたい場合は最適なメソッドはまだ用意されていなさそうでした。無理やり実装するならonDisappear
を使用して孫Viewが閉じられた時に子Viewも閉じることで親Viewまで一気に戻ることが可能です。しかしこの方法だと一瞬子ビューが見えてしまうので最適な方法とは言えなさそうです。
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
ChildView()
} label: {
Text("子")
}
}
}
}
struct ChildView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationLink {
Child2View()
.onDisappear {
dismiss()
}
} label: {
Text("孫")
}
}
}
struct Child2View: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Button {
dismiss()
} label: {
Text("TOPに戻る")
}
}
}
解決策
isDetailLink
を使用することで孫Viewから親まで一気に戻ることができるように実装できるみたいです。ロジックとしては親に@State
で変数を用意しておきその変数を@Binding
で孫まで繋いで、孫からその変数を操作する方法でした。この際に中間のナビゲーションページにはisDetailLink
を付与しないと動作しないようです。
おすすめ記事:Github-SwiftUIで初めの画面に遷移する
データを編集&dismissで戻るが実装できない?
先ほどの階層構造で問題なく動作したdismiss
処理ですが、思うように動作しないこともありました。作成していたのは「リスト画面→詳細画面→編集画面」へと遷移するページで、編集ボタンをクリック後に元データを編集後、詳細画面に戻る流れを期待して作成しています。

構造:親ビュー > 子ビュー1 > 子ビュー2
例 :リスト画面 > 詳細画面 > 編集画面
しかし上記のようなビュー構造の際に子ビュー2側で「更新(dismissを含んだ更新&戻る)ボタン」を実装した場合、子ビュー2でクリックすると親ビュー(リスト画面)まで戻ってしまいます。
デフォルトで実装される「戻るボタン」なら期待通りの遷移をしますが、dismiss
を使用したボタンの場合は期待通りに動きませんでした。以下にコードを載せておきます。
コードの概要
- [構造体]形式のデータをリスト表示
- @ObservedObjectと@EnvironmentObjectでデータを共有
- itemとして配列の要素を子ビューへ渡す
- 子ビュー2(編集ページ)でデータを編集後、更新&dismissで戻る
- 親ページに戻ってしまう?
struct ContentView: View {
@ObservedObject var allFulu = AllFuluLog([FuluLog(productName: "かつおのたたき")])
var body: some View {
NavigationView{
List(allFulu.allData.reversed()){ item in
NavigationLink(destination: { DetailView(item: item).environmentObject(allFulu) }, label: {
Text(item.productName)
})
}
}
}
}
struct DetailView: View {
@EnvironmentObject var allFulu:AllFuluLog
@Environment(\.dismiss) var dismiss
@State var item:FuluLog
var body: some View {
VStack{
Text(item.productName)
NavigationLink(destination: {EditView(item: item).environmentObject(allFulu)}, label: {
Text("編集")
})
}
}
}
struct EditView: View {
@EnvironmentObject var allFulu:AllFuluLog
@Environment(\.dismiss) var dismiss
@State var item:FuluLog
@State var text:String = ""
var body: some View {
VStack{
TextField("テキスト", text: $text)
Button(action: {
let data = FuluLog(productName: text)
allFulu.updateData(data,item.id)
allFulu.setAllData([data])
dismiss()
}, label: {
Text("更新")
})
}
}
}
struct FuluLog: Identifiable,Codable,Equatable {
var id = UUID() // 一意の値
var productName:String // 商品名
}
class AllFuluLog:ObservableObject{
// MARK: -
@Published var allData:[FuluLog] = [] // 全情報
init(_ data:[FuluLog]){
self.setAllData(data)
}
// MARK: - メソッド
func setAllData(_ data:[FuluLog] ){
self.allData = data
}
func updateData(_ item:FuluLog,_ id:UUID){
guard let index = allData.firstIndex(where: { $0.id == id }) else { return }
self.allData[index] = item
}
}
アラートを使用することで解決
「編集ページ」から編集後に「詳細ページ」へ戻るためにアラートを使用することで解決できました。
struct EditView: View {
@EnvironmentObject var allFulu:AllFuluLog
@Environment(\.dismiss) var dismiss
@State var item:FuluLog
@State var text:String = ""
@State var isAlert = false
var body: some View {
VStack{
TextField("テキスト", text: $text)
Button(action: {
let data = FuluLog(productName: text)
allFulu.updateData(data,item.id)
allFulu.setAllData([data])
isAlert = true
}, label: {
Text("更新")
})
.alert(isPresented: $isAlert){
Alert(title:Text("更新しました。"),
message: Text(""),
dismissButton: .default(Text("OK"),
action: {
dismiss()
}))
}
}
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。
私がSwift UI学習に使用した参考書
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。