多朝向初始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
相关产品推荐
相关产品推荐

