Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

grandM

macrumors 68000
Original poster
Oct 14, 2013
1,539
304
I have this piece of code which works like a charm:

Code:
var gallery: Gallery?
var currentGalleryItem: GalleryItem?

func buildScreen() {

   

        if let currentItem = self.currentGalleryItem {


            // the user may not have set only a thumbnail and no photo

            if let currentPhoto = currentItem.photo.photo {

                self.imageView.image = UIImage(data: currentPhoto)

            }

        } else {

            self.imageView.image = UIImage(named: "addImage")

        }

    }

func showNextGalleryItem() {

        if let currentItem = self.currentGalleryItem {

            self.currentGalleryItem = self.gallery?.nextGalleryItem(currentItem.indexInTheArray)

            self.buildScreen()

        } else {

            self.noPhotosAlert()

        }

    }

As I also have a previousGalleryItem function I came up with the idea to add a computed property:
Code:
   var currentGalleryItem: GalleryItem?

        {

        didSet {

            self.buildScreen()

        }

    }

Then my code stops working. It throws me the error that it can not unwrap the optional. It highlights the code
Code:
 self.imageView.image = UIImage(data: currentPhoto)
. I do not understand why.
 
I'm still learning Swift, but doesn't that mean the result of UIImage(data: currentPhoto) is nil?
What if you wrap it in an if and see what that value is before you assign the value to .image?
 
I'm still learning Swift, but doesn't that mean the result of UIImage(data: currentPhoto) is nil?
What if you wrap it in an if and see what that value is before you assign the value to .image?
why would it be nil
I'm assigning a value to currentGalleryItem in the func showNextGalleryItem()
Code:
self.currentGalleryItem = self.gallery?.nextGalleryItem(currentItem.indexInTheArray)

I recall printing currentPhoto and getting data :s Perhaps it was another print but if I just call self.buildScreen() no error is thrown
 
Isn't unwrap only on optionals that are nil?

I'm asking because I'm not 100% sure. Wouldn't the system only unwrap a var if it is both optional and nil?

Is it telling you that it's trying to unwrap something that is a nil and it's returning a nil?

Another thought is that you are using didset, so (correct me where I'm wrong) but that gets called when it's set for the 1st time? Is it nil the 1st time?

I'm kinda lost, does didset get called only on an init or every time the value changes?

Am I correct in assuming the system will only unwrap a var when it's both an optional and nil?
 
unwrapping an optional that is nil will crash the system
an optional can obtain a value or nil
I thought didSet got called each time the variable was given a new value
I thought the if let construction made sure the statements within the construction were only executed when self.currentGalleryItem differs from nil
 
unwrapping an optional that is nil will crash the system
an optional can obtain a value or nil
I thought didSet got called each time the variable was given a new value
I thought the if let construction made sure the statements within the construction were only executed when self.currentGalleryItem differs from nil

Ok, that sounds reasonable except I thought the if let will execute but won't assign the value if it's nil and won't get passed the 'if' part such that no other statements get executed.

So the crash you are getting is from unwrapping a nil?
 
Ok, that sounds reasonable except I thought the if let will execute but won't assign the value if it's nil and won't get passed the 'if' part such that no other statements get executed.

So the crash you are getting is from unwrapping a nil?
yes hence my astonishment
 
Ok, there's a part I need to go back to because i didn't understand it. It came from the Stanford videos and it talked about a strange error in Swift that was fixed with either a ? or !

IIRC it was the 2015 version but I don't remember where it was or the logic behind it.

Here's a link to the #7 video, but it might be in another video. I'm at #8, so it's behind that and probably #5,6 or 7.

One other issue that I'm fuzzy on is that it can change from nil to not nil. I remember him (teacher on Stanford video) saying something about he would be sure it was not nil at one point but not at another point.

Maybe some other method is being called that changes it's state.
 
I think it had to deal with making something optional during an assign or forcing an unwrap during an assign.
Remember, I don't know if this is related to the problem you're having.
 
Most likely there's an issue with self.imageView being nil since that's the only variable your accessing properties of.

I don't know for sure (can't look through it all right now), but I believe so because if data is nil then your UIImage initializer would just return nil (since no data exists and it's a subclass of NSObject, which allows that behavior). We can safely pass any returned form to the image property on UIImageView since it is an optional, too.

I'd set a break point just prior to the code that's throwing your error and see which variables you expect to be there are nil at runtime.

Edit: my apologies - data can not be nil when using it as material to create a UIImage. It can be unreadable, just not an optional. That could be another hole.
 
Last edited:
If you NSLog the vars involved (the image view and the uiimage) which of those two is nil?

A lot of new swift programmers don't use NSLog anywhere near as often as they should to troubleshoot issues like this, I think it's because it looks more complicated than it did with ObjC.
 
If you NSLog the vars involved (the image view and the uiimage) which of those two is nil?

A lot of new swift programmers don't use NSLog anywhere near as often as they should to troubleshoot issues like this, I think it's because it looks more complicated than it did with ObjC.

Swift's equivalent is print but it'd probably be better to set a breakpoint so you can see all variables and each loop until you hit the issue (and that could help expose an out of bounds issue).
 
Good news! I solved the problem. Once again I learned something new and am glad for your help. The problem was the following. The photo did have data. It was the imageView that was not set. The imageView is an IBOutlet. I noticed the viewDidLoad() never being called before the arising of the error. In my viewDidLoad I also called for self.buildScreen() by the way.

I never expected that didSet would be called before viewDidLoad() was called for. As viewDidLoad() wasn't called for yet the IBOutlets did not exist yet. Hence self.imageView gave a nil.

I solved it by going (I guess making all IBOutlets optional by using the question mark could have solved it too):
Code:
   var currentGalleryItem: GalleryItem?

        {

        didSet {

            if let_ = self.view {

                self.buildScreen()

            }

        }

    }

As I am using prepareForSegue to set currentGalleryItem the didSet of currentGalleryItem was being called before the View got loaded. I hope my post will be useful to other people in the future :) Once more my sincere thanks to you guys (and women).
 
