How to Create a Custom BottomSheet — Swift
“Standard bottom sheets display content that complements the screen’s primary content. They remain visible while users interact with the primary content.”(material.io)”
Hello everyone, in this tutorial we'e gonna learn how to create a Generic Custom Bottom Sheet in Swift. It means that you will transform any viewController into a BottomSheet.
So let's start creating our BottomShettViewController and remember to check to create XIB together.
Add two views like this:
First View Constraints
Let's see the constraint in the firstView (black color)
- In the horizontal, we have leading and trailing constraints to Safe Area:
- In the vertical:
- All constraints:
Remember to put any color with opacity between 0.5 and 0.7 to create a good effect when Bottom View Appear
Second View Constraints
- In the horizontal we have a leading and trailing constraint to Safe Area or Super View. You choose whats is better for you:
- In the vertical, we have some different constraints:
- We have bottom space to Superview
- We must have a Height Constraint because if our viewController is contained in a table view we need to set the content size. Auto layout will not give us an error but if we don't do it, our layout will be prone to weird behavior
- We have Top Space to our view blank view
- And finally we have an Align Top constraint to top Safe Area is Greater Than or equal 40, that's mean if our viewController has a full size, it will be below Safe Area 40 points
- All Constraints:
Let's code now
Let's create three outlets one for our view, another to our bottomConstraint (we're gonna create an animation), and the last one to our view height as explained above.
and creating two global variables like this
private let childViewController: UIViewController
private var originBeforeAnimation: CGRect = .zero
The first one will be our embedded view controller and the other is our animation controller.
Let's create our init:
We must call our super.init
because when we initialize our BottomSheet we don't need to pass nibName and Bundle all the time. Let's change our
modalPresentntationStyle = . overFullScreen` because we want to see last view controller information.
Extra Code
I have this Extension that will help you to create the animation, corner radius, and present the BottomSheet.
If you don't want this extension, it's not a problem, just remember to set animated as false when presenting a bottomSheet.
Back to BottomSheet
Let's override three methods of the viewController lifecycle:
- ViewDidLoad
- ViewDidAppear
- ViewDidLayoutSubviews
ViewDidLoad
In viewDidload
we configure our contentView alpha to 1, added PanGestureRecognizer to dismiss our BottomSheet and finally in our bottom constraint we set the constants property with negative value of height from childViewController
to completely hide our content view. We have the func configureChild()
don't worry, I'm gonna show later.
ViewDidAppear
Remember when I explained about height constraint? We verify if our childViewController contains a TableView(I'm gonna show this func later) if childView contains a tableView we update our height constraint with tableView content size. Otherwise we disable this constraint. After we change our bottomConstraint to zero and we animated our view to black color with opacity and the layout.
ViewDidLayoutSubviews
We configure our contentView to have a corner radius the top left and top right. And modify our originAnimation with contentView frame.
After that let's create our dismiss func and we do the inverse in ViewDidLoad. And the dismiss func and should set animated as false to not do a standard effect dismiss.
Let's to our private func
ConfigureChild
We add childViewController as Child of our BottomSheet, and add childViewController view as a subview in contentView and set the constraint to our childViewController view.
IMPORTANT
If your childViewController it`s a ViewController and you want a scroll view… your childViewController must have a scroll view with auto layout.
ContainsTableView
Just check if the subviews in childViewController contain a tableView.
ShouldDismissWithGesture
We verify if Pan Gesture state end
PanGesture
We called our ShouldDismissWithGesture
func and it's true we called another func dismissViewController
.
After we verify if the user moves the point is smaller and equal to our OriginAnimation and disable and enable the gesture.
Finale We move our contentView Y position with gesture Y position
UIGestureRecognizerDelegate
We use this delegate method to no recognize simultaneous gestures, because if our childViewController contains a table View or scroll View, we want to scroll and not dismiss.
And finally we add a TapGesture in our black view top view in XIB and create a IBAction
to dismiss our bottom sheet when tapping in the top.
How to use?
It's very simple, just create your viewController and just need to pass the viewController to our BottomSheetViewContoller and presentBottomSheet.
Cocoapods
Thank you guys for reading this article, I created a pod and you can use the BottomSheet in an easy way https://github.com/brqdigital/BRQBottomSheet