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
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).
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.
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).
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)
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.
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.
Examples to come.
Examples to come.
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:
- Grabber: having it or not, its color, size, corner radius, background color or view...
- Dimming background color
- Corner radius
- Shadow
- Animations speed: appearing and disappearing
- Elasticity effet (default provided value: logarithmic)
- Swipe speed threshold to dismiss
- Swipe height threshold to dismiss
- ...
You can find all the available configuration parameters in the documentation.
The iOS team at Lunabee Studio
LBBottomSheet is available under the Apache 2.0 license. See the LICENSE file for more info.