如何以编程方式为汉堡菜单添加子视图控制器并绑定手势
嘿,我来帮你搞定这个汉堡菜单的控制器整合问题!之前用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 } }
可能踩坑的点(你之前失败的原因)
- 子控制器生命周期没处理:必须调用
addChild和didMove(toParent:),否则子控制器的生命周期方法(比如viewDidLoad)可能不会正常触发。 - 视图层级错误:一定要把
jobListController.view放在最上层,否则滑动时看不到jobList的拖动效果。 - 手势冲突:如果
jobListController是导航控制器,它自带的侧滑返回手势会和你的汉堡菜单手势冲突,这时候可以设置手势代理,允许同时识别:
extension HamburgerMenuController: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
记得给你的pan手势设置delegate = self。
内容的提问来源于stack exchange,提问作者spoax
相关产品推荐
相关产品推荐

