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

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替代导航栏,只需:

  1. 隐藏系统导航栏
  2. 将CollectionView的top约束设置为view.topAnchor
  3. 将Header高度设为屏幕的25%
    这样Header就会显示在页面最顶部,同时还能利用CollectionView的滚动特性(如果需要)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:42:17