Design is not my forte. I recently faced this challenge with my Meshing app where the UI got decluttered.

Join the Meshing beta
Available on iOS

I love adding features (typical developer, uhuh) to my apps, but this enthusiasm usually leads to a cluttered user interface. That is exactly what happened with Meshing. The main screen was packed with controls and options, making it overwhelming for users, especially on iOS because this app is mainly designed for macOS.

Swift Dev suggested me to use a bottom sheet to clean things up. It seemed like a perfect solution - I could tuck away less frequently used controls while keeping them easily accessible.

In this post, I will walk you through my journey of creating a native SwiftUI bottom sheet without any third-party packages!

What is a Bottom Sheet?

Before we get into the code and the modifiers in SwiftUI, let us be sure we know what a bottom sheet is. It is a UI element that slides up from the bottom of the screen, revealing additional content or controls. Think of it as a drawer that you can pull up when needed. If you have used Apple Maps, you know what I am talking about:

Bottom sheets are great for:

  • Displaying additional options
  • Showing more information without leaving the current view
  • Creating a layered UI which is both clean and functional

Creating the Bottom Sheet

Now, let me walk through the process of creating the bottom sheet I used for Meshing.

Always-Open Bottom Sheet

The first challenge was figuring out how to keep the bottom sheet always visible. Swift Dev shared a brilliant tip: use .constant(true) for the isPresented parameter. Here's how it looks:

.sheet(isPresented: .constant(true), content: {
  // Your sheet content goes here
})

This simple trick ensures that the bottom sheet is always present, providing quick access to controls without the need for a separate toggle to open it.

Adjusting the Sheet Size

Next, I needed to decide how much of the screen the bottom sheet should occupy. After playing around and experimenting on the iPhone 15 Pro and 15 Pro Max, I decided with two fractions using the presentationDetents modifier:

.presentationDetents([.fraction(0.1), .fraction(0.5)])

This line creates two "rest points" for the sheet:

  1. A peek view at 10% of the screen height that is always visible
  2. A half-expanded view at 50% of the screen height

The peek view gives the basic control of the grid sizes, and users a hint that more controls are available, while the half-expanded view provides ample space for all controls without obscuring the entire main view.

Preventing Accidental Dismissal

An annoying issue with some bottom sheets is that they are too easy to dismiss accidentally. To prevent this, I used the interactiveDismissDisabled() modifier:

.interactiveDismissDisabled()

The sheet should always be open so this ensures that users cannot swipe the sheet away at all.

Enabling Background Interaction

One of the key features I wanted was the ability for users to interact with the main view (in this case, updating the gradient) while the bottom sheet was open. The presentationBackgroundInteraction(.enabled) modifier made this possible:

.presentationBackgroundInteraction(.enabled)

This allows users to tap and interact with the control points behind the sheet, to use the mesh gradient and control it using the toggles.

Adding Visual Cues

To make it clear that the sheet can be expanded and contracted, I added a visible drag indicator:

.presentationDragIndicator(.visible)

This small visual cue goes a long way in improving usability. I have had users give me feedback who thought it just cannot be dismissed because the drag indicator was missing, which is a valid point.

Putting It All Together

Here is the complete code snippet that brings all these elements together:

.sheet(isPresented: .constant(true), content: {
    ControlPanel(viewModel: viewModel)
        .interactiveDismissDisabled()
        .presentationBackgroundInteraction(.enabled)
        .presentationDetents([.fraction(0.1), .fraction(0.5)])
        .presentationDragIndicator(.visible)
        .ignoresSafeArea()
})

And here it how it looks:

0:00
/0:17

Moving Forward

Creating a bottom sheet in SwiftUI was fun. It did solve my UI clutter problem while keeping it clean while still providing easy access to additional controls.

The next step in the future would be to listen to the scroll detection of the sheet, because if you see the Maps app, it hids the secondary controls and shows it only when the bottom sheet is expanded.

Happy coding, and may your interfaces always be clutter-free!

Astro Affiliate.

Astro (Affiliate)

Find the right keywords for your app and climb the App Store rankings. Improve your app visibility and increase your revenue with Astro. The first App Store Optimization tool designed for indie developers!

Tagged in: