仅使用XIB开发Objective-C自定义可滚动TabBar控制器的问题
纯XIB实现自定义可滚动TabBar控制器(无动画切换+消除多余导航按钮)
我明白你的需求:纯XIB打造可滚动TabBar,切换控制器时不要多余的导航按钮,并且实现和系统TabBarController一致的无动画切换效果。下面是具体的实现方案,一步步来解决你的问题:
一、容器控制器的XIB布局
首先你需要一个容器ViewController(比如叫CustomTabBarController),用它来管理子控制器和自定义TabBar:
- 在XIB中给容器控制器添加两个核心视图:
- 一个
UIView(命名为contentView):用来承载当前显示的子控制器的View,约束设置为顶部、左侧、右侧与容器View对齐,底部与TabBar的顶部对齐。 - 自定义TabBar:你已经完成的包含
UIScrollView和4个按钮的View,约束设置为底部、左侧、右侧与容器View对齐,高度固定(比如49pt)。
- 一个
- 完善UIScrollView的内部约束:
- 给ScrollView添加一个内容容器View(避免直接放按钮的布局问题),内容容器View的上下左右与ScrollView对齐,高度等于ScrollView的高度,同时设置
width约束(如果按钮数量固定,也可以让按钮的总宽度作为内容宽度)。 - 把4个Tab按钮水平排列在内容容器View里,每个按钮设置固定宽度和高度(等于内容容器View的高度),按钮之间可以加间距,最后一个按钮的trailing与内容容器View的trailing对齐,这样ScrollView会自动计算contentSize实现水平滚动。
- 给ScrollView添加一个内容容器View(避免直接放按钮的布局问题),内容容器View的上下左右与ScrollView对齐,高度等于ScrollView的高度,同时设置
二、子控制器的管理与初始化
在CustomTabBarController中添加代码管理子控制器:
import UIKit class CustomTabBarController: UIViewController { @IBOutlet weak var contentView: UIView! @IBOutlet weak var tabScrollView: UIScrollView! // 关联你的4个Tab按钮 @IBOutlet var tabButtons: [UIButton]! // 子控制器数组 private var childViewControllers: [UIViewController] = [] // 当前显示的子控制器 private var currentDisplayedVC: UIViewController? override func viewDidLoad() { super.viewDidLoad() // 初始化子控制器(这里用你自己的控制器类替换) let vc1 = FirstViewController(nibName: "FirstViewController", bundle: nil) let vc2 = SecondViewController(nibName: "SecondViewController", bundle: nil) let vc3 = ThirdViewController(nibName: "ThirdViewController", bundle: nil) let vc4 = FourthViewController(nibName: "FourthViewController", bundle: nil) childViewControllers = [vc1, vc2, vc3, vc4] // 默认显示第一个控制器 if let firstVC = childViewControllers.first { addChildViewController(firstVC) currentDisplayedVC = firstVC } // 设置Tab按钮默认选中状态 tabButtons.first?.isSelected = true } // 添加子控制器的辅助方法 private func addChildViewController(_ vc: UIViewController) { addChild(vc) vc.view.frame = contentView.bounds // 适配屏幕旋转 vc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] contentView.addSubview(vc.view) vc.didMove(toParent: self) } }
三、实现无动画切换+消除多余导航按钮
问题的核心是:不要用push/present来切换控制器(这会导致导航栈叠加出现返回按钮),而是直接替换contentView中的子控制器View,并且全程无动画:
给每个Tab按钮绑定点击事件tabButtonTapped:,添加如下代码:
@IBAction func tabButtonTapped(_ sender: UIButton) { guard let selectedIndex = tabButtons.firstIndex(of: sender), selectedIndex < childViewControllers.count else { return } // 如果点击的是当前选中的按钮,直接返回 if let currentVC = currentDisplayedVC, childViewControllers[selectedIndex] == currentVC { return } // 移除当前显示的子控制器 currentDisplayedVC?.willMove(toParent: nil) currentDisplayedVC?.view.removeFromSuperview() currentDisplayedVC?.removeFromParent() // 添加并显示新的子控制器(无动画) let targetVC = childViewControllers[selectedIndex] addChildViewController(targetVC) currentDisplayedVC = targetVC // 更新Tab按钮选中状态 tabButtons.forEach { $0.isSelected = ($0 == sender) } // 可选:如果容器本身在NavigationController中,隐藏返回按钮 navigationItem.hidesBackButton = true }
为什么这样能消除导航按钮?
因为我们是直接将子控制器作为容器的子控制器来管理,而不是加入到导航栈中,所以不会触发导航栏的返回按钮逻辑。如果你的子控制器需要自定义导航栏内容,直接在子控制器的viewWillAppear中设置navigationItem即可,比如:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationItem.title = "首页" // 可以自定义右侧按钮等,不会出现多余的返回按钮 }
四、额外优化点
- 给Tab按钮添加选中样式:比如修改文字颜色、背景色或者下划线,让用户更清晰看到当前选中的Tab。
- 处理ScrollView的滚动定位:当选中的Tab不在可视区域时,自动滚动到对应位置,可以在
tabButtonTapped:中添加:tabScrollView.scrollRectToVisible(sender.frame, animated: true) - 适配暗黑模式:给TabBar和按钮设置动态颜色,确保在暗黑模式下正常显示。
内容的提问来源于stack exchange,提问作者Uddiptta Ujjawal
相关产品推荐
相关产品推荐

