这是我的协议
protocol PassDataDelegate {
func passData(data: String)
}
我的第一个控制器
class FirstViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
delegate = SecondViewController()
}
@IBAction func sendDataButtonTapped(_ sender: Any) {
delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
}
第二,最后一个
class SecondViewController: UIViewController, PassDataDelegate {
@IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func passData(data: String) {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
应用程序在标签更改部分崩溃。在打开可选项时,它会显示为零。这有什么不对?
答案 0 :(得分:1)
您的代码存在几个基本问题。
我认为,对于代表团和UIStoryboardSegue
机制,您可能也会有一些误解。您应该阅读here (Delegation)和here (Segues)。
话虽如此,让我通过内联评论解释您的问题,解释发生了什么。
// Has to be marked as a class protocol (`: class`) so that
// `weak var delegate: PassDataDelegate?` can be weak.
protocol PassDataDelegate: class {
func passData(data: String)
}
class FirstViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
// Important!
// Make this a `weak` var. In your case, you would fortunately not create a retain cycle
// but there is a big threat of creating those when using delegation patterns with non-weak delegates.
//
// In your case, if you don't make this `weak`, `SecondViewController` would never be deallocated unless you
// cleared this var manually (setting it to `nil`).
//
// Also note that, if you're using `PassDataDelegate` solely for forwarding some data to the next view controller,
// you can dismiss this var entirely. There is no need to have a reference to the second view controller hanging around.
// In fact, as mentioned above, it can be dangerous to do so.
// Additionally, you don't need to make the protocol `: class` then.
private weak var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// It doesn't make any sense to create a `SecondViewController` here.
// The segue mechanism will create a new instance of `SecondViewController`.
// delegate = SecondViewController()
}
@IBAction func sendDataButtonTapped(_ sender: Any) {
// `delegate?` is `nil` here.
// delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination implements our protocol, we can forward data to it.
if let text = textField.text, let delegate = segue.destination as? PassDataDelegate {
// This is optional. You can hang on to the destination view controller here, but
// review the comments above to reason about whether this makes sense or not.
self.delegate = delegate
// We can safely forward the data (text) here.
delegate.passData(data: text)
}
}
}
SecondViewController
可以保持原样。
关于Delegation
委托模式通常描述一个后退指针,它与一个发起实例进行对话。例如。使用UITableViewDataSource
,UITableView
与实现此协议的内容进行对话,以获取有关其数据的信息等。
您实际上是通过转发数据到SecondViewController
。正如评论中所提到的,此代码甚至会中断,因为passData
中SecondViewController
的实施是使用尚未初始化的插座。
现在你可以在这里做三件事之一:
立即保留您正在使用的模式(不授权是准确的)并更改SecondViewController
以使其正常工作
class SecondViewController: UIViewController, PassDataDelegate {
@IBOutlet weak var myLabel: UILabel!
private var data: String?
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
func passData(data: String) {
self.data = data
// Only access `myLabel` if the view is loaded.
if isViewLoaded {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
}
这种方法实际上非常麻烦,因为你需要在任何时候调用passData
这个事实。因此,您不知道您的网点是否已经初始化,这会导致代码膨胀和重复。坏。
完全剥离协议并使用更简单的方法
class FirstViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination is a `SecondViewController`, we know that is has `public var data: String` and we can forward data to it.
if let text = textField.text, let destination = segue.destination as? SecondViewController {
// We can safely forward the data (text) here.
destination.data = text
}
}
}
class SecondViewController: UIViewController {
@IBOutlet weak var myLabel: UILabel!
// Deliberatly marking this a `public` to make clear that
// you're intented to set this from the 'outside'.
public var data: String? {
didSet {
if isViewLoaded {
myLabel.text = data
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
}
同样,有些事情我们不喜欢他的做法:
isViewLoaded
我们可以通过在init
SecondViewController
中提供数据来解决重复性代码问题。但是,由于您正在使用segue,故事板将为您实例化目标视图控制器,您无法控制它。现在你可以使用segue剥离,但这很快就会远离原始问题,并且是一种完全不同的仅代码方法。所以这也不好。
使用协议但正确应用委托模式。
protocol DataProvider: class {
func provideData() -> String?
}
protocol DataHandler: class {
var providerDelegate: DataProvider? { get set }
}
class FirstViewController: UIViewController, DataProvider {
@IBOutlet weak var textField: UITextField!
func provideData() -> String? {
return textField.text
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// If the destination is a `DataHandler`, we can set yourselves as its provider.
if let destination = segue.destination as? DataHandler {
destination.providerDelegate = self
}
}
}
class SecondViewController: UIViewController, DataHandler {
@IBOutlet weak var myLabel: UILabel!
weak var providerDelegate: DataProvider?
override func viewDidLoad() {
super.viewDidLoad()
if let data = providerDelegate?.provideData() {
// Safe to access `myLabel`, because we are in `viewDidLoad`.
myLabel.text = data
}
}
}
这种方法是最通用的。双方都不关心处理程序和提供程序究竟是什么。请注意,在经典委托模式中,您可能没有DataHandler
协议,并在SecondViewController
中检查具体类型(此处为prepareForSegue
)。但是,这种方法更灵活,同时仍然将代理编入其中。从SecondViewController
的角度来看,这种方法也是最强大的。它不必在任何时候处理passData
,而是可以自己决定何时向其代理人(DataProvider
)询问数据。通过这种方式,SecondViewController
可以推断出所有出口等都已初始化并且处理数据是安全的。
答案 1 :(得分:1)
SecondViewController()
不故事板中设计的控制器。这是一个没有连接插座的全新实例(这是崩溃的原因)。您需要对SecondViewController
实例的真实引用。
假设SecondViewController
实例是segue的目标视图控制器,则不需要protocol / delegate,通过segue传递数据
class FirstViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBAction func sendDataButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "Go", sender: nil)
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Go" {
let secondController = segue.destination as! SecondViewController
controller.passedData = textField.text!
}
}
}
class SecondViewController: UIViewController, PassDataDelegate {
@IBOutlet weak var myLabel: UILabel!
var passedData = ""
override func viewDidLoad() {
super.viewDidLoad()
print("This came from first: \(passedData). Will change UI.")
myLabel.text = passedData
}
}