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

如何以编程方式为汉堡菜单添加子视图控制器并绑定手势

嘿,我来帮你搞定这个汉堡菜单的控制器整合问题!之前用UIView实现的逻辑迁移到双控制器模式其实不难,核心是把你的汉堡菜单类改成容器控制器,负责管理jobListController和sideBarController的生命周期、布局,再把原来的手势逻辑绑定到合适的视图上就行。我给你一步步拆解:

第一步:把汉堡菜单改成容器控制器

首先你需要一个自定义的UIViewController作为容器(比如叫HamburgerMenuController),它会作为两个子控制器的父容器,统一管理布局和交互。

第二步:添加两个子控制器到容器

容器控制器必须正确处理子控制器的生命周期,不能直接只加view,要调用addChild和didMove(toParent:)方法。这里分两种布局方式给你示例:

方式1:用Frame布局(和你原来UIView的逻辑最接近)

class HamburgerMenuController: UIViewController {
    // 初始化你的两个子控制器
    private let jobListController = JobListController()
    private let sideBarController = SideBarController()
    
    // 侧边栏宽度,可根据需求调整
    private let sidebarWidth: CGFloat = UIScreen.main.bounds.width * 0.75
    private var isSidebarOpen = false

    override func viewDidLoad() {
        super.viewDidLoad()
        setupChildControllers()
        setupGestures() // 迁移原来的手势逻辑
    }

    private func setupChildControllers() {
        // 1. 添加jobListController(根控制器,占满屏幕)
        addChild(jobListController)
        jobListController.view.frame = view.bounds
        view.addSubview(jobListController.view)
        jobListController.didMove(toParent: self)

        // 2. 添加sideBarController(初始隐藏在屏幕左侧)
        addChild(sideBarController)
        let sidebarFrame = CGRect(x: -sidebarWidth, y: 0, width: sidebarWidth, height: view.bounds.height)
        sideBarController.view.frame = sidebarFrame
        view.addSubview(sideBarController.view)
        sideBarController.didMove(toParent: self)

        // 确保jobList在侧边栏上方,这样滑动时能露出下面的侧边栏
        view.bringSubviewToFront(jobListController.view)
    }
}

方式2:用AutoLayout布局(更适配多屏幕)

如果原来用的是AutoLayout,建议用这种方式,把约束存为属性方便动画修改:

class HamburgerMenuController: UIViewController {
    private let jobListController = JobListController()
    private let sideBarController = SideBarController()
    private let sidebarWidth: CGFloat = UIScreen.main.bounds.width * 0.75
    private var isSidebarOpen = false
    // 存储jobList的leading约束,用于动画
    private var jobListLeadingConstraint: NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupChildControllersWithAutoLayout()
        setupGestures()
    }

    private func setupChildControllersWithAutoLayout() {
        // 添加jobListController
        addChild(jobListController)
        jobListController.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(jobListController.view)
        jobListController.didMove(toParent: self)

        // 添加sideBarController
        addChild(sideBarController)
        sideBarController.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(sideBarController.view)
        sideBarController.didMove(toParent: self)

        // 配置约束
        NSLayoutConstraint.activate([
            // 侧边栏约束:左对齐、上下铺满、固定宽度
            sideBarController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            sideBarController.view.topAnchor.constraint(equalTo: view.topAnchor),
            sideBarController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            sideBarController.view.widthAnchor.constraint(equalToConstant: sidebarWidth),
            
            // jobList约束:上下右铺满,leading约束动态修改
            jobListController.view.topAnchor.constraint(equalTo: view.topAnchor),
            jobListController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            jobListController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ])

        // 单独存储leading约束,用于动画
        jobListLeadingConstraint = jobListController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0)
        jobListLeadingConstraint?.isActive = true

        view.bringSubviewToFront(jobListController.view)
    }
}

第三步:迁移原来的手势逻辑

原来绑定在UIView上的手势,现在只需要把手势添加到jobListController.view(或者容器的view,看你的交互需求),然后把手势处理逻辑里的view替换成两个子控制器的view即可:

示例:平移手势处理(对应Frame布局)

extension HamburgerMenuController {
    private func setupGestures() {
        // 添加平移手势到jobList的view上(和你原来绑定UIView的逻辑一致)
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        jobListController.view.addGestureRecognizer(panGesture)

        // 如果原来有点击按钮触发菜单的逻辑,也可以在这里绑定(比如给jobList里的菜单按钮加闭包通知)
    }

    @objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: view)
        let velocity = gesture.velocity(in: view)

        switch gesture.state {
        case .changed:
            // 拖动时同步更新两个控制器的位置
            let newX = max(0, min(translation.x, sidebarWidth))
            jobListController.view.frame.origin.x = newX
            sideBarController.view.frame.origin.x = newX - sidebarWidth
        case .ended:
            // 根据拖动速度和距离判断是否打开/关闭菜单
            let shouldOpen = (velocity.x > 150) || (jobListController.view.frame.origin.x > sidebarWidth / 2)
            UIView.animate(withDuration: 0.3) {
                if shouldOpen {
                    self.jobListController.view.frame.origin.x = self.sidebarWidth
                    self.sideBarController.view.frame.origin.x = 0
                    self.isSidebarOpen = true
                } else {
                    self.jobListController.view.frame.origin.x = 0
                    self.sideBarController.view.frame.origin.x = -self.sidebarWidth
                    self.isSidebarOpen = false
                }
            }
        default: break
        }
    }
}

如果用AutoLayout,手势处理改成修改约束:

@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
    let translation = gesture.translation(in: view)
    let velocity = gesture.velocity(in: view)

    switch gesture.state {
    case .changed:
        let newConstant = max(0, min(translation.x, sidebarWidth))
        jobListLeadingConstraint?.constant = newConstant
    case .ended:
        let shouldOpen = (velocity.x > 150) || (jobListLeadingConstraint?.constant ?? 0 > sidebarWidth / 2)
        UIView.animate(withDuration: 0.3) {
            self.jobListLeadingConstraint?.constant = shouldOpen ? self.sidebarWidth : 0
            self.view.layoutIfNeeded() // 触发约束动画
            self.isSidebarOpen = shouldOpen
        }
    default: break
    }
}

可能踩坑的点(你之前失败的原因)

  1. 子控制器生命周期没处理:必须调用addChild和didMove(toParent:),否则子控制器的生命周期方法(比如viewDidLoad)可能不会正常触发。
  2. 视图层级错误:一定要把jobListController.view放在最上层,否则滑动时看不到jobList的拖动效果。
  3. 手势冲突:如果jobListController是导航控制器,它自带的侧滑返回手势会和你的汉堡菜单手势冲突,这时候可以设置手势代理,允许同时识别:
extension HamburgerMenuController: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

记得给你的pan手势设置delegate = self。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:36:20