Swift如何在UIViewController中创建适配的UINavigationController
问题背景
- 技术栈:Swift + Storyboard 开发iOS多场景应用,需求是为所有视图控制器配置UINavigationController,代码实现、Storyboard可视化实现两种方案均可。
- 此前尝试手动创建UINavigationBar实现导航效果,存在两个问题:
- 固定frame布局无法适配所有尺寸的iPhone机型
- 导航栏上的按钮无法响应点击事件
- 原有错误实现代码如下:
override func viewDidLoad() { super.viewDidLoad() let height: CGFloat = 50 let width = self.view.frame.width let navbar = UINavigationBar(frame: CGRect(x: 0, y: 40, width: width, height: height)) navbar.backgroundColor = UIColor.white navbar.delegate = self let navItem = UINavigationItem() navItem.title = "Last Call" navItem.rightBarButtonItem = UIBarButtonItem(title: "Add New", style: .plain, target: self, action: nil) navbar.items = [navItem] view.addSubview(navbar) self.view.frame = CGRect(x: 0, y: height, width: width, height: (UIScreen.main.bounds.height - height)) }
- 另外尝试过直接给视图控制器嵌入Navigation Controller,最终展示效果不符合预期。
问题根因
- 手动添加UINavigationBar的实现违反UIKit设计规范:硬编码y轴偏移40pt、导航栏高度50pt,没有考虑刘海/灵动岛机型的安全区规则、不同设备的屏幕尺寸差异,同时强行修改当前控制器view的frame会破坏系统默认的布局适配逻辑,必然出现错位。
- 导航按钮无响应的直接原因:创建
UIBarButtonItem时action参数传入nil,没有绑定实际的响应方法,点击事件无法触发。 - 直接嵌入导航控制器效果异常,一般是嵌入流程错误,没有将导航控制器设置为对应场景的入口根控制器,或者自定义导航栏属性时破坏了系统默认布局。
正确实现方案
方案1:Storyboard可视化实现(推荐)
- 打开Storyboard文件,选中需要配置导航栏的目标视图控制器。
- 点击Xcode顶部菜单栏路径:
Editor->Embed In->Navigation Controller,系统会自动创建UINavigationController实例,将当前选中的控制器设置为导航控制器的根视图,自动完成安全区适配、导航栏默认布局逻辑。 - 多场景应用不需要给每个子页面重复嵌入导航控制器,只需要给每个独立入口场景(比如TabBar的每个分页、模态跳转的根页面)执行上述嵌入操作即可,后续push的子页面会自动复用当前导航栈的导航栏。
- 导航栏内容直接在对应控制器内通过
navigationItem配置即可,不需要手动创建UINavigationBar实例。
方案2:纯代码实现
不要手动将UINavigationBar作为子视图添加到控制器view上,正确做法是将目标控制器包装为UINavigationController的根控制器,在页面入口处完成导航控制器初始化:
// 入口处初始化示例(可在SceneDelegate、AppDelegate,或者跳转触发逻辑中编写) let targetVC = YourCustomViewController() let navController = UINavigationController(rootViewController: targetVC) // 作为窗口根控制器使用 window?.rootViewController = navController // 作为模态跳转页面使用 // present(navController, animated: true)
在目标控制器内部配置导航栏内容时,不要硬编码布局参数,通过系统提供的接口配置即可,同时给按钮绑定实际的响应方法:
override func viewDidLoad() { super.viewDidLoad() // 配置导航栏标题 navigationItem.title = "Last Call" // 配置右侧按钮,必须绑定对应action方法,不能传nil navigationItem.rightBarButtonItem = UIBarButtonItem( title: "Add New", style: .plain, target: self, action: #selector(addNewAction) ) } // 实现按钮点击响应方法 @objc private func addNewAction() { // 编写新增按钮的业务逻辑 }
核心注意点:不要硬编码导航栏的高度、y轴起始坐标,UIKit会自动根据设备状态栏高度、安全区域规则调整导航栏的位置和尺寸,手动写死frame必然出现多机型适配问题。
内容的提问来源于stack exchange,提问作者Mariglen Meta
相关产品推荐
相关产品推荐

