iOS中仿Apple Maps底部视图滑动动画的手势处理问题求助
Hey there! Let's tackle this bottom sheet gesture issue you're having—Apple Maps-style bottom sheets are tricky but super satisfying when they work right. Since you already have your panVC added as a subview from the bottom, let's focus on fixing the pan gesture handling with practical, actionable steps.
Key Steps to Implement Smooth Pan Gesture Handling
1. Add the Pan Gesture Recognizer Correctly
First, make sure you've attached a UIPanGestureRecognizer to your panVC's main view, and handle any potential gesture conflicts upfront:
override func viewDidLoad() { super.viewDidLoad() let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) // Allow simultaneous recognition if you have scroll views inside the sheet panGesture.delegate = self view.addGestureRecognizer(panGesture) } // Optional: Handle gesture conflicts with internal scroll views extension YourPanVC: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { // Only allow simultaneous recognition if the scroll view is at the top guard let scrollView = otherGestureRecognizer.view as? UIScrollView else { return true } return scrollView.contentOffset.y <= 0 } }
2. Define Your Sheet's Dock Positions
Apple Maps' bottom sheet has 3 key states: collapsed, half-expanded, and full-expanded. Define these as constants to keep your code clean:
private let collapsedHeight: CGFloat = 60 // Adjust to your needs private let halfExpandedHeight: CGFloat = UIScreen.main.bounds.height * 0.5 private let fullExpandedHeight: CGFloat = UIScreen.main.bounds.height - 80 // Account for navigation/status bars private var startingSheetY: CGFloat = 0 // Track where the sheet was when the gesture started
3. Handle the Pan Gesture's States
The core logic lives in the gesture handler, where you'll manage the sheet's position during the drag, then snap it to the nearest dock position when the gesture ends:
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view.superview) switch gesture.state { case .began: // Record the sheet's starting Y position when the user starts dragging startingSheetY = view.frame.origin.y case .changed: // Calculate the new Y position, and clamp it to prevent the sheet from going off-screen let newY = startingSheetY + translation.y let clampedY = max(fullExpandedHeight, min(newY, UIScreen.main.bounds.height - collapsedHeight)) view.frame.origin.y = clampedY case .ended, .cancelled: // Use velocity and current position to decide which dock state to snap to let velocity = gesture.velocity(in: view.superview).y let currentY = view.frame.origin.y var targetY: CGFloat // Prioritize velocity first for fast swipes if velocity < -300 { // Fast upward swipe targetY = fullExpandedHeight } else if velocity > 300 { // Fast downward swipe targetY = UIScreen.main.bounds.height - collapsedHeight } else { // Snap to the nearest dock position for slow drags let midHalfFull = (halfExpandedHeight + fullExpandedHeight) / 2 let midCollapsedHalf = (UIScreen.main.bounds.height - collapsedHeight + halfExpandedHeight) / 2 targetY = currentY < midHalfFull ? fullExpandedHeight : currentY < midCollapsedHalf ? halfExpandedHeight : UIScreen.main.bounds.height - collapsedHeight } // Animate the snap for that polished Apple-style feel UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.5) { self.view.frame.origin.y = targetY } default: break } }
4. Set the Initial Sheet Position
Make sure when you add panVC to your mainVC, you set its initial frame to the collapsed state:
// In your mainVC where you add panVC let panVC = YourPanVC() addChild(panVC) view.addSubview(panVC.view) panVC.didMove(toParent: self) // Set initial frame to collapsed state let initialFrame = CGRect( x: 0, y: view.bounds.height - panVC.collapsedHeight, width: view.bounds.width, height: view.bounds.height ) panVC.view.frame = initialFrame
Common Pitfalls to Avoid
- Forgetting to clamp the sheet's position: If you don't limit the Y value, the sheet can be dragged off-screen entirely.
- Ignoring gesture velocity: Without velocity checks, fast swipes won't feel responsive like Apple's implementation.
- Gesture conflicts with internal scroll views: If your sheet has a table/collection view, make sure you only allow dragging the sheet when the scroll view is at the top (the delegate method above handles this).
Give these steps a shot—this should get you pretty close to that Apple Maps-style smooth bottom sheet. If you hit specific snags (like layout issues with Auto Layout instead of frame-based positioning), feel free to share more details!
内容的提问来源于stack exchange,提问作者Ekra

