Library / SDK
lucaszischka/BottomSheet avatar
lucaszischka/BottomSheet

BottomSheet gives SwiftUI a resizable sheet with an arbitrary number of states

A sliding Sheet from the bottom of the Screen with 3 States build with SwiftUI.

1,249 stars164 forksSwiftMIT

At a glance

What is it?
BottomSheet is a SwiftUI package that reproduces the Apple Maps style sheet, where the visible height changes between states instead of staying fixed, with any number of states at any height. It fits an iOS app that needs a detachable panel, and it does not fit a project that has moved to the system sheet introduced in iOS 16.
Who is it for?
BottomSheet fits an iOS app that wants a detachable panel with several heights and needs to support older iOS versions, and it does not fit a project targeting iOS 16 and later that would rather use the system sheet, which the README notes Apple added.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 97 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is that most sheet clones keep the same height in every state

BottomSheet is a sliding sheet from the bottom of the screen with custom states, built with SwiftUI. The Why section explains what it is for and what the previous attempts got wrong.

There have been many different attempts to recreate the BottomSheet from Apple Maps, Shortcuts and Apple Music, because Apple unfortunately does not provide it in their SDK, with a parenthetical update noting that it was more or less added in iOS 16.

The common problem with the previous attempts is stated precisely. The height does not change in the different states. So the BottomSheet is always the same size, for example 800 pixels, and thus remains 800 pixels even if you only see 400 of them. The rest is inaccessible unless you pull the BottomSheet up to the very top.

That is the whole design problem in one sentence, and it explains why the library exists. A sheet that is sized to its largest state but only partly visible has two failure modes. Content below the fold is unreachable, and content above it is invisible but still present in the layout, so a ScrollView inside it cannot scroll properly because the scroll view is sized for the full height.

The second gap the README names is that many implementations out there only have 2 states, not 3 like Apple Maps. Three states, in Apple's case, are a collapsed state, a partial state and a full state, and the ability to flick between them with a drag indicator is what makes the pattern feel native.

The project is MIT licensed, the last push was on 2026-07-18, and the release history shows 3.1.1 from 2023-06-26, 3.1.0 from 2022-09-20 and 3.0.2 from 2022-08-12.

Any number of states, at content height, absolute pixels or screen percentage

The feature list names fully customisable states, with any number of states at any height, and states can have the height of their content, absolute pixel values or percentages of the screen height.

That third option, height determined by the content, is the one that solves the problem the project was built for. If the sheet's medium state is sized to fit what it contains, then the visible portion is the content, and the inaccessible-region problem disappears rather than being worked around.

The state model is BottomSheetPosition, and it is referenced throughout the API. The current state is held in a binding of type Binding<BottomSheetPosition>, and the set of positions the sheet can be dragged or swiped into is a separate array called switchablePositions. Only the positions contained in that array can be switched into, via tapping the drag indicator or swiping the sheet.

Separating the current state from the permitted states is a small design decision with a large effect on how the component composes. A sheet with three defined states can be presented at one of them, and the user can move between the permitted ones, but the app can also drive it to any position directly by writing to the binding.

The modifiers cover the rest of the customisation. The README says there are many view modifiers, and the ones documented include .enableAccountingForKeyboardHeight(Bool), which adds padding to the bottom of the main content when the keyboard appears so all of the main content is visible, with the caveat that if the height of the sheet is smaller than the height of the keyboard, this modifier will not make the content visible, and that it is not available on Mac because it would not make sense there.

The usage sample is for visualisation, not for copying

The README puts a warning above the usage section, and it is unusual enough to be worth quoting. It says this is sample code for visualisation where and how to use, without a working initializer, and points to the Examples section for working code.

The shape of the API is still clear from it:

swift
struct ContentView: View {

    @State var bottomSheetPosition: BottomSheetPosition = .middle //1
    
    var body: some View {
    
        Map() //2
            .bottomSheet() //3
    }
}

