Keywords: iOS Development | Bottom Sheet | Gesture Recognition
Abstract: This article provides an in-depth exploration of implementing bottom sheet interfaces similar to Apple Maps in iOS applications. By analyzing best practices, it details the use of custom view controllers, gesture recognition, and animation effects to create interactive bottom sheets. The content covers the complete development process from basic implementation to advanced features like scroll view integration, offering code examples and design insights to help developers master this popular UI component.
In iOS development, implementing bottom sheet interfaces similar to those in Apple Maps has become an important design pattern for enhancing user experience. This UI element allows users to interact with a sliding panel from the bottom while keeping the main view visible. This article provides a detailed analysis of how to implement this functionality in iOS applications based on best practices.
Technical Architecture Design
The core of implementing bottom sheets lies in creating independent view controller hierarchies. Typically, two main components are required: one for displaying the main content (such as maps) and another specifically managing the display and interaction of the bottom sheet. This separation design pattern facilitates code modularization and maintenance.
First, create the main view controller responsible for initializing and adding the bottom sheet view:
func addBottomSheetView() {
let bottomSheetVC = BottomSheetViewController()
self.addChild(bottomSheetVC)
self.view.addSubview(bottomSheetVC.view)
bottomSheetVC.didMove(toParent: self)
let height = view.frame.height
let width = view.frame.width
bottomSheetVC.view.frame = CGRect(x: 0, y: self.view.frame.maxY, width: width, height: height)
}
Bottom Sheet View Controller Implementation
The bottom sheet view controller needs to handle visual effects, animations, and user interactions. First, implement background blur effects to enhance visual hierarchy:
func prepareBackgroundView() {
let blurEffect = UIBlurEffect(style: .dark)
let visualEffect = UIVisualEffectView(effect: blurEffect)
let bluredView = UIVisualEffectView(effect: blurEffect)
bluredView.contentView.addSubview(visualEffect)
visualEffect.frame = UIScreen.main.bounds
bluredView.frame = UIScreen.main.bounds
view.insertSubview(bluredView, at: 0)
}
The animated display of the bottom sheet is achieved by adjusting the view frame:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.3) { [weak self] in
let frame = self?.view.frame
let yComponent = UIScreen.main.bounds.height - 200
self?.view.frame = CGRect(x: 0, y: yComponent, width: frame!.width, height: frame!.height)
}
}
Gesture Recognition and Interaction
Implementing drag interaction requires adding a pan gesture recognizer:
override func viewDidLoad() {
super.viewDidLoad()
let gesture = UIPanGestureRecognizer(target: self, action: #selector(panGesture))
view.addGestureRecognizer(gesture)
}
@objc func panGesture(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
let y = self.view.frame.minY
self.view.frame = CGRect(x: 0, y: y + translation.y, width: view.frame.width, height: view.frame.height)
recognizer.setTranslation(CGPoint.zero, in: self.view)
}
Scroll View Integration
When the bottom sheet contains scrollable content, gesture conflicts need to be handled. By implementing the UIGestureRecognizerDelegate protocol, precise control over when to enable scrolling functionality can be achieved:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
let gesture = gestureRecognizer as! UIPanGestureRecognizer
let direction = gesture.velocity(in: view).y
let y = view.frame.minY
if (y == fullView && tableView.contentOffset.y == 0 && direction > 0) || (y == partialView) {
tableView.isScrollEnabled = false
} else {
tableView.isScrollEnabled = true
}
return false
}
Advanced Implementation Considerations
More complex implementations need to consider gesture propagation issues. Through custom view classes, touch events can be correctly passed to underlying views:
class PassThroughView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
if view == self {
return nil
}
return view
}
}
In iOS 15 and later versions, Apple provides the native UISheetPresentationController, which can simplify the implementation process:
if let sheet = viewControllerToPresent.sheetPresentationController {
sheet.detents = [.medium(), .large()]
}
present(viewControllerToPresent, animated: true, completion: nil)
Performance Optimization Recommendations
In actual development, attention should be paid to animation performance optimization. Using UIViewPropertyAnimator can provide smoother animation experiences, especially when handling complex gesture interactions. Meanwhile, reasonable memory management should be implemented to ensure proper resource release when view controllers are destroyed.
For bottom sheets that need to support multiple states, implementing a state machine pattern to manage different display states (such as minimized, partially expanded, fully expanded) is recommended. This helps maintain code clarity and maintainability.
Testing and Debugging
During development, it is recommended to use Xcode's view debugging tools to inspect view hierarchies. By simulating different device sizes and orientations, ensure that bottom sheets display and interact correctly under various conditions.
When implementing bottom sheet functionality, user experience testing is crucial. It is necessary to ensure that gesture recognition is sensitive and accurate, animation transitions are natural and smooth, and consistent interactive experiences are provided across different usage scenarios.