2014-09-26 79 views
8

我試圖在Swift中編寫一個小擴展來處理故事板中的UIViewController的實例化。從故事板實例化的UIViewController擴展

我的想法是這樣的:由於UIStoryboard的方法instantiateViewControllerWithIdentifier需要一個標識符來實例化一個給定的故事板的視圖控制器,爲什麼不是每個視圖控制器在我的故事板的標識符等於其確切的類名(即UserDetailViewController分配將有「UserDetailViewController」)的標識符,並創建一個的UIViewController類的方法,將:

  • 接受UIStoryboard實例作爲唯一參數
  • 獲取當前類的字符串名字
  • 與類名作爲參數的故事板實例調用instantiateViewControllerWithIdentifier
  • 獲得新創建的UIViewController實例,並返回它

因此,而不是(其中重複類的字符串名字,不是很好)

let vc = self.storyboard?.instantiateViewControllerWithIdentifier("UserDetailViewController") as UserDetailViewController 

這將是:

let vc = UserDetailViewController.instantiateFromStoryboard(self.storyboard!) 

我用的followi做在Objective-C ng類別:

+ (instancetype)instantiateFromStoryboard:(UIStoryboard *)storyboard 
{ 
    return [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([self class])]; 
} 

但是我完全被Swift版困住了。我希望有某種方式可以做到這一點。 我試過如下:

extension UIViewController { 
    class func instantiateFromStoryboard(storyboard: UIStoryboard) -> Self { 
     return storyboard.instantiateViewControllerWithIdentifier(NSStringFromClass(Self)) 
    } 
} 

返回Self代替AnyObject允許類型推斷工作。否則,我將不得不施放這種方法的每一次返回,這很煩人,但也許你有更好的解決方案?

這給了我錯誤:Use of unresolved identifier 'Self' NSStringFromClass部分似乎是問題所在。

您認爲如何?

  • 有沒有什麼辦法從類函數中返回Self

  • 如何在不需要每次都施加返回值的情況下得到此工作? (即保留-> Self作爲返回值)

謝謝。

+0

我可以看到該實用程序,但意識到這個「解決方案」排除了在給定故事板中具有相同視圖控制器類的兩個實例。 – Caleb 2014-09-29 15:30:07

+0

當然,但這是已知在我的應用程序中是不可能的(因爲慣例是固定的)。我確保故事板中的兩個視圖控制器不能具有相同的類名稱。 – 2014-10-01 07:30:57

+1

回聲@Caleb,這可能是一個壞主意。你正在施加一個不必要的約束,只有很少的好處。從長遠來看,公約並不是特別可靠。 – mattt 2014-10-02 02:15:30

回答

20

如何寫入UIStoryboard的擴展名而不是UIViewController

extension UIStoryboard { 
    func instantiateVC<T: UIViewController>() -> T? { 
     // get a class name and demangle for classes in Swift 
     if let name = NSStringFromClass(T.self)?.componentsSeparatedByString(".").last { 
      return instantiateViewControllerWithIdentifier(name) as? T 
     } 
     return nil 
    } 

} 

即使採用這種方法,使用方面的成本也很低。

let vc: UserDetailViewController? = aStoryboard.instantiateVC() 
+0

如果我不指定vc的類型,該怎麼辦?那麼T會在編譯的時候被推斷爲wile在Tself中使用的嗎? – BangOperator 2016-06-21 12:05:00

+0

@BangOperator我現在不能嘗試它,但它會導致編譯錯誤,我想。由於編譯器無法將任何類型替換爲T,所以相應的機器碼也不可能無法確定。 – findall 2016-06-21 12:19:09

+0

所以當我們做'讓vc:UserDetailViewController = aStoryboard.instantiateVC()'是'T'這裏等於'UserDetailViewController'?你能詳細闡述一下發生了什麼?我的意思是,當你這樣做時,你從類型安全中受益。 – Honey 2016-09-19 16:12:39

