Reputation: 43
I've created (and linked) some outlets to labels in my storyboard, and when I attempt to modify the text, my application throws an exception and freezes, because they are apparently nil
.
Code:
import UIKit
class QuoteView : UITableViewController {
@IBOutlet var quoteCategory: UILabel!
@IBOutlet var quoteTitle: UILabel!
@IBOutlet var quoteAuthor: UILabel!
@IBOutlet var quoteContent: UILabel!
@IBOutlet var quoteTimestamp: UILabel!
}
I'm attempting to set them in the prepareForSegue
method of previous view controller, like so:
overide func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "indivQuoteSegue") {
let svc = segue.destinationViewController as! QuoteView
let quote = quoteArray[(tableView.indexPathForSelectedRow?.row)!];
svc.quoteTitle.text = quote.getQuoteTitle()
svc.quoteAuthor.text = quote.getQuoteAuthor()
svc.quoteContent.text = quote.getQuoteContent()
svc.quoteCategory.text = quote.getCategory()
svc.quoteTimestamp.text = quote.getQuoteTimestamp()
}
}
Upvotes: 2
Views: 1080
Reputation: 1210
Its happening because usually the UI(view) elements are not ready to be manipulated till viewDidLoad is called.
A elegant solution here would be to create a "quote" property in QuoteView and set it in "prepareForSegue". Then later use that quote property to load the UI elements in viewDidLoad or viewWillAppear method. see @Duncan C's answer for details.
overide func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "indivQuoteSegue") {
let svc = segue.destinationViewController as! QuoteView
svc.quote = quoteArray[(tableView.indexPathForSelectedRow?.row)!];
}
}
then In QuoteView
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
self.quoteTitle.text = self.quote.getQuoteTitle()
self.quoteAuthor.text = self.quote.getQuoteAuthor()
self.quoteContent.text = self.quote.getQuoteContent()
self.quoteCategory.text = self.quote.getCategory()
self.quoteTimestamp.text = self.quote.getQuoteTimestamp()
}
Upvotes: 2
Reputation: 131481
Stepping back a little from the "why it doesn't work" question about trying to set a view controller's outlets from outside the view controller, I give you a flat rule:
"Don't do that."
You should treat another view controllers outlets as private and not try to manipulate them. That violates the principle of encapsulation, and important idea in OO design.
Imagine this: I have a control panel object for a piece of stereo equipment. The control panel has a bunch of control UI objects on it that let the user set equalization, noise reduction, etc. Then it has a public API that it uses to interface with the other software components of the system.
If other other objects reach into my control panel and do things like change the value of the equalization sliders, then I can't go back later and replace the UI elements for equalizers to use dials, or numeric inputs, or some other UI element, without breaking the rest of the app. I have tightly coupled other objects in my program to things that should be part of the private implementation of my control panel object.
In your case, it flat doesn't work because in prepareForSegue the destination view controller's views haven't yet been loaded.
Even in cases where it would work, you still should not do that.
Instead, you should create public properties of your view controller that let an outside object specify values for settings. Then the view controller's viewWillAppear method should apply the values in those properties to the UI.
When you do it that way, if you later make changes to the UI, all you have to do is to make sure the public properties still work as expected.
@beyowulf and @harshitgupta gave detailed examples of how to do it the right way. (Although I would argue that the code to install the properties into the UI should in viewWillAppear, not viewDidLoad. That way, in cases where a view controller is shown, covered by another view controller that changes its values, then uncovered, it updates the changed values on-screen when it gets shown again.)
Upvotes: 2
Reputation: 15331
In prepareForSegue
you outlets have not been initialized yet, so you can not set properties of them. You should create String properties and set those instead, or better yet just pass your quote
object. For example:
overide func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "indivQuoteSegue") {
let svc = segue.destinationViewController as! QuoteView
let quote = quoteArray[(tableView.indexPathForSelectedRow?.row)!];
svc.quoteTitleString = quote.getQuoteTitle()
svc.quoteAuthorString = quote.getQuoteAuthor()
svc.quoteContentString = quote.getQuoteContent()
svc.quoteCategoryString = quote.getCategory()
svc.quoteTimestampString = quote.getQuoteTimestamp()
}
}
Then in the viewDidLoad
of QuoteView
set your labels' text
override func viewDidLoad(){
self.quoteTitle.text = self.quoteTitleString
etc...
}
Upvotes: 6
Reputation: 916
You can't assign them in segue as they're not initialized yet, you may think of using optional binding but it's not going to work either, the solution is to pass your data struct or class as var to the segued view then update the outlets it in the new ViewController in viewDidLoad() or using didSet{}.
Upvotes: 1