IBOutlet is nil - swift

I have created a standard outlet for a view that will hold different information based on the button selected on the previous screen.
#IBOutlet weak var labelView: UIView!
It shows it is connected in both the story board view and on the code itself, however, every time I get to any reference to the labelView such as:
if detail.description == "About"
{
labelView.backgroundColor = UIColor.red
}
Then the app crashes out with:
fatal error: unexpectedly found nil while unwrapping an Optional value
I have tried everything I can think of or read on the internet:
Removed and replaced the connection
Deleted the derived data folder like one post suggested
Created a reference to self.view to force it to load
Moved it to viewDidAppear
Moved it to viewWillAppear
Moved it to viewDidLoad (which is where it is currently being
called)
I am sure at this point that the answer is rather simple and I am just completely missing it.

To see where the outlet is being set to nil, try this:
#IBOutlet weak var labelView: UIView? {
didSet {
print("labelView: \(labelView)")
}
}
You should see it set to an initial value when the view is loaded. If it then gets set to nil, put a breakpoint on the print and your should be able to see from the backtrace where it's happening.

Views are lazy initialized. In case you are calling the affected line of code before viewDidLoad() in the views life cycle, try to access viewin advance:
if detail.description == "About" {
_ = self.view
labelView.backgroundColor = UIColor.red
}

Related

Weak var outlet is lost (=nil) when referred to in a delegate method

I have a UICollectionView in my class declared as
#IBOutlet weak var artworkCollectionView: UICollectionView!
Inside this class there is one delegate method called by two other View Controllers, one of these VC is a pop up, the other one is a normal VC.
The delegate method gets some data from the database and then updates the collection view calling inside a closure:
self.artworkCollectionView.reloadData()
When the delegate method is called by the pop up VC, then all works great. BUT when the delegate method is called by the normal VC when it gets to self.artworkCollectionView.reloadData() it gets the infamous Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value.
I have checked all the references to the cell reuseIdentifier and all is correct. I suspect that since the UICollectionView is declared as weak var, when I go from the current class to the pop up and then the pop up calls the delegate methods, the reference is not lost, but when I go from the current class to the normal VC and then the normal VC calls the delegate method the reference to my weak var is lost and so it is "seen" as nil.
#IBOutlet weak var artworkCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Set up
artworkCollectionView.dataSource = self
artworkCollectionView.delegate = self
artworkCollectionView.isUserInteractionEnabled = true
artworkCollectionView.allowsSelection = true
artworkCollectionView.register(UINib(nibName:
"MyCollectionViewCell", bundle: nil),
forCellWithReuseIdentifier: "cell")
}
// delegate method
func reloadCollections() {
retrieveAlbumRatings { (isAlbum) in
if isAlbum {
self.retrieveAlbumData(completion: { (isFinished) in
if isFinished {
// Reload collection views
self.artworkCollectionView.reloadData()
}
})
}
}
}
If I am right, my question is: how can I give weak var artworkCollectionView: UICollectionView! a STRONG reference so that it does not get lost in the flow from the current class to the normal VC and back?
EDIT: here is what I tried so far:
Remove “weak” from the outlet declaration so making it: #IBOutlet var artworkCollectionView: UICollectionView!
But I got the same error
I passed artworkCollectionView to the normal VC via override performSegue and then passed it back as an argument of the delegate method. This does not give me the fatal error but also it does not reload the UICollectionView because I think that anyway the weak reference to the UICollectionView outlet is lost.
Thanks for your help (disclaimer: I am pretty new to Swift..)
Inside this class there is one delegate method called by two other
View Controllers, one of these VC is a pop up, the other one is a
normal VC.
The delegate method gets some data from the database and then updates
the collection view calling inside a closure:
self.artworkCollectionView.reloadData()
The flow appears to be that you have a VC containing the code above, the VC can either open a pop-up or just do a standard push segue to the "normal VC".
You want some operation to occur in either the pop-up VC or normal VC, load some data and then when the user is directed back to the originating VC, the UICollectionView is updated with that data.
Your problems are the following:
I passed artworkCollectionView to the normal VC via override
performSegue and then passed it back as an argument of the delegate
method. This does not give me the fatal error but also it does not
reload the UICollectionView because I think that anyway the weak
reference to the UICollectionView outlet is lost.
You shouldn't be passing anything around like this in most cases unless you have a really good reason to do so (I don't see one).
You want a separation of concerns here. You have to think carefully about what you wanjt to pass between VCs to avoid making weird dependencies between them. I wouldn't pass outlets for multiple reasons, the first being that you now have to keep track of the outlet in multiple VCs if you ever decide to change it. The second being that it requires too much mental gymnastics to keep track of the state of the outlet since it's being passed around everywhere. The outlets are also only guaranteed to be set at certain phases of the lifecycle. For example if you retrieve the destination VC from the segue in prepareForSegue:sender: and attempt to reference the outlets at that time, they will all be nil because they haven't been set yet.
These are all good reasons why the VC that contains the code above should be the one (and the only one) maintaining control over what gets displayed in artworkCollectionView and when. The problem here is how you're approaching this, rather than having the pop-up or normal VC call the delegate method or doing weird things like passing outlets from one VC to another, just pass the data around instead.
The simplest example is:
The pop-up VC and normal VC call some code to actually fetch the
data.
Then depending on how you actually segued to the pop-up VC or
normal VC from original VC, use either parentViewController or
presentingViewController to get the reference to the original VC.
Set the data into the original VC through that reference.
Dismiss the pop-up VC or normal VC if necessary (depends on your specific app, maybe you want the user to push a UIButton to dismiss instead of doing it for them).
When the original VC comes back into view, add some code to a lifecycle method like
viewWillAppear to have it load the contents of the data into the
UICollectionView at that time.
I see no reason why you should be passing any outlets outside of the original VC that should be the one managing it.
Short answer: Don't do that. You should treat a view controller's views as private. You should add a method to your view controller that other objects call to tell it to reload it's collection view.
The longer answer is that your view controller's collection view should stick around as long as the view controller is on-screen. It sounds like you don't have a very strong understanding of object lifecycle and how ARC works. You should read up on that and do some exercises until you understand it better.
Try something like this:
//Make artworkCollectionView a normal weak var, not implicitly unwrapped.
//You'll need to change your other code to unwrap it every time you use it.
#IBOutlet weak var artworkCollectionView: UICollectionView?
...
func reloadCollections() {
retrieveAlbumRatings { (isAlbum) in
if isAlbum {
//The construct `[weak self]` below is called a capture list
self.retrieveAlbumData(completion: { [weak self] (isFinished) in
guard let weakSelf = self else {
print("self is nil");
return
}
}
if isFinished {
// Reload collection views
guard let collectionView = weakSelf.artworkCollectionView else {
print("collectionView is nil!")
return
}
collectionView.reloadData()
})
}
}
}

