I have custom tab bar with two screen and I created a modifier to display custom sheet from bottom.
I don't want to use .sheet
cause I can't change cornerRadius and also I want my sheet to be self-sized (dynamique height).
MainView:
struct MainView: View {
var body: some View {
ZStack(alignment: .bottom) {
TabView(selection: $currentTab) {
Screen1()
.tag(Tab.screen1)
Screen2()
.tag(Tab.screen2)
}
.tabViewStyle(DefaultTabViewStyle())
HStack {
TabItemView(currentTab: $currentTab, tab: .screen1)
TabItemView(currentTab: $currentTab, tab: .screen2)
}
}
}
struct TabItemView: View {
@Binding var currentTab: Tab
var tab: Tab
var body: some View {
Button {
currentTab = tab
} label: {
VStack {
Image(tab.image)
.resizable()
.frame(width: 30, height: 30)
.frame(maxWidth: .infinity)
Text(tab.rawValue)
}.padding(.vertical, 10)
}
}
}
Custom BottomSheet :
struct BottomSheet<Content: View>: View {
@Binding var isPresented: Bool
let onDismiss: (() -> Void)?
let content: Content
init(isPresented: Binding<Bool>, onDismiss: (() -> Void)? = nil, content: @escaping () -> Content) {
self._isPresented = isPresented
self.onDismiss = onDismiss
self.content = content()
}
var isiPad: Bool {
UIDevice.current.userInterfaceIdiom == .pad
}
public var body: some View {
ZStack {
if isPresented {
Color.black.opacity(0.3)
.edgesIgnoringSafeArea(.all)
.transition(.opacity)
.zIndex(1)
.onTapGesture {
dismiss()
}
container
.ignoresSafeArea(.container, edges: .bottom)
.transition(isiPad ? AnyTransition.opacity.combined(with: .offset(x: 0, y: 200)) : .move(edge: .bottom))
.zIndex(2)
}
}.animation(.spring(response: 0.35, dampingFraction: 1), value: isPresented)
}
private var container: some View {
VStack {
Spacer()
if isiPad {
card.aspectRatio(1.0, contentMode: .fit)
Spacer()
} else {
card
}
}
}
private var card: some View {
VStack(alignment: .trailing, spacing: 0) {
content
}
.background(Color.background)
.clipShape(RoundedRectangle(cornerRadius: .radius_xl))
}
func dismiss() {
withAnimation {
isPresented = false
}
onDismiss?()
}
}
extension View {
func bottomSheet<Content: View>(isPresented: Binding<Bool>, onDismiss: (() -> Void)? = nil, backgroundColor: Color = Color(.systemGray6), @ViewBuilder content: @escaping () -> Content) -> some View {
ZStack {
self
BottomSheet(isPresented: isPresented, onDismiss: onDismiss) { content() }
}
}
}
Screen 1:
struct Screen1: View {
...
@State var isPresented = false
...
var body: some View {
Button(action: {
isPresented = true
}, label: {
HStack {
Spacer()
Image(systemName: "plus")
Text("Add")
Spacer()
}
})
.bottomSheet(isPresented: $isPresented) {
PopupView(isPresented: $isPresented, message: "") {
//Action when user confirm
}
}
}
}
The problem is bottom sheet is presented behind my tab bar, when I move my .bottomSheet
modifier to MainView it's working perfectly but in my case I want to use .bottomSheet
in child views cause I have actions to do when user tap confirm or cancel button.
PS: I want to mimic .sheet
behavior, always presenting view on top of root