You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift代码创建半屏ViewController及上滑全屏实现咨询

嘿,这个交互我之前做项目的时候正好实现过,用UIKit的UIPresentationController搭配拖拽手势就能完美复刻Facebook的这个效果,下面是一步步的代码实现方案,你可以直接拿去用:

实现步骤

1. 自定义PresentationController控制初始半屏状态

首先我们需要一个自定义的UIPresentationController,用来设置弹出ViewController的初始半屏位置,同时添加半透明遮罩层,点击遮罩还能关闭弹窗:

class HalfScreenPresentationController: UIPresentationController {
    private lazy var dimmingView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismiss)))
        return view
    }()
    
    override var frameOfPresentedViewInContainerView: CGRect {
        guard let containerView = containerView else { return .zero }
        // 初始占屏幕下半部分,可根据需求调整高度比例
        let halfHeight = containerView.bounds.height / 2
        return CGRect(x: 0, y: containerView.bounds.height - halfHeight, width: containerView.bounds.width, height: halfHeight)
    }
    
    override func presentationTransitionWillBegin() {
        super.presentationTransitionWillBegin()
        dimmingView.frame = containerView?.bounds ?? .zero
        dimmingView.alpha = 0
        containerView?.insertSubview(dimmingView, at: 0)
        
        // 遮罩渐显动画
        presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
            self.dimmingView.alpha = 1
        })
    }
    
    override func dismissalTransitionWillBegin() {
        super.dismissalTransitionWillBegin()
        // 遮罩渐隐动画
        presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
            self.dimmingView.alpha = 0
        })
    }
    
    @objc private func dismiss() {
        presentedViewController.dismiss(animated: true)
    }
}

2. 创建支持拖拽的目标ViewController

接下来做我们要弹出的ViewController,给它添加拖拽手势,实现上滑全屏、下滑还原半屏的逻辑,还可以加个顶部小把手提示用户可拖拽:

class DraggableViewController: UIViewController {
    private var initialY: CGFloat = 0 // 记录初始位置,用于拖拽判断
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupBasicUI()
        setupPanGesture()
    }
    
    private func setupBasicUI() {
        view.backgroundColor = .white
        
        // 添加顶部拖拽把手
        let handleView = UIView()
        handleView.backgroundColor = .lightGray
        handleView.layer.cornerRadius = 3
        view.addSubview(handleView)
        handleView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            handleView.widthAnchor.constraint(equalToConstant: 60),
            handleView.heightAnchor.constraint(equalToConstant: 6),
            handleView.topAnchor.constraint(equalTo: view.topAnchor, constant: 16),
            handleView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }
    
    private func setupPanGesture() {
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        view.addGestureRecognizer(panGesture)
    }
    
    @objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: view)
        let screenHeight = UIScreen.main.bounds.height
        
        switch gesture.state {
        case .began:
            initialY = view.frame.origin.y // 记录拖拽前的位置
        case .changed:
            // 限制拖拽范围,不能拖到屏幕顶部以上
            let newY = max(initialY + translation.y, view.safeAreaInsets.top)
            view.frame.origin.y = newY
        case .ended, .cancelled:
            let velocity = gesture.velocity(in: view)
            // 判断触发全屏/还原的条件:上滑速度快 或者 拖拽位置超过屏幕1/4高度
            if velocity.y < -300 || view.frame.origin.y < screenHeight / 4 {
                UIView.animate(withDuration: 0.3) {
                    self.view.frame = UIScreen.main.bounds // 全屏
                }
            } else {
                UIView.animate(withDuration: 0.3) {
                    self.view.frame.origin.y = screenHeight - (screenHeight / 2) // 回到半屏
                }
            }
        default:
            break
        }
    }
}

3. 在主ViewController中触发弹出

最后在你的主ViewController里,加个按钮点击事件,弹出这个半屏ViewController,并指定我们自定义的PresentationController:

class MainViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        // 创建弹出按钮
        let presentButton = UIButton(type: .system)
        presentButton.setTitle("弹出半屏VC", for: .normal)
        presentButton.addTarget(self, action: #selector(presentHalfScreenVC), for: .touchUpInside)
        view.addSubview(presentButton)
        presentButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            presentButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            presentButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    @objc private func presentHalfScreenVC() {
        let draggableVC = DraggableViewController()
        draggableVC.modalPresentationStyle = .custom
        draggableVC.transitioningDelegate = self
        present(draggableVC, animated: true)
    }
}

// 实现协议,返回自定义的PresentationController
extension MainViewController: UIViewControllerTransitioningDelegate {
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        return HalfScreenPresentationController(presentedViewController: presented, presenting: presenting)
    }
}

额外优化提示

  • 半屏高度可以根据需求调整,比如把frameOfPresentedViewInContainerView里的高度改成固定值或者其他比例
  • 触发全屏的阈值(比如screenHeight / 4)可以根据交互体验微调,让触发逻辑更符合用户习惯
  • 如果需要支持屏幕旋转,记得在HalfScreenPresentationController中重写containerViewWillLayoutSubviews()方法,重新计算presentedView的frame

内容的提问来源于stack exchange,提问作者ayjoy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:49:10