Fatal error: Unexpectedly found nil while changing Image and Label

I am trying to change a picture and a label programmatically.
Here's the current code setup:
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var label: UILabel!
override func viewDidLoad(){
super.viewDidLoad()
setlabel()
setImage()
}
func setImage(){
self.image.image = UIImage(named: name)
}
func setlabel(){
self.label.text = string
}
But unfortunately, I'm receiving nil for both UI elements, the label and the image. I've doublechecked the connection between my storyboard and the variables- they are set correctly, imho. They are available, since I am calling them outside the viewDidLoad function.
Anyone any suggestion?
Regards
I found the solution. I had 2 views- let's call them A and B.
A contained a button- and the button had 2 functions: Change the view and set the labels/images. Those functions were defined in the Controller of view A.
Since I wanted to change the labels and images of view B, I got the exception.
I had to include another Controller and transfer the functions for setting the images and labels to Controller B. Voila, I could change the images and labels.
Summarized: I had the right connections and the right functions, but in the wrong place.
Thank you all!

swift - How to correctly set up a UITextView

I have a view controller that only has a UITextView in it. I'm having a problem initializing the UITextView with another one that I pass to the view controller.
Here's the code for the view controller:
(The File class has a string variable: name, and UITextView variable: content)
// MARK: Properties
var file: File?
#IBOutlet weak var textViewHome: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
navigationItem.title = file?.name
self.automaticallyAdjustsScrollViewInsets = false
print(file?.content.text)
print(textViewHome.text)
textViewHome = file?.content
print(textViewHome.text)
}
The output of those print statements are:
Optional("hello")
hello
So the textViewHome variable gets set with the correct string but nothing shows up in text view when the app is run. I've seen a lot of questions similar to this but haven't found one where the UITextView.text variable is correct but nothing actually shows up. Thanks for any advice
The main problem is that your view hierarchy (what's is showing on the screen) had textViewHome because it's a subview of viewController.view. When you changed the pointer of textViewHome, the view hierarchy still had the old one because you changes the viewController's pointer and you didn't touch the hierarchy.
Design Improvement: It's better to let the File own a string and not UITextView.
class File{
var name: String
var content: String
}
In your viewController, you will have
textViewHome.text = file?.content //content is a string

Swift - UIView.transitionFromView -

What happens to your “fromView” after you use UIView.transitionFromView? How do you get it back? (re-initialize it?).
Here’s a simple example I made and get “fatal error: unexpectedly found nil while unwrapping an Optional value” when I try to get back to the “fromView” or reference a label on it, etc.
Let’s say I’ve set these views in my storyboard - I added a subview to the root called “cardView” and then 2 views under cardView called “backView” and “frontView"
Linked these up to the view controller and linked the Flip button to an action that looks like this (along with the variables):
#IBOutlet weak var backView: UIView!
#IBOutlet weak var frontView: UIView!
#IBOutlet var cardView: UIView!
var front = true
#IBAction func flipIt(sender: AnyObject) {
if front {
UIView.transitionFromView(self.frontView, toView: self.backView, duration: 1, options: UIViewAnimationOptions.TransitionFlipFromRight, completion: nil)
} else {
UIView.transitionFromView(self.backView, toView: self.frontView, duration: 1, options: UIViewAnimationOptions.TransitionFlipFromLeft, completion: nil)
}
front = !front
}
So when I run this - the first tap of the “Flip” button does fine, it flips over to the backView, but then when I tap it again I get “fatal error: unexpectedly found nil while unwrapping an Optional value” on the 2nd transitionFromView line (the one under “else”).
Obviously there’s some effect of transitionFromView that I don’t understand. I've tried a lot of different things, including a tutorial that uses this transition and was able to get it working but it doesn’t use the storyboard so I’m not sure how to apply how that tut does it to my storyboard views, apparently...
thanks for any help.
You've declared your view properties as weak, so they're getting released as soon as it appears they're no longer needed. Just change them to strong (by removing "weak") and you should be golden:
#IBOutlet var backView: UIView!
#IBOutlet var frontView: UIView!

