iOS开发:如何实现目标App同款自定义顶部导航栏
解决方案:复刻自定义导航栏(含跳转一致性处理)
根据你描述的需求(顶部导航栏占视图25%高度、右上角有操作按钮、跳转后保持导航栏样式),结合你尝试过的方案,下面分两种场景给出可行的实现方式:
方案1:改造系统UINavigationBar(推荐,保留系统导航栏交互逻辑)
iOS 11+之后,系统导航栏的布局由AutoLayout管理,直接修改frame不会生效,需要通过自定义导航栏子类来调整高度,同时解决titleView宽度不足的问题:
步骤1:自定义UINavigationBar子类
class CustomNavigationBar: UINavigationBar { // 自定义导航栏高度,设为屏幕高度的25% private let customHeight: CGFloat = UIScreen.main.bounds.height * 0.25 override func sizeThatFits(_ size: CGSize) -> CGSize { return CGSize(width: UIScreen.main.bounds.width, height: customHeight) } override func layoutSubviews() { super.layoutSubviews() // 调整导航栏内部元素的布局 for subview in self.subviews { let classString = NSStringFromClass(subview.classForCoder) // 调整导航栏背景视图高度 if classString.contains("UIBarBackground") { subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: customHeight) } // 调整标题容器的位置(避开状态栏) else if classString.contains("UINavigationBarContentView") { let statusBarHeight = UIApplication.shared.statusBarFrame.height subview.frame = CGRect( x: subview.frame.origin.x, y: statusBarHeight, width: subview.frame.width, height: customHeight - statusBarHeight ) } } } }
步骤2:在ViewController中配置自定义导航栏
在viewDidLoad中替换系统导航栏,并设置自定义titleView和右上角按钮:
override func viewDidLoad() { super.viewDidLoad() // 替换为自定义导航栏 let customNavBar = CustomNavigationBar() navigationController?.navigationBar = customNavBar // 设置自适应宽度的titleView(用AutoLayout替代固定frame) let helperView = UIView() helperView.backgroundColor = .red helperView.translatesAutoresizingMaskIntoConstraints = false navigationItem.titleView = helperView // 约束titleView充满导航栏可用区域 let statusBarHeight = UIApplication.shared.statusBarFrame.height NSLayoutConstraint.activate([ helperView.widthAnchor.constraint(equalTo: customNavBar.widthAnchor), helperView.heightAnchor.constraint(equalToConstant: customNavBar.customHeight - statusBarHeight) ]) // 添加右上角操作按钮 let settingBtn = UIBarButtonItem(title: "设置", style: .plain, target: self, action: #selector(settingAction)) let weatherBtn = UIBarButtonItem(title: "天气", style: .plain, target: self, action: #selector(weatherAction)) navigationItem.rightBarButtonItems = [settingBtn, weatherBtn] } @objc private func settingAction() { // 处理设置按钮点击逻辑 } @objc private func weatherAction() { // 处理天气按钮点击逻辑 }
步骤3:保证跳转后导航栏一致
创建一个BaseViewController父类,将上述导航栏配置逻辑放在父类的viewDidLoad中,后续所有需要显示该导航栏的页面都继承自这个父类,就能保证跳转后样式统一。
方案2:完全自定义导航栏(隐藏系统导航栏,自由度更高)
如果目标App的导航栏完全脱离系统交互逻辑,直接隐藏系统导航栏,自己实现顶部视图:
步骤1:隐藏系统导航栏
override func viewDidLoad() { super.viewDidLoad() navigationController?.setNavigationBarHidden(true, animated: false) }
步骤2:创建可复用的自定义导航栏视图
class CustomNavBarView: UIView { // 定义按钮点击回调 var onSettingTap: (() -> Void)? var onWeatherTap: (() -> Void)? override init(frame: CGRect) { super.init(frame: frame) setupUI() } required init?(coder: NSCoder) { super.init(coder: coder) setupUI() } private func setupUI() { backgroundColor = .white // 添加标题标签 let titleLabel = UILabel() titleLabel.text = "自定义标题" titleLabel.textAlignment = .center titleLabel.translatesAutoresizingMaskIntoConstraints = false addSubview(titleLabel) // 添加设置按钮 let settingBtn = UIButton(type: .system) settingBtn.setTitle("设置", for: .normal) settingBtn.addTarget(self, action: #selector(settingTapped), for: .touchUpInside) settingBtn.translatesAutoresizingMaskIntoConstraints = false addSubview(settingBtn) // 添加天气按钮 let weatherBtn = UIButton(type: .system) weatherBtn.setTitle("天气", for: .normal) weatherBtn.addTarget(self, action: #selector(weatherTapped), for: .touchUpInside) weatherBtn.translatesAutoresizingMaskIntoConstraints = false addSubview(weatherBtn) // 布局约束 NSLayoutConstraint.activate([ titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor), titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor), settingBtn.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16), settingBtn.centerYAnchor.constraint(equalTo: centerYAnchor), weatherBtn.trailingAnchor.constraint(equalTo: settingBtn.leadingAnchor, constant: -16), weatherBtn.centerYAnchor.constraint(equalTo: centerYAnchor) ]) } @objc private func settingTapped() { onSettingTap?() } @objc private func weatherTapped() { onWeatherTap?() } }
步骤3:在ViewController中添加自定义导航栏
override func viewDidLoad() { super.viewDidLoad() navigationController?.setNavigationBarHidden(true, animated: false) // 创建自定义导航栏,高度设为屏幕的25% let navBarHeight = UIScreen.main.bounds.height * 0.25 let customNavBar = CustomNavBarView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: navBarHeight)) // 设置按钮回调 customNavBar.onSettingTap = { [weak self] in // 处理设置点击 } customNavBar.onWeatherTap = { [weak self] in // 处理天气点击 } view.addSubview(customNavBar) // 调整CollectionView的布局,使其紧贴导航栏底部 sampleCollectionView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ sampleCollectionView.topAnchor.constraint(equalTo: customNavBar.bottomAnchor), sampleCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), sampleCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), sampleCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) }
步骤4:跳转后保持导航栏一致
同样将自定义导航栏的添加逻辑封装到BaseViewController中,后续页面继承该父类即可实现全局统一的导航栏样式。
关于你尝试的CollectionView Header方案
你之前的Header显示在导航栏下方,是因为系统导航栏未隐藏且CollectionView的top约束指向了导航栏底部。如果想用Header替代导航栏,只需:
- 隐藏系统导航栏
- 将CollectionView的top约束设置为
view.topAnchor - 将Header高度设为屏幕的25%
这样Header就会显示在页面最顶部,同时还能利用CollectionView的滚动特性(如果需要)。
内容的提问来源于stack exchange,提问作者Pangu
相关产品推荐
相关产品推荐

