簡體   English   中英

從故事板實例化的 UIViewController 擴展

[英]UIViewController extension to instantiate from storyboard

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

我的想法如下:由於UIStoryboard的方法instantiateViewControllerWithIdentifier需要一個標識符來實例化給定故事板的視圖控制器,為什么不在我的故事板中為每個視圖控制器分配一個等於其確切類名的標識符(即UserDetailViewController將有一個標識符“UserDetailViewController”),並在 UIViewController 上創建一個類方法,它將:

  • 接受UIStoryboard實例作為唯一參數
  • 以字符串形式獲取當前類名
  • 以類名作為參數調用 storyboard 實例上的instantiateViewControllerWithIdentifier
  • 獲取新創建的UIViewController實例,並返回

所以,而不是(將類名作為字符串重復,不是很好)

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

這將是:

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

我曾經在 Objective-C 中使用以下類別進行操作:

+ (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作為返回值)

如何編寫擴展到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()

感謝MartinR和他的回答 ,我知道答案:

更新:用協議重寫。

實例化

protocol StringConvertible {
    var rawValue: String {get}
}

protocol Instantiable: class {
    static var storyboardName: StringConvertible {get}
}

extension Instantiable {
    static func instantiateFromStoryboard() -> Self {
        return instantiateFromStoryboardHelper()
    }

    private static func instantiateFromStoryboardHelper<T>() -> T {
        let identifier = String(describing: self)
        let storyboard = UIStoryboard(name: storyboardName.rawValue, bundle: nil)
        return storyboard.instantiateViewController(withIdentifier: identifier) as! T
    }
}

//MARK: -

extension String: StringConvertible { // allow string as storyboard name
    var rawValue: String {
        return self
    }
}

STORYBOARDNAME

enum StoryboardName: String, StringConvertible {
    case main = "Main"
    //...
}

用法:

class MyViewController: UIViewController, Instantiable {

    static var storyboardName: StringConvertible {
        return StoryboardName.main //Or you can use string value "Main"
    }
}

let viewController = MyController.instantiateFromStoryboard()

我們正在將我們的客觀c項目移植到swift。 我們已將項目拆分為模塊。 模塊有自己的故事板。 我們通過避免顯式故事板名稱將您的(甚至是我們的)問題的解決方案擴展到另一個級別。

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

}

兩件事情:

  • Objective-C中的類構造函數是Swift中的便捷初始化器。 使用convenience init而不是class func
  • NSStringFromClass(Self)NSStringFromClass(self.type)

或者,你可以這樣做

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

在UIViewController中使用協議來實現您的想法

let vc = YourViewController.instantiate(from: .StoryboardName)

你可以看到我鏈接的使用:D

https://github.com/JavanC/StoryboardDesignable

您可以添加此擴展程序: -

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

你可以像這樣創建UIViewController實例:

使用您的所有故事板名稱創建enum

enum AppStoryboard: String {
   case main = "Main"
   case profile = "Profile"
}

然后,這是實例化UIViewController的擴展

extension UIViewController {

    class func instantiate<T: UIViewController>(appStoryboard: AppStoryboard) -> T {

        let storyboard = UIStoryboard(name: appStoryboard.rawValue, bundle: nil)
        let identifier = String(describing: self)
        return storyboard.instantiateViewController(withIdentifier: identifier) as! T
    }
}

用法:

let profileVC: ProfileVC = ProfileVC.instantiate(appStoryboard: .profile)
self.navigationController?.pushViewController(profileVC,animated:true)

這是一個基於@ findall解決方案的現代Swift示例:

extension UIStoryboard {
    func instantiate<T>() -> T {
        return instantiateViewController(withIdentifier: String(describing: T.self)) as! T
    }

    static let main = UIStoryboard(name: "Main", bundle: nil)
}

用法:

let userDetailViewController = UIStoryboard.main.instantiate() as UserDetailViewController

我認為在嘗試從故事板中實例化視圖控制器時可能會失敗,因為很快就會發現這種問題。

作為@ChikabuZ版本的補充,我在這里考慮了故事板所在的捆綁包(例如,如果你的故事板在另一個捆綁包中而不是你的應用程序)。 如果你想使用xib而不是storyboad,我還添加了一個小函數。

extension UIViewController {

    static func instantiate<TController: UIViewController>(_ storyboardName: String) -> TController {
        return instantiateFromStoryboardHelper(storyboardName)
    }

    static func instantiate<TController: UIViewController>(_ storyboardName: String, identifier: String) -> TController {
        return instantiateFromStoryboardHelper(storyboardName, identifier: identifier)
    }

    fileprivate static func instantiateFromStoryboardHelper<T: UIViewController>(_ name: String, identifier: String? = nil) -> T {
        let storyboard = UIStoryboard(name: name, bundle: Bundle(for: self))
        return storyboard.instantiateViewController(withIdentifier: identifier ?? String(describing: self)) as! T
    }

    static func instantiate<TController: UIViewController>(xibName: String? = nil) -> TController {
        return TController(nibName: xibName ?? String(describing: self), bundle: Bundle(for: self))
    }
}

我有類似的想法並決定使用下面的擴展。 它仍然使用正常的實例化過程,但消除了對字符串類型 Storyboard 和 View Controller 名稱的依賴:

let myVC = UIStoryboard(.main).instantiate(MyViewController.self)

上面的返回類型是預先轉換為MyViewController ,而不是標准的UIViewController

extension UIStoryboard {
    
    enum Name: String {
        case main   = "Main"
        case launch = "LaunchScreen"
        case other  = "Other"
    }
    
    convenience init(_ name: Name, bundle: Bundle? = nil) {
        self.init(name: name.rawValue, bundle: bundle)
    }
    
    func instantiate<T: UIViewController>(_ type: T.Type) -> T {
        instantiateViewController(withIdentifier: String(describing: type)) as! T
    }
    
}

請注意,您必須確保每個 VC 的Storyboard Identifier與其類名完全匹配! 如果不這樣做將導致異常:

由於未捕獲的異常“NSInvalidArgumentException”而終止應用程序,原因:“故事板(<UIStoryboard:0x6000035c04e0>)不包含標識符為“MyViewController”的視圖控制器

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM