List(SwiftUI)のセルタップでモーダルシートを表示する

お題

今日もSwiftUIに丸腰で挑んでいます。間違いには屈しない。

Listの各セルにButtonを表示して、Listのセルをタップすればなにかする、というパターン

List {
	ForEach(要素一式, id: ) { 要素 in
		Button(action: {
			タップされたらすること
			モーダルシートが表示されているかどうかのフラグ切り替え
		}) {
			Buttonに表示するもの
			}
		}
	}
}

さて、タップされたときにモーダルシートを表示させたいとき、

.sheet(isPresented: フラグ) {
	表示するView
}

はどこへつけるべきか?

付ける位置としては

  • Button
  • ForEachの外側
  • List

の3択。

有力なのは1つ目のButton。各セルに対し、ForEachでButtonを生成しているので、モーダルシートを表示する処理も一緒に書くのは自然な感じがします。

実験

まずは素直に書いてみてシミュレーションで実行してみます。

セルをタップしたならば、

Warning: Attempt to present <なんたかかんたら>  on <ほにゃらら> which is already presenting <うんぬん>

という警告が数十回発生しました。

適当な推測ですが、セルをタップしたときにすべてのセルでモーダルシートを表示する処理が走っているのではないでしょうか。

一回モーダルを開いているのに更に開こうとして、「もう表示してまっせ」となっているようです。「モーダルシートが表示されているかどうかのフラグ」が切り替えられたという情報が一斉に各Buttonに及んでいるように見えます。

Listの方へ付け替えて再度実行してみると、今度は警告が出ませんでした。

まとめ

単体でButtonを生成するときはモーダルシートを表示する処理をButtonにつければいいと思います。Listのセル要素としてButtonを使うときは、Listの方につけたほうがいい場合がありそうです。(設計・実装次第)

もしくは、Listの各セルを一つのViewとして切り出して、「モーダルシートが表示されているかどうかのフラグ」を各セルそれぞれに持たせるかですかね。