下図のように 画像を選択 → 画像の切り出し(クロッピング) と複数の画面を連続してモーダル表示させつつ遷移させたい。
モーダルA → モーダルB と連続して遷移できるが、モーダルBがなぜか閉じれなくなってしまう。
実行環境
- Xcode 11.6
- iOS 13.6
問題の挙動
モーダルA → モーダルB と連続させたい。しかし実装するとモーダルBが閉じれなくなってしまう不具合が発生する。
import SwiftUI struct SampleTwoModalView : View { enum SheetType { case imagePick case imageCrop } @State private var currentSheet: SheetType = .imagePick @State private var sheetIsPresented = false var body: some View { VStack { Button(action: { self.currentSheet = .imagePick self.sheetIsPresented = true }, label: { Text("select images & crop images") }) } .sheet(isPresented: $sheetIsPresented) { if (self.currentSheet == .imagePick) { ImagePickerView(sourceType: .photoLibrary, onCanceled: { // on cancel }) { (image) in //画像を選択するとクロッピング画面へ遷移させる DispatchQueue.main.async { self.currentSheet = .imageCrop self.sheetIsPresented = true } } } else if (self.currentSheet == .imageCrop) { ImageCropView(originalImage: UIImage(), onCanceled: { // on cancel }) { (image) in // on success } } } } }
連続してモーダル表示させたい場合にはどうすればよいのか?
解決編
モーダルAを閉じたあと、1秒待ってからモーダルBを表示する。すると、モーダルBが閉じられるようになる。
import SwiftUI struct SampleTwoModalView : View { enum SheetType { case imagePick case imageCrop } @State private var currentSheet: SheetType = .imagePick @State private var sheetIsPresented = false @State private var image: UIImage? = nil var body: some View { VStack { Button(action: { self.currentSheet = .imagePick self.sheetIsPresented = true }, label: { Text("select images & crop images") }) } .sheet(isPresented: $sheetIsPresented) { if (self.currentSheet == .imagePick) { ImagePickerView(sourceType: .photoLibrary, onCanceled: { // on cancel }) { (image) in self.image = image //画像を選択するとクロッピング画面へ遷移させる DispatchQueue.main.asyncAfter(wallDeadline: .now() + .milliseconds(1000)) { self.currentSheet = .imageCrop self.sheetIsPresented = true } } } else if (self.currentSheet == .imageCrop) { ImageCropView(originalImage: self.image!, onCanceled: { // on cancel }) { (image) in // on success } } } } }
ただし、正しい正解とは思えないので、他に対応方法があれば教えて欲しい。
(2020/07/29追記) 解決編その2
dismissアニメーション中に次の画面をモーダル表示させることで、SwiftUI内部のステータス管理が不正な状態になってしまうのが原因ではないかと考えています。今後のSwiftUIの修正に期待したい部分です。またSwiftUI側ではdismissが完了したタイミングを取得できないため、dismissが終了してすぐに次の画面へ遷移できません。
私の結論としては、Xcode11.6時点ではUIImagePickerControllerなどのUIKitを使っている以上、SwiftUIで連続してスムーズにモーダル画面を表示するのは難しいので、UIKit側でdismissのタイミングを取る、でした。
本質部分ではないので、ここでは該当部分のみを転記したいと思います。イメージとしては以下の通りです。
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { guard let image = info[.originalImage] as? UIImage else { picker.dismiss(animated: true) { self.onImagePicked(nil) } return } // UIKit側で閉じるのを待ってからSwiftUI側に画像を渡す picker.dismiss(animated: true) { self.onImagePicked(image) } }
ソースコードは下記のリポジトリで参照可能です。
参考記事
ひとつのViewから複数の画面をモーダルで表示させる方法は、以下の記事に書いた。