如何在 Swift 中获取 UIStoryboard 数组?
How can I get array of UIStoryboard in Swift?
我发现我的 Storyboard 变得很复杂,所以决定把它拆分成不同的 Storyboard。但是我想自由地实例化一个 UIViewController,无论我把视图控制器放在哪里。这样,我可以在 View Controller 从 Storyboard 到 Storyboard 之间移动,而不需要记住我把 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;
}
问题是,我无法在任何地方找到 return 故事板实例列表的 属性 / 方法,例如 .storyboards
。有什么解决方法吗?我知道我可能有一个故事板名称的静态列表,但那样的话,扩展就不会是动态的并且独立于项目。
有人可以帮忙吗?谢谢。
编辑:
结合 and answer from here 来安全地实例化 viewcontroller(如果没有找到 return 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({ [=12=].hasSuffix(".storyboardc" )})
let storyboardNames = storyboardFileNames.map({ ([=12=] as NSString).deletingPathExtension as String })
let storyboardArray = storyboardNames.map({ UIStoryboard(name: [=12=], 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
文件夹中的限制。
就 运行 时间而言,这不是最高效的代码,我知道。但就目前而言,它很容易被理解,我可以接受。 :) 感谢大家的帮助!
故事板通常设置为本地化,因此您应该在包资源目录的 Base.lproj
子目录中查找它们。故事板被编译成扩展名为 .storyboardc
的文件,因此您应该查找具有该后缀的文件并将其删除。
let directory = Bundle.main.resourcePath! + "/Base.lproj"
let allResources = try! FileManager.default.contentsOfDirectory(atPath: directory)
let storyboardFileNames = allResources.filter({ [=10=].hasSuffix(".storyboardc" )})
let storyboardNames = storyboardFileNames.map({ ([=10=] as NSString).deletingPathExtension as String })
Swift.print(storyboardNames)
如果您已经创建了特定于设备的故事板(通过向故事板文件名添加 ~ipad
或 ~iphone
),那么您还需要去除这些后缀并消除重复项。
请注意,编译的故事板后缀不是 SDK 的文档部分,因此它可能会在 iOS / Xcode 的未来版本中更改。
不要那样做。
相反,根据需要使用 Storyboard References 转至不同的 Storyboard。
在你的情况下,如果你一起丢弃故事板,也许会更干净。并以编程方式创建所有视图控制器。
如其他地方所述,故事板在编译应用程序时被编译为不透明(即二进制、未记录).storyboardc
文件和 运行。 UIStoryboard
API 只允许实例化初始视图控制器,或一个(已知的)命名视图控制器,因此没有简单的方法来询问您的应用程序是否有 'unknown' 故事板元素。实例化未显示的 UI 元素也可能有副作用。然而...
如果您将 .storyboard
文件置于复制文件构建阶段,您可以询问 XML 并恢复,例如UIViewController/UIView 标识符、自定义属性等 IBDecodable seems to do a reasonable job of this. Installation-wise (cocoapods) it's MacOS-only but will run happily if you embed it in your iOS app (install the SWXMLHash 先决条件并克隆 IBDecodable/git-submodule 它,随便什么)。可能存在我略过的效率和本地化问题,但对于我的用例(从自定义属性构建入职弹出窗口但没有明确说明)它工作正常。
为了更具体地回答所提出的问题,询问故事板的 ID 将允许应用程序查找(通过字典或类似的),并实例化一个视图控制器,无论它是在视觉上存储的。
例如:
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 { /* ... */ }
})
我发现我的 Storyboard 变得很复杂,所以决定把它拆分成不同的 Storyboard。但是我想自由地实例化一个 UIViewController,无论我把视图控制器放在哪里。这样,我可以在 View Controller 从 Storyboard 到 Storyboard 之间移动,而不需要记住我把 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;
}
问题是,我无法在任何地方找到 return 故事板实例列表的 属性 / 方法,例如 .storyboards
。有什么解决方法吗?我知道我可能有一个故事板名称的静态列表,但那样的话,扩展就不会是动态的并且独立于项目。
有人可以帮忙吗?谢谢。
编辑:
结合
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({ [=12=].hasSuffix(".storyboardc" )})
let storyboardNames = storyboardFileNames.map({ ([=12=] as NSString).deletingPathExtension as String })
let storyboardArray = storyboardNames.map({ UIStoryboard(name: [=12=], 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
文件夹中的限制。
就 运行 时间而言,这不是最高效的代码,我知道。但就目前而言,它很容易被理解,我可以接受。 :) 感谢大家的帮助!
故事板通常设置为本地化,因此您应该在包资源目录的 Base.lproj
子目录中查找它们。故事板被编译成扩展名为 .storyboardc
的文件,因此您应该查找具有该后缀的文件并将其删除。
let directory = Bundle.main.resourcePath! + "/Base.lproj"
let allResources = try! FileManager.default.contentsOfDirectory(atPath: directory)
let storyboardFileNames = allResources.filter({ [=10=].hasSuffix(".storyboardc" )})
let storyboardNames = storyboardFileNames.map({ ([=10=] as NSString).deletingPathExtension as String })
Swift.print(storyboardNames)
如果您已经创建了特定于设备的故事板(通过向故事板文件名添加 ~ipad
或 ~iphone
),那么您还需要去除这些后缀并消除重复项。
请注意,编译的故事板后缀不是 SDK 的文档部分,因此它可能会在 iOS / Xcode 的未来版本中更改。
不要那样做。
相反,根据需要使用 Storyboard References 转至不同的 Storyboard。
在你的情况下,如果你一起丢弃故事板,也许会更干净。并以编程方式创建所有视图控制器。
如其他地方所述,故事板在编译应用程序时被编译为不透明(即二进制、未记录).storyboardc
文件和 运行。 UIStoryboard
API 只允许实例化初始视图控制器,或一个(已知的)命名视图控制器,因此没有简单的方法来询问您的应用程序是否有 'unknown' 故事板元素。实例化未显示的 UI 元素也可能有副作用。然而...
如果您将 .storyboard
文件置于复制文件构建阶段,您可以询问 XML 并恢复,例如UIViewController/UIView 标识符、自定义属性等 IBDecodable seems to do a reasonable job of this. Installation-wise (cocoapods) it's MacOS-only but will run happily if you embed it in your iOS app (install the SWXMLHash 先决条件并克隆 IBDecodable/git-submodule 它,随便什么)。可能存在我略过的效率和本地化问题,但对于我的用例(从自定义属性构建入职弹出窗口但没有明确说明)它工作正常。
为了更具体地回答所提出的问题,询问故事板的 ID 将允许应用程序查找(通过字典或类似的),并实例化一个视图控制器,无论它是在视觉上存储的。
例如:
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 { /* ... */ }
})