# SwiftUI Bottom Sheet: How to Hide Unwanted UI Components

This blog post will explain alternatives for programmatically determining a presented sheet's detent in SwiftUI.

To present a bottom sheet on iOS is very easy with SwiftUI thanks to the `sheet` and `presentationDetents` modifiers.

> To **turn a normal sheet into a bottom sheet**, we only need to **define supported detents** with `.presentationDetents([.medium, .large])`.

%[https://sarunw.com/posts/swiftui-bottom-sheet/] 

At work I was asked if it is possible to programmatically determine the presented sheet's detent and react to its changes. A possible use case might be to show UI controls (i.e., a large icon) on a large sheet but hide the controls on a bottom sheet.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717885465427/eedf6ab9-0bff-4d74-952c-027e6e59de10.gif align="center")

[Curt Clifton](https://fosstodon.org/@curtclifton@indieweb.social) pointed out to me that SwiftUI offers a version of the modifier that takes a binding to the currently selected detent: [presentationDetents(\_:selection:)](https://developer.apple.com/documentation/swiftui/view/presentationdetents(_:selection:))

<mark>That should be the preferred solution for most developers!</mark>

But what if the View in the bottom sheet is provided by a 3rd party SDK? Should the SDK offer to inject such binding? What if the app developer doesn't provide the binding? I was looking for alternatives that could work without any prerequisites for the consumer of the View.

The [verticalSizeClass](https://developer.apple.com/documentation/swiftui/environmentvalues/verticalsizeclass) is unaffected by detent changes, so that's not an option 🙁

I asked ChatGTP for a workaround, and here is the answer from OpenAI.

* **SheetDetentObserver**: This class conforms to `ObservableObject` and publishes changes to the current detent.
    
* **DetentSheetView**: A custom `UIViewControllerRepresentable` that presents a sheet and uses `UISheetPresentationController` to manage detents.
    
* **Coordinator**: Acts as the delegate for `UISheetPresentationControllerDelegate`, observing changes in the detent and updating the `SheetDetentObserver`.
    

🤔 Hmm, oookkkkk ... maybe it's possible, but the solution seems to have a lot of overhead. I was looking for a simpler solution that does not require using UIKit.

Do I need the detent, or would it be okay to know the sheet size? Then I could hide the unwanted UI controls if a certain threshold is exceeded for the height.

Let's try it! [`GeometryReader`](https://developer.apple.com/documentation/swiftui/geometryreader) to the rescue! Using `GeometryReader` for this particular use case seems perfectly fine but be aware that this container view comes with some challenges.

%[https://fatbobman.com/en/posts/geometryreader-blessing-or-curse/] 

We can get the container size and use a threshold (e.g. 500) to determine when to show certain controls.

```swift
struct SheetContentView: View {
    var body: some View {
        GeometryReader { proxy in
                ScrollView {
                    SomeHeaderView(showImage: proxy.size.height > 500 ? true : false)
                    SomeSheetContentView()
                }
            }
    }
}
```

We could also create a custom SwiftUI environment variable to make the value accessible for all subviews.

```swift
struct SheetContentViewSizeKey: EnvironmentKey {
    static var defaultValue: CGSize = .zero
}

extension EnvironmentValues {
    var mySheetContentViewSize: CGSize {
        get { self[SheetContentViewSizeKey.self] }
        set { self[SheetContentViewSizeKey.self] = newValue }
    }
}
```

Then the `mySheetcontentViewSize` can be accessed by any view in the view hierarchy.

```swift
struct SomeSheetContentView: View {
    // built-in SwiftUI environment variable
    @Environment(\.isPresented) var isPresented
    // custom
    @Environment(\.mySheetContentViewSize) var mySheetContentViewSize

    var body: some View {
        VStack {
            Text("Height: \(mySheetContentViewSize.height)")
            Text("Is Presented: \(isPresented)")
        }
    }
}
```

Complete code from my example:

```swift
struct ContentView: View {
    @State var isSheetShown = false
    
    var body: some View {
        VStack {
            Button("Open Sheet") { isSheetShown.toggle() }
            .sheet(isPresented: $isSheetShown) {
                SheetContentView()
                    .presentationDetents([.medium, .large])
            }
        }
        .padding()
    }
}

struct SheetContentView: View {    
    var body: some View {
        GeometryReader { proxy in
                ScrollView {
                    SomeHeaderView(showImage: proxy.size.height > 500 ? true : false)
                    SomeSheetContentView()
                }
                .environment(\.mySheetContentViewSize, proxy.size)
            }
    }
}

 
struct SomeHeaderView: View {
    var showImage: Bool
    var body: some View {
        HStack {
            Text("My Sheet Header")
            Spacer()
            if showImage {
                Image(systemName: "moon.stars.fill")
                    .frame(width: 75, height: 75)
            }
        }
        .background(.red)
    }
}

struct SomeSheetContentView: View {
    // built-in SwiftUI environment variable
    @Environment(\.isPresented) var isPresented
    // custom
    @Environment(\.mySheetContentViewSize) var mySheetContentViewSize

    var body: some View {
        VStack {
            Text("Height: \(mySheetContentViewSize.height)")
            Text("Is Presented: \(isPresented)")
        }
    }
}

struct SheetContentViewSizeKey: EnvironmentKey {
    static var defaultValue: CGSize = .zero
}

extension EnvironmentValues {
    var mySheetContentViewSize: CGSize {
        get { self[SheetContentViewSizeKey.self] }
        set { self[SheetContentViewSizeKey.self] = newValue }
    }
}
```

This blog post might not be helpful for everyone, but I thought it's a good idea to document this option. Not only for me but also for my co-workers 😀
