创建一个通用的 UViewController 初始化器

Creating a generic UViewController initialiser

我正在尝试创建可用于初始化新实例的 UIViewController 扩展。对于我项目中的每个视图控制器,我都有一个相应的故事板。

EditSomethingViewController.swift
EditSomethingViewController.storyboard

这是我目前拥有的:

extension UIViewController {

    static func initalize() -> UIViewController? {
        let name = String(self)

        let storyboard = UIStoryboard(name: name, bundle: nil)

        return storyboard.instantiateInitialViewController()
    }

}

然而这意味着当我使用它时,我仍然必须投射响应。

if let viewController = EditSomethingViewController.initalize() as? EditSomethingViewController {
     // do something with view controller here  
}

是否可以以这样一种方式创建扩展,这意味着我不必投射响应?

p.s。正在处理一个用 Swift 2.3 编写的旧项目,因此希望得到支持的答案。

您可以将 return 类型更改为 Self,这将匹配您调用方法的类型。

这是我用过的方法。它将需要放入协议扩展中。

static func loadFromStoryboard() -> Self? {
    let storyboard = UIStoryboard(name: NSStringFromClass(self),
                                  bundle: Bundle(for: self))
    return storyboard.instantiateInitialViewController() as? Self
}

我使用这个扩展:

extension UIViewController
{
    class func instantiateFromStoryboard(_ name: String = "Main") -> Self
    {
        return instantiateFromStoryboardHelper(name)
    }

    fileprivate class func instantiateFromStoryboardHelper<T>(_ name: String) -> T
    {
        let storyboard = UIStoryboard(name: name, bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: String(describing: self)) as! T
        return controller
    }
}

用法:

let controller = MyViewController.instantiateFromStoryboard()

我假设您不想让您的每个 VC 都手动遵守协议。那将是太多的工作:)

我还没有测试过,但应该可以:

protocol Initializable {
    static func initalize() -> Self?
}

extension UIViewController: Initializable {
    static func initalize() -> Self? {
        let name = NSStringFromClass(self as! AnyClass)

        let storyboard = UIStoryboard(name: name, bundle: nil)

        return storyboard.getInitialVC(type: self)
    }
}

extension UIStoryboard {
    func getInitialVC<T: UIViewController>(type: T.Type) -> T? {
        return instantiateInitialViewController() as? T
    }
}

添加到@Chikabuz 的回答中,我可以将他的代码片段添加到这样的内容中

extension UIViewController
{
    class func instantiateFromStoryboard(_ name: String = "Main", identifier: String) -> Self
    {
        return instantiateFromStoryboardHelper(name, identifier: identifier)
    }

    fileprivate class func instantiateFromStoryboardHelper<T>(_ name: String, identifier: String) -> T
    {
        let storyboard = UIStoryboard(name: name, bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: identifier) as! T
        return controller
    }
}

然后,在我的 table 视图控制器中,像这样(我试图将每个单元格的行自动化到它自己的 vc):

struct MenuItem {
    let title: String
    let subtitle: String
    let `class`: AnyClass
}
class MenuViewController: UITableViewController {

    private var menu: [MenuItem] = [
        MenuItem(title: "Some View",
                 subtitle: "The good old description",
                 class: FirstViewController.self),
        MenuItem(title: "Another View",
                 subtitle: "Demo of this view",
                 class: SecondViewController.self)
    ]
    ...
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: 
    IndexPath) {
        let item = menu[indexPath.row]

        let vcClass = item.class as! UIViewController.Type
        let vc = vcClass.instantiateFromStoryboard(identifier: String(describing: item.class))

        self.navigationController?.pushViewController(vc, animated: true)
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

好了,魔术! 您的故事板不必与 vc 的文件名完全相同,但默认为 Main.

PS:如果你有一些崩溃,可能是你忘记添加必须与你的 vc 名称完全相同的 identifier,...或者 vc 不在 Main.storyboard

这是一个重型解决方案,但如果您决定投资它,那将是非常好的。

SwiftGen 可以生成代码,以编程方式从故事板中初始化视图控制器。 Docs

// You can instantiate scenes using the `instantiate` method:
let vc = StoryboardScene.Dependency.dependent.instantiate()

方法二

enum Storyboards: String {
    case main = "Main"
    case home = "Home"
    
    func instantiateVC<T>(_ identifier: T.Type) -> T?  {
        let storyboard = UIStoryboard(name: rawValue, bundle: nil)
        guard let viewcontroller = storyboard.instantiateViewController(withIdentifier: String(describing: identifier)) as? T else { return nil}
        return viewcontroller
    }
}

用法:(确保故事板 ID 和 class-名称相同。)

if let vc = Storyboards.main.instantiateVC(ViewController.self) {
    self.present(vc, animated: true, completion: nil)
}

方法一

故事板扩展:

extension UIStoryboard {
    enum Name: String {
        case main = "Main"
        case home = "Home"
    }

    static func instantiateViewController<T>(storyboard name: Name = .main, ofType type: T.Type) -> T? {
        return UIStoryboard(name: name.rawValue, bundle: nil).instantiateViewController(withIdentifier: String(describing: type.self)) as? T
    }
}

用法:(确保故事板 ID 和 class-名称相同。)

if let vc = UIStoryboard.instantiateViewController(storyboard: .home, ofType: HomeVC.self) {
    self.present(vc, animated: true, completion: nil)
}