SwiftUIのモーダルの表示・非表示
※ iOS16でのやり方です
モーダルの非表示
説明の都合上、先に非表示(モーダルを閉じるとき)についてです。
モーダルを閉じる場合はこのようにEnvironmentのdismissを使って画面を閉じます。
// モーダルで表示する画面 struct ModalContents: View { @Environment(\.dismiss) private var dismiss var body: some View { Button("Done") { dismiss() } } }
このコードを最初見たときに違和感感じませんでしたか?わたしは感じました。
dismissってぱっと見valueっぽいですがdismiss()で呼んでいるのでclosureなのでしょうか。
dismissの正体
EnvironmentValuesにdismissは定義してあり、型は DismissAction
でした
public var dismiss: DismissAction { get }
DismissAction
はなにかというとstructです。
struct DismissAction
https://developer.apple.com/documentation/swiftui/dismissaction
structなのになぜ関数のように呼ぶことができるかというとcallAsFunction()が定義されているからです。 https://developer.apple.com/documentation/swiftui/dismissaction/callasfunction()
つまり、dismiss()とかくと暗黙的にdismissのcallAsFunction()を呼んでいることになるのです。
struct ModalContents: View { @Environment(\.dismiss) private var dismiss var body: some View { Button("Done") { dismiss() // Implicitly calls dismiss.callAsFunction() } } }
DismissActionのcallAsFunction()内で画面を閉じる処理が実行されていると考えられます。
モーダルの表示
モーダルを画面を開く場合は、 完全に全画面の場合fullScreenCover, 完全に全画面ではなく上にちょっと浮いた感じにする場合はsheetをを使います。
https://developer.apple.com/documentation/swiftui/view/fullscreencover(ispresented:ondismiss:content:) https://developer.apple.com/documentation/swiftui/view/sheet(ispresented:ondismiss:content:)
struct ContentView: View { @State private var isFullScreenCoverViewPresented = false var body: some View { NavigationStack { Group { Button { isFullScreenCoverViewPresented = true } label: { Text("+") } } .fullScreenCover(isPresented: $isFullScreenCoverViewPresented) { ModalContents() } } }
モーダルで開いた画面のナビゲーションバーに閉じるボタンをつける
モーダルで表示する画面側をNavigationStackでwrapしてToolbarItemを追加します。
struct ModalContents: View { @Environment(\.dismiss) private var dismiss var body: some View { NavigationStack { Group { } .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button { dismiss() } label: { Label("Close", systemImage: "xmark") } } } } } }
同じような画面が何個もある場合はこういったContainerViewを用意しておくと ModalView { ModalContents() }
とするだけで閉じるボタンが追加できるので便利かもしれません。
struct ModalView<Content: View>: View { @Environment(\.dismiss) var dismiss var content: () -> Content var body: some View { NavigationStack { content() .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button { dismiss() } label: { Label("Close", systemImage: "xmark") } } } } } }