如何让UIViewController子类自动加载父类XIB文件?
问题描述
假设SomeViewController是BaseViewController的子类。调用BaseViewController()会自动从BaseViewController.xib加载视图;调用SomeViewController()会自动查找SomeViewController.xib,若该文件不存在则加载失败。当然可以使用SomeViewController(nibName: "BaseViewController", bundle: nil),但这种方式不够便捷。请问有没有办法在子类没有对应的XIB文件时,自动回退到父类的XIB文件?
解决方案
可以通过重写子类的XIB相关属性或初始化方法来实现自动回退逻辑,以下是两种实用方案:
方案一:重写nibName属性(推荐)
直接重写子类的nibName计算属性,在内部判断子类XIB是否存在,不存在则返回父类XIB名称:
class BaseViewController: UIViewController { // 父类定义自身默认XIB名称的方法,方便子类调用 class func defaultNibName() -> String { return String(describing: self) } } class SomeViewController: BaseViewController { override var nibName: String? { let currentClassName = String(describing: type(of: self)) let targetBundle = Bundle(for: type(of: self)) // 检查当前子类XIB是否存在 if targetBundle.path(forResource: currentClassName, ofType: "xib") != nil { return currentClassName } // 不存在则回退到父类XIB return BaseViewController.defaultNibName() } required init?(coder: NSCoder) { super.init(coder: coder) } }
这种方式无需重写初始化方法,逻辑简洁且符合系统设计逻辑。
方案二:重写初始化方法
如果需要更灵活的控制,可以重写子类的初始化方法,在内部处理XIB的回退逻辑:
class SomeViewController: BaseViewController { override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { let subclassNibName = String(describing: type(of: self)) let targetBundle = nibBundleOrNil ?? Bundle.main var finalNibName: String? = subclassNibName // 检查子类XIB是否存在 if targetBundle.path(forResource: subclassNibName, ofType: "xib") == nil { finalNibName = BaseViewController.defaultNibName() } super.init(nibName: finalNibName, bundle: targetBundle) } required init?(coder: NSCoder) { let subclassNibName = String(describing: type(of: self)) let targetBundle = Bundle(for: type(of: self)) if targetBundle.path(forResource: subclassNibName, ofType: "xib") == nil { // 子类XIB不存在时,手动指定父类XIB初始化 super.init(nibName: BaseViewController.defaultNibName(), bundle: targetBundle) } else { super.init(coder: coder) } } }
批量优化扩展
如果有多个子类需要该逻辑,可以把判断逻辑封装成UIViewController的扩展,避免重复代码:
extension UIViewController { func fallbackNibName(to parentClass: UIViewController.Type) -> String { let currentName = String(describing: type(of: self)) let targetBundle = Bundle(for: type(of: self)) if targetBundle.path(forResource: currentName, ofType: "xib") != nil { return currentName } return String(describing: parentClass) } }
之后子类只需在nibName或初始化方法中调用该方法即可。
内容的提问来源于stack exchange,提问作者Andrei Herford
相关产品推荐
相关产品推荐

