【问题标题】:Creating a generic UViewController initialiser创建一个通用的 UViewController 初始化器
【发布时间】:2017-05-09 15:49:47
【问题描述】:

我正在尝试创建一个可用于初始化新实例的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  
}

是否可以通过无需强制转换响应的方式创建扩展程序?

附言处理一个用 Swift 2.3 编写的旧项目,因此希望得到支持的答案。

【问题讨论】:

  • 你能不能把它转换成函数,然后返回一个EditSomethingViewController?

标签: ios swift generics uiviewcontroller swift-protocols


【解决方案1】:

我使用这个扩展:

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()

【讨论】:

    【解决方案2】:

    我假设您不想让您的每一个 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
        }
    }
    

    【讨论】:

      【解决方案3】:

      方法 2

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

      用法:(确保 Storyboard Id 和 class-name 相同。)

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

      方法 1

      故事板扩展:

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

      用法:(确保 Storyboard Id 和 class-name 相同。)

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

      【讨论】:

        【解决方案4】:

        您可以将返回类型更改为Self,这将与您调用该方法的类型相匹配。

        这是我用来执行此操作的方法。而是需要将其放入协议扩展中。

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

        【讨论】:

          【解决方案5】:

          添加到@Chikabuz 的答案,我可以将他的 sn-p 添加到这样的内容中

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

          然后,在我的表格视图控制器中,类似这样的事情(我正在尝试将每个单元格的行自动化到自己的 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:如果你遇到了一些崩溃,很可能是你忘了加上identifier,它必须和你的vc名字一模一样,...或者vc不在Main.storyboard里面

          【讨论】:

            【解决方案6】:

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

            SwiftGen 可以生成代码以编程方式从情节提要中初始化视图控制器。 Docs

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

            【讨论】:

              猜你喜欢
              • 2012-05-23
              • 2018-04-07
              • 1970-01-01
              • 2019-03-06
              • 1970-01-01
              • 1970-01-01
              • 2017-11-23
              • 1970-01-01
              • 2012-06-23
              相关资源
              最近更新 更多