繁体   English   中英

从 storyboard 自定义 UIViewController

[英]Custom init of UIViewController from storyboard

我试图找到一些相关问题,但一无所获,希望有人能提供帮助。

我在 storyboard 上设置了一些 UIViewController。然后我想在代码中加载其中一个视图控制器并将其推送到导航堆栈。 我想出正确的方法是使用

instantiateViewControllerWithIdentifier

这会调用init(coder: NSCoder)并且一切正常,我的程序可以运行,但我希望能够有一个自定义初始化程序来为我的视图 controller 设置一些变量。请考虑以下事项:

class A : UIViewController {   

  let i : Int

  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    // property self.i not initialized at super.init call   
  }

}

我显然得到了一个错误,因为i需要在创建 object 时指定。 有什么解决办法吗? 我对将i声明为var并稍后对其进行配置不感兴趣,因为这违背了重点,而且我不再有编译器保证i是不可变的。

澄清编辑

假设我有一个当前加载的ViewController ,它有一些变量i 这个值是可变的,可以改变。 现在假设我想从这个 ViewController 中呈现另一个,并用i初始化它。

class ViewController: UIViewController {

   var i : Int

   // ... other things

  // in response to some button tap...
  @IBAction func tappedButton(sender: AnyObject) {
    let st = UIStoryboard(name: "Main", bundle: nil)
    let vc = st.instantiateViewControllerWithIdentifier("AControllerID") as! A
    // How do I initialize A with i ?
    self.presentViewController(vc, animated: true, completion: nil)
  }

}

我似乎无法做到这一点并通过使用let而不是var来保持i不变。

简化我之前的答案,这是快速的,并避免了其他hacky修复:

这是一个详细视图控制器,您可能希望从设置了objectID的storyboard实例化:

import UIKit

class DetailViewController: UIViewController {

    var objectID : Int!

    internal static func instantiate(with objectID: Int) -> DetailViewController {

        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailViewController") as DetailViewController
        vc.objectID = objectID
        return vc
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if((objectID) != nil){
            print("Here is my objectID: \(objectID)")
        }
    }
}

以下是如何使用它来推送一个objectID设置为1的导航控制器:

self.navigationController.pushViewController(DetailViewController.instantiate(1), animated: true)

添加了博客文章: https //theswiftcook.wordpress.com/2017/02/17/how-to-initialize-a-storyboard-viewcontroller-with-data-without-segues-swift-3-0git/

链接到GitHub上的示例: https //github.com/hammadzz/Instantiate-ViewController-From-Storyboard-With

下面是两个帮助程序,一个是Storyboard枚举,在项目中添加项目中的每个故事板作为一个案例。 该名称必须与{storyboard_name} .storyboard文件匹配。 故事板中的每个视图控制器都应将其故事板标识符设置为类的名称。 这是非常标准的做法。

import UIKit

public enum Storyboard: String {
    case Main
    case AnotherStoryboard
    //case {storyboard_name}

    public func instantiate<VC: UIViewController>(_ viewController: VC.Type) -> VC {
        guard
            let vc = UIStoryboard(name: self.rawValue, bundle: nil)
                .instantiateViewController(withIdentifier: VC.storyboardIdentifier) as? VC
            else { fatalError("Couldn't instantiate \(VC.storyboardIdentifier) from \(self.rawValue)") }

        return vc
    }

    public func instantiateInitialVC() -> UIViewController {

        guard let vc = UIStoryboard(name: self.rawValue, bundle: nil).instantiateInitialViewController() else {
            fatalError("Couldn't instantiate initial viewcontroller from \(self.rawValue)")
        }

        return vc
    }
}

extension UIViewController {
    public static var defaultNib: String {
        return self.description().components(separatedBy: ".").dropFirst().joined(separator: ".")
    }

    public static var storyboardIdentifier: String {
        return self.description().components(separatedBy: ".").dropFirst().joined(separator: ".")
    }
}

以下是如何使用视图控制器中的值集从故事板中实例化。 这是魔术:

import UIKit

class DetailViewController: UIViewController {

    var objectID : Int!
    var objectDetails: ObjectDetails = ObjectDetails()        

    internal static func instantiate(with objectID: Int) -> DetailViewController {

        let vc = Storyboard.Main.instantiate(DetailViewController.self)
        vc.objectID = objectID
        return vc
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if((objectID) != nil){
            // In this method I use to make a web request to pull details from an API
            loadObjectDetails()
        }
    }
}

(架构影响/复制Kickstarter的开源iOS项目)

解决此问题的一种(丑陋)方法:

您可以在代码中的外部缓冲区中设置let i (本例中为AppDelegate变量)

required init?(coder aDecoder: NSCoder) {
    self.i = UIApplication.shared().delegate.bufferForI

    super.init(coder: aDecoder)
}

当你通过Storyboard启动你的UIViewController时:

UIApplication.shared().delegate.bufferForI = myIValue
self.navigationController!.pushViewControllerFading(self.storyboard!.instantiateViewController(withIdentifier: "myViewControllerID") as UIViewController)

编辑:您不必通过AppDelegate传递值。 这里更好的答案。

iOS 13开始,可以使用最新更新的 API,同时从Storyboard UIViewController

首先,让我们为UIViewController创建自定义初始化程序,它使用NSCoder

final class ViewController: UIViewController {
    var someProperty: Int
    
    init(coder: NSCoder, someProperty: Int) {
        self.someProperty = someProperty
        super.init(coder: coder)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

稍后,您可以使用更新的 API 创建ViewController

let viewController = UIStoryboard.yourStoryboard.instantiateViewController(identifier: String(describing: ViewController.self)) { creator in
    let viewController = UIViewController(coder: creator, someProperty: someValue)
    return UIViewController()
}

如您所见,在闭包中,我们传递了新的初始化程序。 这种创建 viewController 的方法非常强大,因为当viewController使用viewController创建时,我们(最终)可以使用初始化器,这取决于required init(coder: NSCoder) UIStoryboard 有关详细信息, 请参阅此处

从 iOS 13 开始,故事板中有一项新功能。 如果您使用 segue 操作,则在实例化时将数据传递给故事板视图控制器非常简单。 在界面构建器中,从 segue 控制拖动到将执行 segue 的视图控制器。 然后实现类似于以下内容的附加参数:

在源 VC 中:

@IBSegueAction func showChooser(_ coder: NSCoder, sender: Any?) -> ChooserTableViewController? {
    
    return ChooserTableViewController(coder: coder, useMap: true)
}

在目标 VC 中:

let useMap: Bool

init?(coder: NSCoder, useMap: Bool) {
        self.user = useMap
        super.init(coder: coder)
}

如果您需要在不使用 segue 的情况下实例化 VC,故事板有新的实例化函数,允许您在视图控制器创建期间调用自定义编码器 init。 有关详细信息,请参阅 UIStoryboardViewControllerCreator 类型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM