LBBottomSheet

master

LBBottomSheet gives you the ability to present a controller in a kind of "modal" for which you can choose the height you want.
LunabeeStudio/LBBottomSheet

LBBottomSheet

Installation

Swift Package Manager

To install using Swift Package Manager, in Xcode, go to File > Add Packages..., and use this URL to find the LBBottomSheet package: https://github.com/LunabeeStudio/LBBottomSheet.git

After adding this Swift Package to your project, you have to import the module:

import LBBottomSheet

Usage

The BottomSheet gives you the ability to present a controller in a kind of "modal" for which you can choose the height you want.

The are 3 differents ways of configuring the BottomSheet height represented by the HeightMode enum.
Here are the available height modes:

HeightMode Description
fitContent The bottom sheet will call preferredHeightInBottomSheet on the embedded controller to get the needed height.
free The bottom sheet height will be contained between minHeight and maxHeight and the bottom sheet will remain where the user releases it.
specific The bottom sheet will have multiple height values. When the user releases it, it will be attached to the nearest provided specific value. When presented, the bottom sheet will use the minimum value. It can be swipped up to the maximum value. You don't have to take care of the values order, the bottom sheet will sort them to find the matching one.

We'll see through the following examples, how you can configure it (don't hesitate to look at the documentation to see all what you can do).

FitContent - Example #1

To show MyViewController in a bottom sheet above the current controller, you just need to call this from a view controller:

let controller: MyViewController = .init()
presentAsBottomSheet(controller)

A default Theme and a default Behavior will be used.

In this example, the grabber background is transparent. This way you see the tableView content behind the grabber when scrolling, which is the default configuration. Let's see in the next example how to configure this.

FitContent - Example #2

If you want, you can provide your own Theme and Behavior configurations.
For example, here we customize the grabber background and the swipeMode:

let controller: MyViewController = .init()
let grabberBackground: BottomSheetController.Theme.Grabber.Background = .color(.tableViewBackground.withAlphaComponent(0.9), isTranslucent: true)
let grabber: BottomSheetController.Theme.Grabber = .init(background: grabberBackground)
let theme: BottomSheetController.Theme = .init(grabber: grabber)
let behavior: BottomSheetController.Behavior = .init(swipeMode: .full)
presentAsBottomSheet(controller, theme: theme, behavior: behavior)

In this example, the background is translucent and we have a swipeMode set to .full which means that the swipe down gesture will be detected from all the BottomSheet (this is the default behavior).

FitContent - Example #3

In this example, the grabber background is opaque and the swipeMode is set to .top which means that the swipe down gesture will only be detected from the grabber zone:

let controller: MyViewController = .init()
let grabberBackground: BottomSheetController.Theme.Grabber.Background = .color(.tableViewBackground, isTranslucent: false)
let grabber: BottomSheetController.Theme.Grabber = .init(background: grabberBackground)
let theme: BottomSheetController.Theme = .init(grabber: grabber)
let behavior: BottomSheetController.Behavior = .init(swipeMode: .top)
presentAsBottomSheet(controller, theme: theme, behavior: behavior)


FitContent - Example #4

By default, the BottomSheet prevents you from interacting with the controller presenting it (like a standard modal).
It is possible to configure this in the Behavior using this parameter: forwardEventsToRearController.
This way you can continue to interact with the controller behind it. For a better experience, we advise you to set the dimmingBackgroundColor color to .clear and to implement the BottomSheetPositionDelegate on the controller presenting your BottomSheet to dynamically adapt its bottom content inset if needed.
Here is the BottomSheet configuration code:

let controller: MyViewController = .init()
let grabberBackground: BottomSheetController.Theme.Grabber.Background = .color(.tableViewBackground, isTranslucent: false)
let grabber: BottomSheetController.Theme.Grabber = .init(background: grabberBackground)
let theme: BottomSheetController.Theme = .init(grabber: grabber, dimmingBackgroundColor: .clear)
let behavior: BottomSheetController.Behavior = .init(swipeMode: .full, forwardEventsToRearController: true)
presentAsBottomSheet(controller, theme: theme, behavior: behavior)

Here is the BottomSheetPositionDelegate implementation on the controller presenting the BottomSheet:

extension MainViewController: BottomSheetPositionDelegate {
    func bottomSheetPositionDidUpdate(y: CGFloat) {
        tableView.contentInset.bottom = tableView.frame.height - y
    }
}

This will prevent you from having content hidden by the BottomSheet in case you need to interact with it.

FitContent - Advanced configuration

In this mode, by default, the height is automatically calculated:

  • If the BottomSheet contains a UITableView/UICollectionView even if contained in a parent controller, it will use the contentInset top, bottom and the content size height to determine the needed height.
  • Otherwise, it will take the frame height of the embedded controller view.

If you want to customize this calculation, you have to declare this variable on the controller you're embedding en the BottomSheet:

@objc var preferredHeightInBottomSheet: CGFloat { /* Do your custom calculation here */ }

When this variable is declared, the BottomSheet will find and use it instead of the default calculation.

If you present in a BottomSheet a controller for which the height can change while it is visible, you can tell the BottomSheet to update its height in order to keep a height matching the embbeded controller needs. This update is animated by the BottomSheet.
To tell the BottomSheet to update its height, you just have to call this:

bottomSheetController?.preferredHeightInBottomSheetDidUpdate()

bottomSheetController is available in any UIViewController as navigationController or tabBarController for example.

The last thing for this mode is about the dynamic types. If you present in a BottomSheet, a controller using dynamic types to manage the font size changes based on the user's choices, the controller might need more height than the initial one if the font size changes while the controller is presented.
You don't have to manage this as the BottomSheet is listening for the content size category changes notification. If the user changes the font size, the BottomSheet will automatically trigger a height update.

If you need to manually dismiss the bottom sheet, you have 2 ways to do this.
In case you have a reference to the bottom sheet, you can call theis function on it:

dismiss(_ completion: (() -> Void)? = nil)

Otherwise, from any controllers under the bottom sheet, you can call

dismissBottomSheet(_ completion: (() -> Void)? = nil)

This will dismiss the top presented bottom sheet if it is currently the top most controller.


Free height - Example #1

Examples to come.


Specific heights - Example #1

Examples to come.


Customization

On the BottomSheet, it is possible to configure its appearance and its behavior.
To do this you have 2 structs: Theme and Behavior.
Thanks to these structs, you can configure things like:

You can find all the available configuration parameters in the documentation.

Author

The iOS team at Lunabee Studio

License

LBBottomSheet is available under the Apache 2.0 license. See the LICENSE file for more info.

Description

  • Swift Tools 5.5.0
View More Packages from this Author

Dependencies

  • None
Last updated: Fri Oct 18 2024 14:29:32 GMT-0900 (Hawaii-Aleutian Daylight Time)