skip to Main Content

How would you make the .sheet presented above the tab bar wihtout covering it in SwiftUi? I’ve seen a somewhat solution but not one in SwiftUi.
enter image description here

So far anything I try just places the sheet overlaying the tab bar

import SwiftUI

struct ContentView: View {
    @State private var showSheet = true

    var body: some View {
        TabView {
            HomeView(showSheet: $showSheet)
                .tabItem {
                    Label("Home", systemImage: "house")
                }
            Text("Second Tab")
                .tabItem {
                    Label("Second", systemImage: "2.circle")
                }
        }
    }
}

struct HomeView: View {
    @Binding var showSheet: Bool

    var body: some View {
        VStack {
            Button("Show TabView Sheet") {
                showSheet.toggle()
            }
            .sheet(isPresented: $showSheet) {
                SheetContent()
                    .presentationDetents([.medium, .large])
                    .interactiveDismissDisabled()
                    .presentationBackgroundInteraction(.enabled(upThrough: .large))
            }
        }
    }
}

struct SheetContent: View {
    var body: some View {
        VStack {
            Text("First Tab Content")
            Text("More Content in the Sheet")
            // Add more content here as needed
        }
    }
}

#Preview {
    ContentView()
}

2

Answers


  1. Chosen as BEST ANSWER

    Based On Benzy Neez's answer, I also implemented smother animation and snapping points to make it look like a sheet. (Huge thanks to Benzy Neez)

    import SwiftUI
    
    struct ContentView: View {
        @State private var showSheet = true
    
        var body: some View {
            TabView {
                HomeView(showSheet: $showSheet)
                    .tabItem {
                        Label("Home", systemImage: "house")
                    }
                    .toolbarBackground(.visible, for: .tabBar)
                    .toolbarBackground(.background, for: .tabBar)
                Text("Second Tab")
                    .tabItem {
                        Label("Second", systemImage: "2.circle")
                    }
            }
        }
    }
    
    struct HomeView: View {
        @Binding var showSheet: Bool
        
    
        var body: some View {
            VStack {
                Button("Show TabView Sheet") {
                    showSheet.toggle()
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .overlay(alignment: .bottom) {
                if showSheet {
                    SheetContent()
                        .transition(.move(edge: .bottom))
                }
            }
            .animation(.easeInOut, value: showSheet)
        }
    }
    
    struct SheetContent: View {
        let minHeight: CGFloat = 100
        let snapHeight: CGFloat = 150
        let midHeight: CGFloat = 400
        let maxHeight: CGFloat = 700
        @State private var extraHeight = CGFloat.zero
        @State private var dragHeight = CGFloat.zero
    
        init() {
            _extraHeight = State(initialValue: snapHeight - minHeight)
        }
    
        var body: some View {
            VStack {
                Text("First Tab Content")
                Text("More Content in the Sheet")
                
            }
            .frame(maxWidth: .infinity, maxHeight: minHeight + extraHeight)
            .offset(y: -dragHeight / 2)
            .background {
                UnevenRoundedRectangle(cornerRadii: .init(topLeading: 20, topTrailing: 20))
                    .padding(.bottom, -300)
                    .foregroundStyle(.yellow)
                    .offset(y: -dragHeight)
            }
            .overlay(alignment: .top) {
                Capsule()
                    .frame(width: 36, height: 5)
                    .foregroundStyle(.secondary)
                    .padding(5)
                    .offset(y: -dragHeight)
            }
            .animation(.easeInOut, value: extraHeight)
            .animation(.easeInOut, value: dragHeight)
            .gesture(
                DragGesture()
                    .onChanged { val in
                        let dy = -val.translation.height
                        let minDragHeight = minHeight - (minHeight + extraHeight)
                        let maxDragHeight = maxHeight - (minHeight + extraHeight)
                        dragHeight = min(max(dy, minDragHeight), maxDragHeight)
                    }
                    .onEnded { _ in
                        let snapPoints: [CGFloat] = [snapHeight, midHeight, maxHeight]
                        let currentHeight = minHeight + extraHeight + dragHeight
                        let closestSnapPoint = snapPoints.min(by: { abs($0 - currentHeight) < abs($1 - currentHeight) }) ?? snapHeight
                        withAnimation(.easeInOut) {
                            extraHeight = closestSnapPoint - minHeight
                            dragHeight = 0
                        }
                    }
            )
        }
    }
    
    
    
    #Preview {
        ContentView()
    }
    
    

    enter image description here


  2. In your example, the sheet cannot be dismissed interactively, but background interaction has been enabled. So I would suggest, an overlay could be used to replace the sheet:

    // ContentView
    
    HomeView(showSheet: $showSheet)
        .tabItem {
            Label("Home", systemImage: "house")
        }
        .toolbarBackground(.visible, for: .tabBar)
        .toolbarBackground(.background, for: .tabBar)
    
    struct HomeView: View {
        @Binding var showSheet: Bool
    
        var body: some View {
            VStack {
                Button("Show TabView Sheet") {
                    showSheet.toggle()
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .overlay(alignment: .bottom) {
                if showSheet {
                    SheetContent()
                        .transition(.move(edge: .bottom))
                }
            }
            .animation(.easeInOut, value: showSheet)
        }
    }
    
    struct SheetContent: View {
        var body: some View {
            VStack {
                Text("First Tab Content")
                Text("More Content in the Sheet")
                // Add more content here as needed
            }
            .frame(maxWidth: .infinity, minHeight: 200)
            .background {
                UnevenRoundedRectangle(cornerRadii: .init(topLeading: 20, topTrailing: 20))
                    .fill(.yellow)
            }
        }
    }
    

    Animation


    EDIT You were asking in a comment if it would be possible to resize the overlay. You would need to implement this yourself using a drag gesture. Perhaps something like this:

    struct SheetContent: View {
        let minHeight: CGFloat = 200
        let maxHeight: CGFloat = 450
        @State private var extraHeight = CGFloat.zero
        @State private var dragHeight = CGFloat.zero
    
        var body: some View {
            VStack {
                Text("First Tab Content")
                Text("More Content in the Sheet")
                // Add more content here as needed
            }
            .frame(maxWidth: .infinity, maxHeight: minHeight + extraHeight)
            .offset(y: -dragHeight / 2)
            .background {
                UnevenRoundedRectangle(cornerRadii: .init(topLeading: 20, topTrailing: 20))
                    .padding(.bottom, -300)
                    .foregroundStyle(.yellow)
                    .offset(y: -dragHeight)
            }
            .overlay(alignment: .top) {
                Capsule()
                    .frame(width: 36, height: 5)
                    .foregroundStyle(.secondary)
                    .padding(5)
                    .offset(y: -dragHeight)
                    .gesture(
                        DragGesture()
                            .onChanged { val in
                                let dy = -val.translation.height
                                let minDragHeight = minHeight - (minHeight + extraHeight)
                                let maxDragHeight = maxHeight - (minHeight + extraHeight)
                                dragHeight = min(max(dy, minDragHeight), maxDragHeight)
                            }
                            .onEnded { val in
                                extraHeight = extraHeight + dragHeight
                                dragHeight = 0
                            }
                    )
            }
        }
    }
    

    Aniamtion

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search