macOSアプリでSwiftUIによるモーダルシートを表示する

Swift
MacOSアプリでSwiftUIによるモーダルシートを表示する

はじめに

Vaporアプリケーション経由でMongoDBに保存された本の情報をListViewに表示させることができました。

表示させただけでは能がないので、本の削除のためのモーダルシートを表示させてみます。

iOS向けでSwiftUIを使ったSheetの表示をしたことがあるので、これの応用になると思います。基礎を押さえておくと応用は(とりあえず)いかようにも利きますね。

準備

前提として

ここまでできたところからのスタートです。

方針

macOS向けのアプリケーション開発初心者が着手前に考えた方針になります。進めていく上で軌道修正かけるかもしれないです。

  • モーダルシートにするViewを作る
  • モーダルシートを表示させたり閉じたりできるようにする
  • モーダルシートにデータを渡す
  • モーダルシートにデータが表示されるのを確認する

モーダルシートが表示できるようにする

モーダルシートに表示させるViewを作る

まずはモーダルシートを作ってみます。
今回作りたいのは、本をListViewから選択して、これを削除してよいかどうかの確認ダイアログです。これを踏まえて、データを表示する枠組みを作っていきます。

@Binding var selectedItem:Book?

var body: some View {
    VStack {
        Text("Delete Book")
            .font(.title)
            .frame(maxWidth: .infinity, alignment: .leading)
        Spacer()
        Text(String(selectedItem?.title ?? ""))
                .font(.body)
                .fontWeight(.bold)
                .opacity(1.0)
                .truncationMode(.tail)
                .frame(maxWidth: .infinity, alignment: .leading)
        HStack(alignment: .center) {
            Text(String(selectedItem?.publisher ?? ""))
                .font(.footnote)
                .fontWeight(.regular)
                .opacity(0.75)
                .truncationMode(.middle)
                .frame(maxWidth: .infinity, alignment: .leading)
        }
        Spacer()
        HStack {
            Button(action: {
            }) {
                Text("Delete")
            }
            Button(action: {
            }) {
                Text("Close")
            }
        }
    }
    .padding(16.0)
    .frame(width: 240, height: 160)
}

すでにチラッと出ていますが、モーダルシートの呼び出し元からモーダルシートにデータを渡す時に使うプロパティはこれです。

@Binding var selectedItem:Book?

モーダルシートがデータを管理するのではなく、呼び出し元で管理されるものを共有してもらう形なので、Binding属性です。

プレビューでこのような感じになっています。

表示させようとしているモーダルシートのデザイン

モーダルシートを表示させてみる

モーダルシートのViewができましたので、早速これを表示させてみます。ちなみにモーダルシートのViewの名前はConfirmDeleteにしています。

ボタンをクリックするとダイアログが出るようにします。

@State private var showingSheet = false

Button(action: {
    self.showingSheet.toggle()
}) {
    Text("Delete")
}.sheet(isPresented: self.$showingSheet, onDismiss: {print("シート閉じた")}) {
    ConfirmDelete(selectedItem: self.$selectedBook)
}

これで表示することができます。クリックしたボタンが見えませんが、シートの裏に隠れてしまっています。

まずは単純に表示させてみる

モーダルシートを閉じてみる

このままでは開いたら閉じることができません。閉じることができるようにします。

モーダルシート上のCloseボタンをクリックした時のアクションを追加しておきます。

@Environment(\.presentationMode) var presentationMode

Button(action: {
    self.presentationMode.wrappedValue.dismiss()
}) {
    Text("Close")
}

参考までに

デリゲートでモーダルシートの呼出し元に閉じさせるという手もあるかと思います。閉じるまでは開いた者の責任という考え方です。家に帰り着くまでが遠足というのと似ています。えっ?似てない?

モーダルシートからデータを返す時にも、データの処理を呼び出し元に全部任せる事ができる(データ一式を丸投げできる)ので便利です。

本が選択されていない時はモーダルシートを開かない

削除するかどうかの確認ダイアログという位置づけのため、ListViewで本が選択されていなければモーダルシート自体を表示させる必要がありません。選択されていない旨のダイアログを出すアプローチもありますが、今回は単純に表示させない方法を取ります。でも、ダイアログを出したほうが使う人にとっては親切です。

Button(action: {
    if let _ = self.selectedBook
    {
        self.showingSheet.toggle()
    }
}) {
    Text("Delete")
}.sheet(isPresented: self.$showingSheet, onDismiss: {print("シート閉じた")}) {
    ConfirmDelete(selectedItem: self.$selectedBook)
}

ボタンをクリックした時にListViewのセルが選択されているかどうかで判断し、モーダルシートの表示フラグを更新しないようにしています。

確認してみる

ListViewに本の一覧を表示して、モーダルシートを表示させてみます。

選択した本の情報とともにモーダルシートを表示する

Closeをクリックするとこの通り。

Closeボタンをクリックしてモーダルシートを閉じる

もちろん、ListViewで本が選択されていなければ、モーダルシートは表示されません。

まとめ

iOSと同じ方法でmacOSでもモーダルシートを表示させることができました。気になるところがあるとすると、モーダルシートの表示非表示をフラグで制御していることです。モディファイアの都合上、致し方ない部分なんですかね……

気になるDeleteボタン

モーダルシートにはボタンが2つあります。今回はCloseボタンのみに処理を実装し、もう一つのDeleteボタンには何も記述しませんでした。

目標としては、Deleteボタンをクリックすると、ListViewで選択した本をMongoDBから削除したいのです。これを実現するにはモーダルシートを閉じた時に、どのボタンがクリックされたかを判定しないといけません。デリゲートの出番ですかね。