我尝试寻找一些相关问题,但没有得到任何结果,希望有人能提供帮助。
我在故事板上设置了一些 UIViewController。然后我想在代码中加载视图控制器之一并将其推送到导航堆栈上。我发现正确的方法是使用
instantiateViewControllerWithIdentifier
这会调用
init(coder: NSCoder)
,一切都很好,我的程序可以工作,但我希望能够有一个自定义初始化程序,为我的视图控制器设置一些变量。考虑以下几点:
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
。有什么解决办法吗?我对将 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)
}
}
我似乎无法做到这一点并通过使用
i
而不是 let
来保持 var
不可变。
我之前的答案的简化,速度很快并且避免了替代的黑客修复:
这是一个详细视图控制器,您可能希望使用 objectID 集从情节提要实例化:
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)
链接到 GitHub 上的示例: https://github.com/hammadzz/Instantiate-ViewController-From-Storyboard-With-Data
下面有两个助手,一个是 Storyboard 枚举,将项目中的每个 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 项目)
从
iOS 13
开始,您可以使用新更新的API,同时从UIViewController
实例化Storyboard
。
首先,让我们为
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 viewController
}
如您所见,在闭包中,我们传递了新的初始值设定项。这种创建
viewController
的方法非常强大,因为当 required init(coder: NSCoder)
使用 viewController
创建时,我们(终于!)可以使用初始化器,这取决于 UIStoryboard
。
欲了解更多信息,请参阅此处。
从 iOS 13 开始,故事板中有一个新功能。如果您使用 segue 操作,则在实例化故事板视图控制器时将数据传递到它是非常简单的。在界面生成器中,按住 Control 键并从 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.useMap = useMap
super.init(coder: coder)
}
如果您需要在不使用 segue 的情况下实例化 VC,则故事板有新的实例化函数,它们允许您在视图控制器创建期间调用自定义编码器 init。有关详细信息,请参阅 UIStoryboardViewControllerCreator 类型。
解决这个问题的(丑陋的)方法:
您可以从代码中的外部缓冲区设置
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 传递该值。更好的答案在这里。