Swift: UIImageView - Unexpectedly found nil while unwrapping an Optional value

class DisplayImageVC: BasePageView { //BasePageView inherits from UIViewController
#IBOutlet weak var displayImage: UIImageView!
override func viewDidLoad() {
self.displayImage.backgroundColor = UIColor.yellowColor()
}
....
in another class I try this and get a fatal error:
var displayImageView = self._pageContent[1] as DisplayImageVC
displayImageView.displayImage.image = UIImage(named: ("displayChecklane"))
Log:
fatal error: unexpectedly found nil while unwrapping an Optional value
Images.xcassets
var displayImageView = self._pageContent[1] as DisplayImageVC
displayImageView.displayImage.image = UIImage(named: ("displayChecklane"))
You are crashing on the second line. You know this is because something is nil, but you do not know what is nil here. You are assuming it is the image. That is a false assumption; if it were correct, there would be no crash, as you are not force-unwrapping it, and it is legal to assign nil to an image view's image.
Instead, consider this: maybe displayImageView.displayImage is nil. That is, what is nil is the UIImageView you are trying to assign to. This would make sense since this is an outlet. If the DisplayImageVC's view has not loaded, its outlets have not been filled - they are still nil.
How you solve this depends on what you want to do. Personally I think your approach is bogus from the start; you have no business setting another view controller's outlet or a property of its outlet, or any aspect of its interface. You should have an image property in DisplayImageVC, and here, you should be setting that property; that way, when the view controller shows its view, it can configure its own interface.