The three annotations carry the meaning. The first is the current state of the BottomSheet, and if you do not want the BottomSheet to be draggable and the state to be switchable, you can use the .isResizable(false) modifier. The second is the view which the BottomSheet overlays, and the note here matters: if you want to overlay a TabBar or a NavigationView, you need to add the BottomSheet on a higher level. The third is how you add the BottomSheet.

The TabBar and NavigationView note is the kind of thing that is easy to skip and expensive to debug. Attaching the sheet at the wrong level in the view hierarchy means the sheet is clipped by the container it is placed inside, and the symptom looks like a layout bug rather than an attachment bug.

The position enum has a .middle case, which confirms the three-state model with a partial state in the middle.

Two content variants, title string or custom header view

There are two overloads of the bottomSheet modifier, and they differ in how the header is supplied.

The first takes a title as header content:

swift
.bottomSheet(
    bottomSheetPosition: Binding<BottomSheetPosition>,
    switchablePositions: [BottomSheetPosition],
    title: String?,
    content: () -> MContent
)

The second takes a custom header view:

swift
.bottomSheet(
    bottomSheetPosition: Binding<BottomSheetPosition>,
    switchablePositions: [BottomSheetPosition],
    headerContent: () -> HContent?,
    mainContent: () -> MContent
)

The parameters are otherwise identical in meaning. bottomSheetPosition is the binding that holds the current position or state, switchablePositions is the array of positions that can be switched into, and the content closures are the views used for the header and the main content. The title is a String that is displayed as title, and you can use a view that is used as header content instead, so the two forms are interchangeable in capability.

The generic parameterisation is worth noting. MContent and HContent are generic, so the closures return your own view types and the compiler infers them from the call site rather than requiring a type annotation. That is what makes the API compose with an existing view hierarchy rather than forcing a specific content type.

The build-in header and title is listed as a feature, and the search bar in the header is supported as well, so a sheet with a search field in its title bar is a supported shape rather than something you build on top.

Apple scroll behaviour is a modifier, and it only scrolls at the top

The modifier list includes.enableAppleScrollBehavior(Bool), which packs the mainContent into a ScrollView, with the behaviour on the iPhone described as the ScrollView being only enabled, and scrollable, when the BottomSheet is in a...Top position.

The README describes it as an embedded.appleScrollBehaviour() modifier, to replicate Apple's ScrollView behaviour, and the library also says it works with ScrollView, List and every other view, and can hide the content when in the...Bottom position like Apple does.

The interaction between the sheet's state and the scroll view inside it is the heart of the Apple Maps pattern, and getting it wrong is the usual failure of a hand-rolled implementation. When the sheet is in a partial state, a scroll view inside it will fight with the sheet's own pan gesture, and whichever view wins the gesture decides whether the user scrolls the content or drags the sheet. Apple resolves it by allowing content scrolling only when the sheet is fully expanded, which is what this modifier replicates.

The hidden-content behaviour in the...Bottom position is the other half of the trick. A collapsed sheet that still renders its content shows clipped rows of text under the collapsed edge, which is the visual signature of a sheet that has not quite collapsed. Hiding it is what makes the collapsed state look intentional.

Installation, requirements and the version 3 rewrite

The preferred installation route is Swift Package Manager, and the README walks through it in Xcode: open your project and navigate to File then Add Packages, paste the repository URL at https://github.com/lucaszischka/BottomSheet and click Next, select Up to Next Major Version for Rules, and click Add Package.

CocoaPods is the alternative, with the pod name differing from the repository name:

ruby
pod 'BottomSheetSwiftUI'

Then run pod install in the Terminal.

The requirements are iOS 13, macCatalyst 13 and macOS 10.15, with Swift 5.5 and Xcode 12. That is a meaningful floor for a SwiftUI package, since SwiftUI itself arrived in iOS 13, and the package carries a SwiftPM compatible badge as well as CocoaPods badges for the BottomSheetSwiftUI pod.

