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

多朝向初始VC与竖屏限制目标VC间手势驱动转场实现问询

解决不同朝向VC间的自定义手势驱动转场问题

针对你遇到的「固定竖屏的初始VC与支持多朝向的目标VC间手势转场失效」问题,我们可以从界面朝向控制和手势转场适配两个核心点入手,完美复刻原生相机App的交互效果:

1. 先解决界面朝向的核心冲突

原生相机的本质是:相机VC本身锁定竖屏,但允许弹出的图库VC自由旋转。要实现这个效果,不能直接让presenting VC(相机)强制锁定竖屏,否则presented VC(图库)会被限制朝向——我们需要做一个临时朝向开关:

1.1 全局与相机VC的朝向配置

  • 先在App的Deployment Info中勾选所有需要支持的朝向(比如Portrait、Landscape Left/Right)。
  • 给相机VC添加一个开关变量,控制是否临时放开朝向限制:
    class CameraViewController: UIViewController {
        // 临时开关:图库显示期间允许所有朝向
        var shouldAllowAllOrientations = false
        
        override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
            return shouldAllowAllOrientations ? .allButUpsideDown : .portrait
        }
    }
    

1.2 图库VC的朝向与恢复逻辑

图库VC可以自由旋转,同时在消失时恢复相机VC的竖屏锁定:

class GalleryViewController: UIViewController {
    // 支持所有非倒置的朝向
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .allButUpsideDown
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // 恢复相机VC的竖屏限制
        if let cameraVC = presentingViewController as? CameraViewController {
            cameraVC.shouldAllowAllOrientations = false
            // 强制设备回到竖屏
            UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
        }
    }
}

1.3 弹出图库时的朝向处理

present图库时,打开相机VC的朝向开关,并强制刷新设备朝向:

extension CameraViewController {
    func presentGallery() {
        let galleryVC = GalleryViewController()
        galleryVC.modalPresentationStyle = .fullScreen // 必须用fullScreen才能让图库控制朝向
        galleryVC.transitioningDelegate = self // 绑定自定义转场代理
        shouldAllowAllOrientations = true
        present(galleryVC, animated: true) { [weak self] in
            // 转场完成后,同步设备物理朝向
            UIDevice.current.setValue(UIDevice.current.orientation.rawValue, forKey: "orientation")
        }
    }
}

2. 实现手势驱动的自定义转场

采用UIViewControllerAnimatedTransitioning + UIPercentDrivenInteractiveTransition方案,同时适配不同朝向的手势坐标计算:

2.1 自定义转场动画器

实现滑入滑出的动画逻辑,模拟原生相机的拖动手感:

class GalleryTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    let isPresenting: Bool
    let duration: TimeInterval = 0.3
    
    init(isPresenting: Bool) {
        self.isPresenting = isPresenting
        super.init()
    }
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let container = transitionContext.containerView
        let toVC = transitionContext.viewController(forKey: .to)!
        let fromVC = transitionContext.viewController(forKey: .from)!
        
        if isPresenting {
            // 弹出动画:图库从底部滑入
            toVC.view.frame = CGRect(x: 0, y: container.bounds.height, width: container.bounds.width, height: container.bounds.height)
            container.addSubview(toVC.view)
            
            UIView.animate(withDuration: duration, animations: {
                toVC.view.frame = container.bounds
            }) { _ in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
        } else {
            // 关闭动画:图库滑出底部
            UIView.animate(withDuration: duration, animations: {
                fromVC.view.frame = CGRect(x: 0, y: container.bounds.height, width: container.bounds.width, height: container.bounds.height)
            }) { _ in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
        }
    }
}

2.2 手势与交互转场控制

在相机VC中添加拖动手势,配合UIPercentDrivenInteractiveTransition控制转场进度:

extension CameraViewController {
    private var interactiveTransition: UIPercentDrivenInteractiveTransition?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 注意:手势要添加到图库VC的视图上,可在present完成后添加
        NotificationCenter.default.addObserver(self, selector: #selector(addPanGestureToGallery), name: NSNotification.Name("GalleryDidPresent"), object: nil)
    }
    
    @objc private func addPanGestureToGallery() {
        guard let galleryVC = presentedViewController as? GalleryViewController else { return }
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
        galleryVC.view.addGestureRecognizer(panGesture)
    }
    
    @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
        guard let galleryVC = presentedViewController as? GalleryViewController else { return }
        
        // 基于当前图库视图的坐标系计算拖动进度(适配所有朝向)
        let translation = gesture.translation(in: galleryVC.view)
        let progress = translation.y / galleryVC.view.bounds.height
        
        switch gesture.state {
        case .began:
            interactiveTransition = UIPercentDrivenInteractiveTransition()
            dismiss(animated: true)
        case .changed:
            interactiveTransition?.update(progress)
        case .ended, .cancelled:
            // 进度超过50%则完成关闭,否则回弹
            let shouldComplete = progress > 0.5
            shouldComplete ? interactiveTransition?.finish() : interactiveTransition?.cancel()
            interactiveTransition = nil
        default:
            break
        }
    }
}

2.3 绑定转场代理

让相机VC遵守UIViewControllerTransitioningDelegate,提供动画器和交互控制器:

extension CameraViewController: UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return GalleryTransitionAnimator(isPresenting: true)
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return GalleryTransitionAnimator(isPresenting: false)
    }
    
    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactiveTransition
    }
}

3. 关键问题修复说明

  • 之前第一种方案失效:是因为手势坐标没有适配界面朝向,我们直接基于图库VC的视图坐标系计算进度,不管设备怎么转,都能正确识别拖动距离。
  • 之前第二种方案失效:.overFullScreen会让presenting VC依然控制设备朝向,换成.fullScreen并配合临时朝向开关,就能让图库VC自由旋转。
  • 相机VC固定UI的实现:如果要让相机的部分按钮不随旋转转动,可以把这些按钮放在单独的UIView里,监听UIDeviceOrientationDidChangeNotification手动旋转这个view的transform,保持VC本身的supportedInterfaceOrientations逻辑不变。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:27:09