Creating a generic UViewController initialiser

一个人想着一个人 提交于 2019-11-30 17:20:40

问题


I'm trying to create a UIViewController extension that I can use to initialise new instances. For each view controller in my project I have a corresponding storyboard.

i.e.

EditSomethingViewController.swift
EditSomethingViewController.storyboard

This is what I have so far:

extension UIViewController {

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

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

        return storyboard.instantiateInitialViewController()
    }

}

However this means that when I use it, I still have to cast the response.

i.e.

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

Is it possible to create the extension in such a way that means I don't have to cast the response?

p.s. Working on an old project written in Swift 2.3 so would appreciate answers that are supported.


回答1:


I use this extension:

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
    }
}

Usage:

let controller = MyViewController.instantiateFromStoryboard()



回答2:


I assume that you don't want to make every one of your VCs conform to a protocol manually. That would be too much work :)

I haven't tested this but this should work:

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
    }
}



回答3:


You can change the return type to be Self which will match the type you are calling the method on.

This is a method I've used to do this. It will need to be put into a protocol extension instead.

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



回答4:


Adding to @Chikabuz answer, I could add his snippet to something like this

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
    }
}

And then, in my table view controller, something like this (i'm trying to automate each cell's row to its own 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)
    }
}

and there you go, magic! Your storyboard doesn't have to be exactly the same as vc's file name but default to Main.

PS: If you got some crashes, it's probably that you forgot to add identifier that must be exactly the same as your vc name,... or that vc is not inside Main.storyboard




回答5:


This is a heavy-duty solution, but it will be really nice if you decide to invest in it.

SwiftGen can generate code to programmatically initialize your view controllers from your storyboards. Docs

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


来源:https://stackoverflow.com/questions/43874430/creating-a-generic-uviewcontroller-initialiser

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!