SwiftUIによるsheet + fullScreen (multiple sheet)
経緯
iOS14.4以下の場合multiple sheetに対応していない事象が発生しているが
解消方として、指定Itemにより表示するViewModifierを複数指定したり
PJにより対応方法は様々存在している。
が、Sheet + fullScreenCoverを組み合わせて使用している記事があまりなかった。
実施
カスタムModifier作成
extension View {
func sheets<Content: View>(
isPresented: Binding<Bool>,
isFullScreenPresented: Binding<Bool>,
onDismiss: @escaping () -> Void,
@ViewBuilder content: @escaping () -> Content
) -> some View {
self.modifier(
ViewModifier(
isPresented: isPresented,
isFullScreenPresented: isFullScreenPresented,
onDismiss: onDismiss,
builder: content
)
)
}
}
struct ViewModifier<V: View>: ViewModifier {
let isPresented: Binding<Bool>
let isFullScreenPresented: Binding<Bool>
let onDismiss: () -> Void
let builder: () -> V
@ViewBuilder
func body(content: Content) -> some View {
if #available(iOS 15.0, *) {
content
.sheet(isPresented: isPresented, onDismiss: onDismiss, content: builder)
.fullScreenCover(isPresented: isFullScreenPresented, content: builder)
} else {
Group {
ZStack {
content
.sheet(isPresented: isPresented, onDismiss: onDismiss, content: builder)
EmptyView()
.fullScreenCover(isPresented: isFullScreenPresented, content: builder)
}
}
}
}
}
使用
struct SampleView: View {
enum Present {
case sheet
case fullScren
}
@State var isPresented = false
@State var isFullScreenPresented = false
@State var present: Present = .sheet
var body: some View {
VStack {
Button("showSheet") {
present = .sheet
isPresented.toggle()
}
Button("showFullScreen") {
present = .fullScren
isFullScreenPresented.toggle()
}
}
.sheets(
isPresented: $isPresented,
isFullScreenPresented: $isFullScreenPresented,
onDismiss: {
/// ...
}
) {
switch present {
case .sheet:
/// ...
case .fullScren:
/// ...
}
}
}
}
詳細
.sheet + .fullScreenCoverをViewBuilderでラップしたカスタムModifierの作成
self.modifier(
ViewModifier(
isPresented: isPresented,
isFullScreenPresented: isFullScreenPresented,
onDismiss: onDismiss,
builder: content
)
)
@ViewBuilder
func body(content: Content) -> some View {
if #available(iOS 15.0, *) {
content
.sheet(isPresented: isPresented, onDismiss: onDismiss, content: builder)
.fullScreenCover(isPresented: isFullScreenPresented, content: builder)
} else {
Group {
ZStack {
content
.sheet(isPresented: isPresented, onDismiss: onDismiss, content: builder)
EmptyView()
.fullScreenCover(isPresented: isFullScreenPresented, content: builder)
}
}
}
}
使用する際の注意点として
シート内容を返却するクロージャはisPresented
、isFullScreenPresented
のどちらのBinding時にも返却されるため
意図的にViewClassにてStatesを使用し判定する必要がある(もう少しいい方法がありそうだが、肥大化する部分でもないので一時的にこちらを採用)
又、contentに対してsheet
、fullScreenCover
を設定してしまうと、iOS14.4未満での仕様上、後述したViewBuilderが後ガチしてしまうため、EmptyView
に対して設定を行う
※iOS15以降の場合はEmptyView
に対してViewBuilderを指定するとイベントが発火されない事象が発生するためiOS15.0以前のバージョンと切り分ける必要がある
enum Present {
case sheet
case fullScren
}
参考
https://developer.apple.com/documentation/swiftui/viewmodifier
https://developer.apple.com/documentation/swiftui/view/sheet(ispresented:ondismiss:content:)
https://zenn.dev/yorifuji/articles/swiftui-multiple-sheet
Discussion