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

この記事からわかること

  • Swift UInavigationView画面遷移コントロールする方法
  • dismiss使い方注意点
  • 画面を戻る際に子ビューだけで無く親ビューまで閉じる原因

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

Swift UIでnavigationViewの戻るボタンを実装する際に使用するdismissの使い方と注意点をまとめていきます。

またNavigationViewの使い方に関しては 以下の記事を参考にしてください。

dismissとは?

dismissNavigationView内でNavigationLinkで遷移したビューから戻る処理を実装するために使用できるプロパティです。dismissを翻訳すると「却下」や「解散」などの意味になります。

dismiss自体はロケールやカラースキームなどの環境値を保持するEnvironmentValues構造体のインスタンスプロパティとして定義されています。

var dismiss: DismissAction { get }

DismissAction構造体とは?

dismissプロパティにはDismissAction構造体が格納されます。つまりdismiss自体は単なるプロパティ名であり正体はDismissAction構造体ということになります。

DismissAction構造体はインスタンスかされる際に暗黙的に自身が持つcallAsFunctionメソッドを呼び出し実行します。このメソッドが現在表示されているビューを閉じるメソッドになるようです。

使い方

dismissを使用する方法をみていきます。使用用途は「NavigationLinkで遷移したビューから戻る処理を実装」する際です。

使用する流れ

  1. 子供側に実装
  2. 環境変数dismissを使用可能にする
  3. アクション部分で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ボタン")
        })
    }
}

データを編集&dismissで戻るが実装できない?

先ほどの階層構造で問題なく動作したdismiss処理ですが、思うように動作しないこともありました。作成していたのは「リスト画面→詳細画面→編集画面」へと遷移するページで、編集ボタンをクリック後に元データを編集後、詳細画面に戻る流れを期待して作成しています。

リスト画面から詳細画面→編集画面で階層になっている子ビューだけを閉じたい場合
構造:親ビュー > 子ビュー1 > 子ビュー2
例 :リスト画面 > 詳細画面 > 編集画面

しかし上記のようなビュー構造の際に子ビュー2側で「更新(dismissを含んだ更新&戻る)ボタン」を実装した場合、子ビュー2でクリックすると親ビュー(リスト画面)まで戻ってしまいます。

デフォルトで実装される「戻るボタン」なら期待通りの遷移をしますが、dismissを使用したボタンの場合は期待通りに動きませんでした。以下にコードを載せておきます。

コードの概要

  1. [構造体]形式のデータをリスト表示
  2. @ObservedObjectと@EnvironmentObjectでデータを共有
  3. itemとして配列の要素を子ビューへ渡す
  4. 子ビュー2(編集ページ)でデータを編集後、更新&dismissで戻る
  5. 親ページに戻ってしまう?

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学習に使用した参考書

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index