我发现我的故事板变得非常复杂,并决定将其拆分为不同的故事板。但是我希望能够自由地实例化UIViewController,无论我把视图控制器放在哪里。这样,我可以将View Controller从Storyboard移动到Storyboard,而不需要记住我把View Controller放在哪里,我也是不必更新代码,因为它们都使用相同的代码来实例化具有该名称的相同View Controller,无论它位于何处。
因此,我想创建一个UIViewController的扩展,如下所示:
extension UIViewController {
func instantiate (named: String?, fromStoryboard: String? = nil) -> UIViewController? {
guard let named = named else { return nil; }
if let sbName = fromStoryboard {
let sb = UIStoryboard(name: sbName, bundle: nil);
return sb.instantiateViewController(withIdentifier: named);
}
else {
for sb in UIStoryboard.storyboards {
if let vc = sb.instantiateViewController(withIdentifier: named) {
return vc;
}
}
}
return nil;
}
问题是,我找不到属性/方法来返回任何地方的.storyboards
等故事板实例列表。这有什么解决方法吗?我知道我可能有一个静态的故事板名称列表,但是这样,扩展名不会是动态的,也不依赖于项目。
有人可以帮忙吗?感谢。
修改
结合accepted answer和here的答案来安全地实例化viewcontroller(如果没有找到则返回nil),这是我的代码:
UIStoryboard + Storyboards.swift:
extension UIStoryboard {
static var storyboards : [UIStoryboard] {
let directory = Bundle.main.resourcePath! + "/Base.lproj"
let allResources = try! FileManager.default.contentsOfDirectory(atPath: directory)
let storyboardFileNames = allResources.filter({ $0.hasSuffix(".storyboardc" )})
let storyboardNames = storyboardFileNames.map({ ($0 as NSString).deletingPathExtension as String })
let storyboardArray = storyboardNames.map({ UIStoryboard(name: $0, bundle: Bundle.main )})
return storyboardArray;
}
func instantiateViewControllerSafe(withIdentifier identifier: String) -> UIViewController? {
if let availableIdentifiers = self.value(forKey: "identifierToNibNameMap") as? [String: Any] {
if availableIdentifiers[identifier] != nil {
return self.instantiateViewController(withIdentifier: identifier)
}
}
return nil
}
}
的UIViewController + Instantiate.swift:
extension UIViewController {
static func instantiate (named: String?, fromStoryboard: String? = nil) -> UIViewController? {
guard let named = named else { return nil; }
if let sbName = fromStoryboard {
let sb = UIStoryboard(name: sbName, bundle: nil);
return sb.instantiateViewControllerSafe(withIdentifier: named);
}
else {
for sb in UIStoryboard.storyboards {
if let vc = sb.instantiateViewControllerSafe(withIdentifier: named) {
return vc;
}
}
}
return nil;
}
}
限制所有故事板文件必须位于Base.lproj
文件夹。
我知道,就运行时间而言,它不是最有效的代码。但就目前而言,它很容易被理解,我可以忍受这个。 :)感谢所有帮助过的人!
答案 0 :(得分:3)
故事板通常设置为本地化,因此您应该在捆绑包的资源目录的Base.lproj
子目录中查找它们。故事板被编译成扩展名为.storyboardc
的文件,因此您应该查找具有该后缀的文件并将其删除。
let directory = Bundle.main.resourcePath! + "/Base.lproj"
let allResources = try! FileManager.default.contentsOfDirectory(atPath: directory)
let storyboardFileNames = allResources.filter({ $0.hasSuffix(".storyboardc" )})
let storyboardNames = storyboardFileNames.map({ ($0 as NSString).deletingPathExtension as String })
Swift.print(storyboardNames)
如果您已创建特定于设备的故事板(通过向故事板文件名添加~ipad
或~iphone
),那么您还需要删除这些后缀并消除重复项。
请注意,特定编译的故事板后缀不是SDK的文档部分,因此可能会在未来版本的iOS / Xcode中更改。
答案 1 :(得分:1)
在你的情况下,如果你把故事板放在一起就可能会更清洁。并以编程方式创建所有视图控制器。
答案 2 :(得分:0)
不要那样做。
相反,请根据需要使用Storyboard References来转换到不同的故事板。
答案 3 :(得分:0)
如其他地方所述,在编译和运行应用程序时,情节提要板将编译为不透明的(即二进制文件,未记录).storyboardc
文件。 UIStoryboard
API仅允许实例化初始View Controller或一个(已知的)命名实例,因此没有幼稚的方法来为“未知”情节提要元素询问您的应用。实例化未显示的UI元素也可能会有副作用。但是...
如果您将.storyboard
文件放入“复制文件”构建阶段,则可以查询XML并进行恢复,例如UIViewController / UIView标识符,自定义属性等。IBDecodable似乎可以做到这一点。在安装方面(cocoapods)仅适用于MacOS,但如果将其嵌入到iOS应用中(安装SWXMLHash先决条件并克隆IBDecodable / git-submodule,则可以成功运行)。我可能会忽略效率和本地化问题,但是对于我的用例(在未明确说明的情况下,从自定义属性构建入职弹出窗口)就可以了。
要回答更具体的问题,查询故事板中的ID将允许该应用(通过字典或类似方法)查找并实例化一个View Controller,无论它在视觉上存储在哪里。
例如:
Bundle.main.urls(forResourcesWithExtension: "storyboard", subdirectory: nil)?
.forEach({ (url) in
do {
let file = try StoryboardFile(url: url)
if !file.document.launchScreen {
file.document.scenes?.forEach({ (scene) in
scene.viewController?.viewController.rootView?.subviews?
.forEach({ (view) in
// Do stuff...
})
})
}
} catch { /* ... */ }
})