iOS如何实现可通过顶部下拉关闭的非全屏界面?
iOS实现非全屏可下拉关闭界面的方案
一、官方原生方案(iOS 15+ 推荐)
使用UISheetPresentationController,这是苹果官方提供的底部弹窗控制器,天然支持下拉关闭、多尺寸切换等特性,无需自定义手势。
实现步骤:
- 创建需要展示的内容视图控制器
- 配置sheet的显示参数:
- 设置
detents指定可切换的尺寸(如.medium()中等高度、.large()全屏,也可自定义高度) - 开启
prefersGrabberVisible显示顶部抓手,提示用户可拖动 - 可选设置
largestUndimmedDetentIdentifier,让非全屏状态下背景保持清晰
- 设置
- 以present方式展示控制器
代码示例:
// 创建内容控制器 let contentVC = YourContentViewController() // 配置Sheet参数 if let sheet = contentVC.sheetPresentationController { // 支持中等高度和全屏两种尺寸 sheet.detents = [.medium(), .large()] // 显示顶部拖动抓手 sheet.prefersGrabberVisible = true // 中等高度时背景不模糊 sheet.largestUndimmedDetentIdentifier = .medium // 内容滚动到顶部时不自动展开全屏 sheet.prefersScrollingExpandsWhenScrolledToEdge = false } // 展示弹窗 present(contentVC, animated: true)
二、自定义手势实现(兼容iOS 15以下或高度定制场景)
如果需要兼容低版本系统,或者要完全自定义弹窗的样式和拖动逻辑,可以通过UIPanGestureRecognizer实现下拉关闭。
实现思路:
- 创建一个容器视图作为弹窗主体,布局为非全屏样式(如底部向上弹出,高度占屏幕2/3)
- 给容器视图添加拖动手势
- 在手势回调中处理拖动逻辑:
- 拖动过程中实时更新容器视图的位置
- 拖动结束时,根据拖动距离或速度判断是否关闭弹窗,否则回弹到原位置
代码示例:
class CustomBottomSheetVC: UIViewController { private let containerView = UIView() // 记录容器视图初始Y坐标 private var initialContainerY: CGFloat = 0 override func viewDidLoad() { super.viewDidLoad() setupUI() addPanGesture() } private func setupUI() { // 设置背景半透明 view.backgroundColor = UIColor.black.withAlphaComponent(0.3) // 配置容器视图样式 containerView.backgroundColor = .white containerView.layer.cornerRadius = 16 containerView.clipsToBounds = true view.addSubview(containerView) // 布局容器视图为底部弹出的非全屏样式 containerView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), containerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 2/3) ]) // 记录初始位置 initialContainerY = containerView.frame.origin.y } private func addPanGesture() { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) containerView.addGestureRecognizer(panGesture) } @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view) switch gesture.state { case .began: // 手势开始时重新记录初始位置 initialContainerY = containerView.frame.origin.y case .changed: // 拖动过程中更新容器位置,限制不能向上拖动超过初始位置 let newY = initialContainerY + translation.y containerView.frame.origin.y = max(initialContainerY, newY) case .ended, .cancelled: let velocity = gesture.velocity(in: view) // 判断关闭条件:拖动距离超过100pt,或向下拖动速度超过500pt/s let shouldDismiss = translation.y > 100 || velocity.y > 500 if shouldDismiss { // 动画滑出屏幕后关闭控制器 UIView.animate(withDuration: 0.3) { self.containerView.frame.origin.y = self.view.bounds.height } completion: { _ in self.dismiss(animated: false) } } else { // 动画回弹到初始位置 UIView.animate(withDuration: 0.3) { self.containerView.frame.origin.y = self.initialContainerY } } default: break } } }
三、第三方库方案(可选)
如果不想重复造轮子,可以使用成熟的第三方库,比如PopupController、MDCBottomSheet等,这些库封装了完整的底部弹窗逻辑,支持下拉关闭、样式定制等功能。
内容的提问来源于stack exchange,提问作者Metin SevTHP
相关产品推荐
相关产品推荐

