动态添加UITabBarItem后切换页面崩溃,IBOutlet为空如何解决?
问题描述
现有通过Storyboard创建的UITabBarController,已包含Home和Settings两个Tab页。根据条件动态添加两个对应Program和Report标签的ProgramReportVC实例,但切换到这两个标签页时App崩溃,报错:
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
崩溃原因是ProgramReportVC中的@IBOutlet var skeletonView: UIView!等IBOutlet属性均为nil。
相关代码
MainTabbarController代码
class MainTabbarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() addedConditionalTabs(isInserted: true) } // 根据条件在现有Tab中插入新标签页 func addedConditionalTabs(isInserted: Bool){ if isInserted{ let programVC = ProgramReportVC() // ProgramReportVC 继承自 UIViewController programVC.screenTitle = "Program" let reportVC = ProgramReportVC() reportVC.screenTitle = "Report" programVC.tabBarItem = UITabBarItem(title: "Program", image: UIImage(named: "ProgramImage"), tag: 2) reportVC.tabBarItem = UITabBarItem(title: "Report", image: UIImage(named: "ReportImage"), tag: 3) self.viewControllers?.insert(programVC, at: 2) self.viewControllers?.insert(reportVC, at: 3) } } }
ProgramReportVC代码
@IBOutlet var skeletonView: UIView! @IBOutlet var views: [UIView]! @IBOutlet var subviews: [UIView]! override func viewDidLoad() { super.viewDidLoad() self.showSkeleton() } func showSkeleton() { self.skeletonView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) self.view.addSubview(self.skeletonView) self.view.bringSubviewToFront(self.skeletonView) let gradient = SkeletonGradient(baseColor: UIColor(named: "skeletonColor")!) let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftRight) self.skeletonView.isSkeletonable = true for items in views { items.isSkeletonable = true } for items in subviews { items.isSkeletonable = true items.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation) } }
错误原因
直接通过ProgramReportVC()初始化控制器属于纯代码实例化,不会加载关联的Storyboard布局文件,导致所有通过Storyboard关联的IBOutlet属性都未被初始化,保持为nil。在viewDidLoad中调用showSkeleton()时,隐式解包这些nil属性就会触发崩溃。
解决方案
方案1:从Storyboard加载控制器(推荐)
因为你的IBOutlet是关联到Storyboard视图的,必须从对应的Storyboard中加载控制器才能完成IBOutlet的绑定。
步骤1:设置Storyboard ID
在Storyboard中选中ProgramReportVC,打开Identity Inspector,在Storyboard ID字段填写唯一标识(比如"ProgramReportVC"),确保勾选Use Storyboard ID。
步骤2:修改初始化代码
更新addedConditionalTabs方法,从Storyboard加载控制器:
func addedConditionalTabs(isInserted: Bool){ if isInserted{ // 从Main.storyboard加载ProgramReportVC,替换为你的Storyboard名称 guard let programVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ProgramReportVC") as? ProgramReportVC else { print("无法加载ProgramReportVC") return } programVC.screenTitle = "Program" guard let reportVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ProgramReportVC") as? ProgramReportVC else { print("无法加载ProgramReportVC") return } reportVC.screenTitle = "Report" programVC.tabBarItem = UITabBarItem(title: "Program", image: UIImage(named: "ProgramImage"), tag: 2) reportVC.tabBarItem = UITabBarItem(title: "Report", image: UIImage(named: "ReportImage"), tag: 3) self.viewControllers?.insert(programVC, at: 2) self.viewControllers?.insert(reportVC, at: 3) } }
方案2:纯代码构建视图(放弃Storyboard)
如果不想依赖Storyboard,需要手动创建所有视图并布局,移除IBOutlet关联:
class ProgramReportVC: UIViewController { // 移除IBOutlet,改为手动创建视图 var skeletonView: UIView! var views: [UIView] = [] var subviews: [UIView] = [] override func viewDidLoad() { super.viewDidLoad() setupViews() showSkeleton() } // 手动创建并布局所有视图 private func setupViews() { // 创建skeletonView skeletonView = UIView() skeletonView.backgroundColor = UIColor(named: "skeletonColor")?.withAlphaComponent(0.5) ?? .lightGray skeletonView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(skeletonView) // 布局skeletonView NSLayoutConstraint.activate([ skeletonView.topAnchor.constraint(equalTo: view.topAnchor), skeletonView.leadingAnchor.constraint(equalTo: view.leadingAnchor), skeletonView.trailingAnchor.constraint(equalTo: view.trailingAnchor), skeletonView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) // 根据需求创建并添加其他views和subviews // 示例:创建一个子视图 let sampleSubview = UIView() sampleSubview.backgroundColor = .white sampleSubview.translatesAutoresizingMaskIntoConstraints = false view.addSubview(sampleSubview) subviews.append(sampleSubview) NSLayoutConstraint.activate([ sampleSubview.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), sampleSubview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), sampleSubview.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), sampleSubview.heightAnchor.constraint(equalToConstant: 60) ]) } func showSkeleton() { guard let skeletonColor = UIColor(named: "skeletonColor") else { print("无法获取skeletonColor") return } let gradient = SkeletonGradient(baseColor: skeletonColor) let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftRight) skeletonView.isSkeletonable = true for item in views { item.isSkeletonable = true } for item in subviews { item.isSkeletonable = true item.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation) } } }
内容的提问来源于stack exchange,提问作者kiran
相关产品推荐
相关产品推荐

