如何在 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 { /* ... */ }
        })