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

求助:Swift中实现交互式Push View Controller的方案

实现从右侧边缘拖动的交互式Push View Controller

方案一:纯Swift手动实现交互式Push

你当前的代码只是触发push动作,没有跟随拖动进度的交互动画。要实现带进度的交互式push,需要自定义导航控制器的转场逻辑,结合手势进度控制动画:

1. 自定义导航控制器(处理手势与转场)

class InteractivePushNavController: UINavigationController, UINavigationControllerDelegate {
    private var interactiveTransition: UIPercentDrivenInteractiveTransition?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
    
    // 指定push转场的动画器
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return operation == .push ? PushTransitionAnimator() : nil
    }
    
    // 关联交互式转场
    func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactiveTransition
    }
    
    // 给子控制器添加右侧边缘拖动手势
    func setupRightEdgePan(for vc: UIViewController) {
        let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        pan.edges = .right
        vc.view.addGestureRecognizer(pan)
    }
    
    @objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: view)
        let progress = abs(translation.x) / view.bounds.width
        
        switch gesture.state {
        case .began:
            interactiveTransition = UIPercentDrivenInteractiveTransition()
            // 让子控制器提供要push的目标VC
            if let targetVC = (topViewController as? PushTargetProvider)?.getTargetViewController() {
                pushViewController(targetVC, animated: true)
            }
        case .changed:
            interactiveTransition?.update(progress)
        case .ended, .cancelled:
            let velocity = gesture.velocity(in: view)
            // 进度过半或拖动速度足够时完成push,否则取消
            if progress > 0.5 || velocity.x < -500 {
                interactiveTransition?.finish()
            } else {
                interactiveTransition?.cancel()
            }
            interactiveTransition = nil
        default:
            interactiveTransition?.cancel()
            interactiveTransition = nil
        }
    }
}

// 协议:让子控制器提供要push的目标页面
protocol PushTargetProvider {
    func getTargetViewController() -> UIViewController?
}

2. 自定义转场动画器

class PushTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.35
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromVC = transitionContext.viewController(forKey: .from),
              let toVC = transitionContext.viewController(forKey: .to) else {
            transitionContext.completeTransition(false)
            return
        }
        
        let container = transitionContext.containerView
        toVC.view.frame = CGRect(x: container.bounds.width, y: 0, width: container.bounds.width, height: container.bounds.height)
        container.addSubview(toVC.view)
        
        UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
            fromVC.view.frame = CGRect(x: -container.bounds.width * 0.3, y: 0, width: container.bounds.width, height: container.bounds.height)
            toVC.view.frame = container.bounds
        } finished: { success in
            fromVC.view.frame = container.bounds // 恢复原位置
            transitionContext.completeTransition(success && !transitionContext.transitionWasCancelled)
        }
    }
}

3. 在子控制器中使用

class SourceVC: UIViewController, PushTargetProvider {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        
        // 绑定导航控制器的手势
        if let nav = navigationController as? InteractivePushNavController {
            nav.setupRightEdgePan(for: self)
        }
    }
    
    func getTargetViewController() -> UIViewController? {
        let targetVC = TargetVC()
        targetVC.view.backgroundColor = .lightGray
        targetVC.title = "目标页面"
        return targetVC
    }
}

class TargetVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "目标页面"
    }
}

方案二:在Swift中使用RTInteractivePush

这个OC库可以直接在Swift中调用,只需两步:

1. 配置桥接文件

如果项目没有桥接文件,新建一个命名为你的项目名-Bridging-Header.h的文件,在Build Settings的Objective-C Bridging Header中设置文件路径,然后添加:

#import <RTInteractivePush/RTInteractivePush.h>

2. 启用交互式push

在导航控制器中开启功能并设置触发边缘:

// 示例在AppDelegate中配置
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let nav = UINavigationController(rootViewController: SourceVC())
    nav.interactivePushEnabled = true
    nav.interactivePushEdge = .right // 设置为右侧边缘触发
    
    window = UIWindow(frame: UIScreen.main.bounds)
    window?.rootViewController = nav
    window?.makeKeyAndVisible()
    return true
}

或者在单个控制器的viewDidLoad中设置:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationController?.interactivePushEnabled = true
    navigationController?.interactivePushEdge = .right
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 15:35:35