UITabBarController中UINavigationController初始未完全包裹UIViewController问题
我把UITabBarController设置为根视图控制器,每个标签页对应一个UINavigationController,其初始UIViewController背景设为红色。但首次启动时,UINavigationController并没有完全包裹这个UIViewController,切换标签页后就恢复正常了。请问这个问题出在哪里?
附上相关代码:
AppDelegate 代码
window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = TabBarViewController() window?.makeKeyAndVisible()
TabBarViewController 代码
class TabBarViewController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() // Navigation Tab let navVC = NavigationViewController() // Departure Tab let depVC = DeparturesViewController() // Settings Tab let setVC = SettingsViewController() self.viewControllers = [ createNavigationController(title: "Navigation", rootViewController: navVC, imageName: "map"), createNavigationController(title: "Abfahrten", rootViewController: depVC, imageName: "station"), createNavigationController(title: "Einstellungen", rootViewController: setVC, imageName: "user"), ] } private func createNavigationController(title: String, rootViewController: UIViewController, imageName: String) -> UINavigationController { rootViewController.title = title let nc = UINavigationController(rootViewController: rootViewController) nc.title = title nc.view.backgroundColor = .white nc.navigationBar.prefersLargeTitles = true nc.navigationController?.navigationItem.largeTitleDisplayMode = .always nc.tabBarItem.image = UIImage(named: imageName)?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate) return nc } override func viewWillAppear(_ animated: Bool) { self.selectedIndex = 0 } }
NavigationViewController 代码
class NavigationViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .red } }
问题原因分析
这个问题的核心出在大标题(Large Title)的设置错误,以及首次启动时的布局时机问题:
无效的大标题设置代码:
在createNavigationController方法里,你写了nc.navigationController?.navigationItem.largeTitleDisplayMode = .always,但这里的nc本身就是UINavigationController,它的navigationController属性是nil(只有当导航控制器被另一个导航控制器包裹时才会有值),所以这行代码完全没有生效。这会导致首次启动时,导航栏的大标题布局没有被正确配置,进而影响根视图控制器的view布局范围。首次启动的布局时机:
在UITabBarController的viewDidLoad方法中创建并设置子控制器时,视图的布局约束还没有完全计算完成。首次渲染时,导航栏的高度和布局范围没有被正确传递给根VC,导致根VC的view没有占满导航控制器的可用区域;而切换标签页时,视图会触发重新布局,此时所有约束和布局参数都已正确计算,所以显示恢复正常。
解决方案
只需要修改createNavigationController方法中的大标题设置代码,将大标题的显示模式设置在根视图控制器的navigationItem上,而不是无效的nc.navigationController上:
private func createNavigationController(title: String, rootViewController: UIViewController, imageName: String) -> UINavigationController { rootViewController.title = title // 给根VC的navigationItem设置大标题显示模式 rootViewController.navigationItem.largeTitleDisplayMode = .always let nc = UINavigationController(rootViewController: rootViewController) nc.title = title nc.view.backgroundColor = .white nc.navigationBar.prefersLargeTitles = true nc.tabBarItem.image = UIImage(named: imageName)?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate) return nc }
另外,还有一个小优化:viewWillAppear中的self.selectedIndex = 0可以移除,因为UITabBarController默认的selectedIndex就是0,这行代码没有必要。
修改后,首次启动时导航栏的大标题布局会被正确配置,根VC的view也会正常填充导航控制器的可用区域,不会再出现布局异常的问题。
内容的提问来源于stack exchange,提问作者Lukas Kirner