Last edited:
  • Like
Reactions: Mascots
Ok, glad your problem is solved, but I'm confused.

I thought IBOutlet was preprocessed out to nothing. Meaning it's not a type and has NO value to the code except to tell Interface Builder to load it.

So what does it mean when you say "The imageView is an IBOutlet" ? Do you mean the imageView is built in Interface Builder, which means it is loaded at runtime and didn't get loaded before the call and was therefore nil?

If so, wouldn't a forced unwrap be the proper cure?
 
Ok, glad your problem is solved, but I'm confused.

I thought IBOutlet was preprocessed out to nothing. Meaning it's not a type and has NO value to the code except to tell Interface Builder to load it.

So what does it mean when you say "The imageView is an IBOutlet" ? Do you mean the imageView is built in Interface Builder, which means it is loaded at runtime and didn't get loaded before the call and was therefore nil?

If so, wouldn't a forced unwrap be the proper cure?
Your interpretation is correct. The imageview if an instance of UIImageView. As the imageview is a part of the view it was build in Interface Builder. Therefore the code starts out with @IBOutlet just pointing at the connection with the view. The corresponding code is:
Code:
 @IBOutlet weak var imageView: UIImageView!
As you can see it is forced unwrapped. But that just resulted in the errors. I understand your question. I do not know why forced unwrapping it did not solve the problem.
I just ran a test again to get more information. The problem was indeed occuring at self.imageView.image. The problem arose before viewDidLoad got called for. If I changed self.imageView.image to self.imageView?.image that error disappeared. It got stuck at another UI element being
Code:
self.previousBarButtonItem.enabled = true
which resulted in the same error being "fatal error: unexpectedly found nil while unwrapping an Optional value". I find it strange too as I am setting the element. One thing is for sure viewDidLoad did not appear yet. If anyone can shed a light I'm listening for sure.
 
Ok, so viewDidLoad wasn't called yet and you're processing an image.
The part of the Stanford video I'm watching now just covered some of that. IIRC, He said not to do any processing of an image before viewDidLoad and even before viewWillAppear. I think it's the example where he loads large images from a site. One reason is that you have no view yet, so you can't communicate to the user what's going on if it fails, takes a long time or if you have no connection.

That's not to say you must, because I don't know that, but it might be "best practice" because UI alway must be on the main thread and has the highest priority.

I think it's also in line with lazy instantiation and maybe that's at the root of the unexpected behavior.

Doesn't apple use lazy instantiation by default and wouldn't that cause it to be nil and if you followed the same path, the error might go away?

Don't know for sure, but would be easy to check, just more the call to viewWillAppear and comment it out of the other spots.
 
Ok, so viewDidLoad wasn't called yet and you're processing an image.
The part of the Stanford video I'm watching now just covered some of that. IIRC, He said not to do any processing of an image before viewDidLoad and even before viewWillAppear. I think it's the example where he loads large images from a site. One reason is that you have no view yet, so you can't communicate to the user what's going on if it fails, takes a long time or if you have no connection.

That's not to say you must, because I don't know that, but it might be "best practice" because UI alway must be on the main thread and has the highest priority.

I think it's also in line with lazy instantiation and maybe that's at the root of the unexpected behavior.

Doesn't apple use lazy instantiation by default and wouldn't that cause it to be nil and if you followed the same path, the error might go away?

Don't know for sure, but would be easy to check, just more the call to viewWillAppear and comment it out of the other spots.
I hadn't looked into the stanford course again. I did see you posted the film of it, was still going to look at it. But I guess that explains it. I'm processing an image. By adding the if let _ = self.view construction I'm playing it safe. Then the view must exist and the error disappears.
 
Last edited:
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.