モーダルウインドウを出したい【Swiftui】

2022-04-06 10:20 PM

今回、アプリケーションを作成するのにあたって、Xcodeのバージョンが2022年4月5日の時点で13になっている。同時に、Macosのバージョンも12(monterey)となり自分の自宅のMacもiMac、そしてM1チップ搭載のPCになっていることから、いろいろと調べなおさいないといけない状況にある。

このような環境下で動作してくれるアプリケーションの開発を。ということで、SwiftuiもiPhone・iPadに近づいているのであろうと考えられるのでとりあえずUIを作成してみた。

ところが、ここで起きた問題が、いくつかの項目があって、その中から一つ、もしくは複数を選択してデータとして保管する部分があるのだが、項目が多いのであれば、単純にチェックボックスを表示するのは得策ではない。

そこで、別のウインドウの表示してリストを表示。そこから一つ、もしくは複数の項目を選択して親ウインドウの項目の中に代入させる。そのようなものを考えていたのだけども、この「別のウインドウ」の表示が自分には難しいものだった。

何せ、ネット上の情報のほとんどがiPhoneに向けた説明ばかりなので、Macosに対しての説明が少ない。

さて今回はそのモーダルウインドウが成功したので記録を。

最終的にこのような貧相なウインドウになってしまったのだけど、モーダルウインドウさえ成功すればいいので、問題はない。

「選んでください」となっているボタンを押すとモーダルウインドウが表示されるというもの。

早速「ContentView.swift」の編集で上記の画像のようなものを作成した。

今回はリストを表示してそこから選択させるという方法を使う。


Button(action: {
  // ボタンを押した時のアクション
}) {
  // ボタンに表示する文字
  Text("選んでください")
}

これが一番の問題になる。

あともう一つ。肝心のモーダルウインドウの内容を作成しなくてはいけない。そこで、「modal.swift」というファイルを作成。

「macOS」→「SwiftUI View」を選択して作成します。


@Environment(\.presentationMode) var presentationMode

モーダルウインドウを表示するかどうかの制御を行う変数になるようです。


@Binding var selectedItem_title:String?
@Binding var selectedItem_list: [String]?
@Binding var selectedItem_value:String?

「selectedItem_title」はウインドウのタイトルに使います。
「selectedItem_list」はリスト表示するため、配列変数にしています。
「selectedItem_value」最終的に選択された文字列がここに入ります。

この3つはBindingというタイプで、どうやらデータを参照させるための宣言方法のようです。

親ウインドウで宣言した変数と連動していると考えていいかもしれません。


var body: some View {
  VStack {
    Text(selectedItem_title ?? "")
      .font(.title)
      .frame(maxWidth: .infinity, alignment: .leading) // end Text
    Spacer()
    List {
      if selectedItem_list != nil {
        ForEach(0..<(selectedItem_list?.count)!) { num in
          HStack {
            Text(selectedItem_list![num])
              .font(.footnote)
              .fontWeight(.regular)
              .truncationMode(.middle)
              .frame(maxWidth: .infinity, alignment: .leading) // end Text
            Spacer()
            Button(action: {
              selectedItem_value = selectedItem_list![num]
              self.presentationMode.wrappedValue.dismiss()
            }) {
              Text("選択")
            } // end Button
          } // end HStack
        } // end ForEach
      } // end if
    } // end List
    Spacer()
    HStack {
      Button(action: {
        selectedItem_value = ""
        self.presentationMode.wrappedValue.dismiss()
      }) {
        Text("閉じる")
      } // end Button
    } // end HStack
  }
  .padding(16.0)
  .frame(width: 240, height: 400) // end VStack
}

成功したプログラムはこんな感じになりました。

「Text(selectedItem_title ?? “”)」はBindingで宣言した変数は値が「nil」になっている可能性もありうる。そんなわけで、変数が空だったら何を表示するのか、ということをやっています。

Listの中も同じような感じで「nil」だったら何も表示せず、それ以外なら、ForEachで配列変数の内容を表示していきます。

ただ、このListそのものは選択することができないので、一応、一つの項目に対してボタンを設置していこうと考えました。

その部分のコードを抜粋します。


Button(action: {
  selectedItem_value = selectedItem_list![num]
  self.presentationMode.wrappedValue.dismiss()
}) {
  Text("選択")
}

こんな感じで、ボタンが押されたら、文字列を親ウインドウに返すための処理。「selectedItem_value」に選択されたボタンに対応した文字列を代入してから、ウインドウを閉じます。


Button(action: {
  selectedItem_value = ""
  self.presentationMode.wrappedValue.dismiss()
}) {
  Text("閉じる")
}

最後の部分のこのボタンは単純にウインドウを閉じるためのものなので、データを返す時は空にしてから閉じます。

結果…

こういったリストを表示させることができました。

さらに、

こんな感じで閉じたときにコンソールに、ウインドウを閉じた、もしくはデータを選択してウインドウを閉じたときに親ウインドウが子ウインドウから選択したした文字を受け取り、それを出力することに成功しました。

しかし、実際に現在開発しているアプリケーションに適用させてみると、そううまくはいかない。

一つのフォームの中でこういった選択させることが多いので、これをテンプレートのように、リストのデータを渡して選択させるということを複数回繰り返そうとしたら、エラーが発生。

調べてみると、描画を司るプログラムがありまして、そのプログラムが複数回同じことを繰り返すなら、変数を飛ばして表示するよ。というメッセージだったらしく…ここで氷翠の目論見は崩れてしまったわけです。

ということで、何らかの方法はあるかもしれないのだけども、現在はそこで足踏みをしている余裕はないので、一つずつにモーダルウインドウのViewを用意してせっせと次の工程に進んでいきます。