2

兩件事情:

  • 在Objective-C類構造函數在夫特方便初始化。使用convenience init而不是class func
  • NSStringFromClass(Self)NSStringFromClass(self.type)
+0

好的,我錯過了在Objc中使用類構造函數作爲Swift中便利初始值設定項的一點。我認爲編寫一個'init(storyboard:UIStoryboard)'是不可能的,因爲便利的初始化器必須使用'self.init()'進行委託。在我的情況下是沒有意義的。 – 2014-10-02 07:42:18

+0

所以基本上,我的「問題」沒有優雅的解決方案。 – 2014-10-02 08:10:09

2

由於MartinR和他answer,我知道答案:

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 identifier = String(describing: self) 
     let controller = storyboard.instantiateViewController(withIdentifier: identifier) as! T 
     return controller 
    } 
} 

用法:

let viewController = MyController.instantiateFromStoryboard() 
let viewController2 = MyController.instantiateFromStoryboard("Main") 
+0

Swift 3.0不起作用 – 2017-06-03 10:27:24

+0

@NikKov,謝謝,更新了我的答案。 – ChikabuZ 2017-06-03 11:26:58

3

我們正在移植我們的目標C項目,以迅速。我們已經將項目分解成模塊。模塊有自己的故事板。我們通過避免顯式的故事板名稱將您的(甚至是我們的)問題的解決方案擴展到另一個級別。

// Add you modules here. Make sure rawValues refer to a stroyboard file name. 
enum StoryModule : String { 
    case SomeModule 
    case AnotherModule = "AnotherModulesStoryBoardName" 
    // and so on... 
} 

extension UIStoryboard { 
    class func instantiateController<T>(forModule module : StoryModule) -> T { 
     let storyboard = UIStoryboard.init(name: module.rawValue, bundle: nil); 
     let name = String(T).componentsSeparatedByString(".").last 
     return storyboard.instantiateViewControllerWithIdentifier(name!) as! T 
    } 
} 

// Some controller whose UI is in a stroyboard named "SomeModule.storyboard", 
// and whose storyboardID is the class name itself, ie "MyViewController" 
class MyViewController : UIViewController { 
    // Controller Code 
} 

// Usage 
class AClass 
{ 
    // Here we must alwasy provide explicit type 
    let viewController : MyViewController = UIStoryboard.instantiateController(forModule: StoryModule.SomeModule) 

} 
1

或者,你可以這樣做

func instantiateViewControllerWithIdentifier<T>(_ identifier: T.Type) -> T { 
      let identifier = String(describing: identifier) 
      return instantiateViewController(withIdentifier: identifier) as! T 
     } 
0

您可以添加此擴展名: -

extension UIStoryboard{ 

    func instantiateViewController<T:UIViewController>(type: T.Type) -> T? { 
     var fullName: String = NSStringFromClass(T.self) 
     if let range = fullName.range(of:".", options:.backwards, range:nil, locale: nil){ 
      fullName = fullName.substring(from: range.upperBound) 
     } 
     return self.instantiateViewController(withIdentifier:fullName) as? T 
    } 
} 

並且可以實例化視圖控制器是這樣的: -

self.storyboard?.instantiateViewController(type: VC.self)! 
2

這裏是我在最近的雨燕4.0的項目做到了:

protocol InstantiableFromStoryboard {} 

extension InstantiableFromStoryboard { 
    static func fromStoryboard(name: String = "Main", bundle: Bundle? = nil) -> Self { 
     let identifier = String(describing: self) 
     guard let viewController = UIStoryboard(name: name, bundle: bundle).instantiateViewController(withIdentifier: identifier) as? Self else { 
      fatalError("Cannot instantiate view controller of type " + identifier) 
     } 
     return viewController 
    } 
} 

extension UIViewController: InstantiableFromStoryboard {} 

使用

let viewController = ViewController.fromStoryboard()