[英]UIViewController extension to instantiate from storyboard
我正在尝试在 Swift 中编写一个小扩展来处理故事板中UIViewController
实例化。
我的想法如下:由于UIStoryboard
的方法instantiateViewControllerWithIdentifier
需要一个标识符来实例化给定故事板的视图控制器,为什么不在我的故事板中为每个视图控制器分配一个等于其确切类名的标识符(即UserDetailViewController
将有一个标识符“UserDetailViewController”),并在 UIViewController 上创建一个类方法,它将:
UIStoryboard
实例作为唯一参数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()
更新:用协议重写。
实例化
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)
}
两件事情:
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
您可以添加此扩展程序: -
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.