The README also announces that version 3 is out and points at a pull request for the changes, with the note to read the README for more information on the changes. The podspec at the root is BottomSheetSwiftUI.podspec, and the sources live in a Sources/ directory, with a .swiftlint.yml at the root, so the package is a single SwiftPM target with SwiftLint configured.

The version story is worth flagging for anyone pinning. The release history has 3.1.1 from June 2023 as the newest tag, 3.1.0 from September 2022 and 3.0.2 from August 2022, while the last push was on 2026-07-18. The source tree is being worked on, but the tagged releases have not moved in three years, so a version bump for a Swift or Xcode change may mean working from a branch rather than a tag.

Where BottomSheet is the wrong tool

The first wrong-tool case is the newest iOS. The README's own parenthetical update says the BottomSheet was more or less added in iOS 16. A project whose minimum deployment target is iOS 16 and that is willing to use the system sheet has a native solution with no dependency, no custom gesture arbitration and Apple's own accessibility behaviour, and adopting a third-party clone to reproduce something the platform now ships is a step backwards.

The second is an app that is not a SwiftUI app. The package is SwiftUI-only, and while the requirement list includes macOS 10.15 and macCatalyst 13, there is no UIKit or AppKit path. A team with an existing UIKit codebase would be introducing a second UI paradigm for one component.

The third is a sheet that needs a fixed height with a scroll view inside it. The library's premise is that the sheet's height changes between states, and the Apple scroll behaviour it embeds assumes the sheet is sized to its content. A design where the sheet is always the same size and the content scrolls inside it is the case the library explicitly criticises other implementations for getting wrong, so it is not the case this library serves.

The fourth is a team that has not read past the warning. The usage sample in the README does not have a working initializer, and the state-to-height mapping is described in prose rather than in an API table. This is a package where the demo project and the source are the documentation, and a team that wants an API reference will spend time in Xcode instead of in a document.

Editorial conclusion

BottomSheet fits an iOS app that wants a detachable panel with several heights and needs to support older iOS versions, and it does not fit a project targeting iOS 16 and later that would rather use the system sheet, which the README notes Apple added. Before adopting it, check that your minimum deployment target is covered, since the requirements are iOS 13, macCatalyst 13 and macOS 10.15 with Swift 5.5, and note that the sample code in the README is explicitly for visualisation rather than a working initializer, so the Examples section is where the real usage is.

Frequently asked questions

What does BottomSheet add to SwiftUI?

It provides a bottom sheet whose height changes between states, rather than staying at its largest size, with any number of states at content height, absolute pixels or a percentage of screen height. The state is held in a Binding<BottomSheetPosition> and the permitted states are given as switchablePositions.

How do I install BottomSheet?

The preferred route is Swift Package Manager, by adding the repository URL in Xcode under File then Add Packages and selecting Up to Next Major Version. It is also available on CocoaPods as pod 'BottomSheetSwiftUI', followed by pod install. The requirements are iOS 13, macCatalyst 13, macOS 10.15, Swift 5.5 and Xcode 12.

What does the enableAppleScrollBehavior modifier do?

It packs the main content into a ScrollView and replicates Apple's behaviour, where on the iPhone the ScrollView is only enabled and scrollable when the BottomSheet is in a ...Top position, so content scrolling and sheet dragging do not fight over the same gesture.

Is BottomSheet still released?

The most recent tagged release is 3.1.1 from 2023-06-26, with 3.1.0 from 2022-09-20 and 3.0.2 from 2022-08-12, while the last push to the repository was on 2026-07-18. The README points at the version 3 pull request for the changes introduced in that major release.

Official sources

  1. Issues
  2. License: MIT
  3. lucaszischka/BottomSheet on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lucaszischka-bottomsheet.svg)](https://hysenlabs.com/projects/lucaszischka-bottomsheet)

Was this useful?