0

Just about a month ago I started learning swift (mainly from books), with no prior programming experience whatsoever. I am still having trouble wrapping my head around several concepts, mainly value types and reference types, and things such as delegation. This is probably the reason why I can’t seem to find an answer for the following problem.

I have an Xcode project with 2 classes as in 2 swift files.

  1. A viewController class containing a collection view;
  2. A collectionViewCell class.

In the viewController class I have a method to disable and enable user interaction of the collectionView:

func collectionViewInteractionEnabled(boolean: Bool) {
   collectionView.userInteractionEnabled = boolean
}

When called from the viewController class, the method works. But when called from the collectionViewCell class, i get this:

"fatal error: unexpectedly found nil while unwrapping an Optional value”

Xcode highlights the contents of my function as the culprit. This makes me think that the collectionView is not there for the collectionViewCell class. But my infant programming brain really can’t follow the thought process needed to figure this out.

The only solution I found was using a notification to let the function perform. It works, but isn’t there another way that I should know about? Is it possible to call a function from a different class, and still have it perform as if it was being called from the class wherein the function is declared?

Edit: Update after trying Patricks' advice:

The question mark does stop the app from crashing, but does not change the behavior of the code. The collectionViewis indeed created in storyboard with 3 outlets connected, the reference, and the datasource and delegate.

The function is called and performs correctly from within the viewController class. So I assume that it is not nil. If i try to call it from the collectionViewCell class (a different .swift file), then it aparently is nil. I thought the collectionViewCell' class just can't see it since thecollectionView` is created somewhere else? It makes no sense to me and the more I think about it, the more I get lost.

Could it be that I am just calling the function wrong from the other file? I am using:

In the collectionViewCell class

ViewController().collectionViewInteractionEnabled(true)

HugoLXO
  • 222
  • 3
  • 10

2 Answers2

2

I'm assuming your collectionView property is an @IBOutlet, which will have a type of UICollectionView!, which is an implicitly unwrapped type (as indicated by the !). This means that this value may or may not be nil, and if you try to access it directly when it is nil, your app will crash with the exact error message you are receiving. There are several ways to "unwrap" these values in order to work with them safely if they are not nil. In your case, I would do this:

collectionView?.userInteractionEnabled = boolean

This is called Optional Chaining. If your collectionView is nil when this line executes, the execution will abort and move to the next line. But if collectionView is defined to a non-nil value, then the userInteractionEnabled property will be set to the value of boolean. Now, of course, why is your collectionView nil? If this is an @IBOutlet, you've probably not connected the referencing outlet in Interface Builder. If you're creating the collection view programmatically, then you're trying to access it before you've defined it. Furthermore, you may still get this error in either case if you have not called this function before the viewDidLoad() method is called the view controller that owns the collection view. At that point, your view is loaded and your collectionView value will be defined, and you may safely interact with it.

Patrick Lynch
  • 2,742
  • 1
  • 16
  • 18
  • Thank you so much for your detailed answer Patrick. Certainly the order of the `viewDidLoad()` method is something to keep in mind for me from now on. But still no luck. I updated my question to reflect your answer, it didn't fit in the comments :) – HugoLXO Jul 29 '15 at 22:15
  • Can you post the project in a GitHub repo? I'd be happy to debug it for you. – Patrick Lynch Jul 29 '15 at 22:31
  • I need to figure out how to make a repo (I remember reading about it in a book, I'll have to look it up). Is it possible to send a private message with the repo afterwards though? I'm a bit shy of having my mess public! – HugoLXO Jul 29 '15 at 22:45
  • It's okay, no one's going to judge we just want to help. Post a .zip and once we find the answer you can delete it. – Patrick Lynch Jul 29 '15 at 22:53
0

After looking at your code, here is the problem:

MemoryController().memoryCollectionViewUserInteractionEnabled(true)

When you call MemoryController() you are creating a new instance of that class, not a reference to the one loaded from your storyboard as I assume you are expecting. There is a special procedure for instantiating a view controller from a storyboard. If you instantiate without a storyboard as you are doing, your views will not be defined and linked as @IBOutlet references, and will be nil, hence the crash.

But that's not what you really want anyway—you want a reference the one loaded from the storyboard. An easy way to do that is to set up a delegation relationship between your cell and the view controller. Have your view controller implement a protocol, and have your cell use a property typed to that protocol that the view controller can then set to self when it creates the cell.

// Define the protocol
protocol MemoryCardCellDelegate {
    func cardDidTurnDown()
}

class MemoryCardCell: UICollectionViewCell, UICollectionViewDelegate {
    // Create property typed to the protocol
    var delegate: MemoryCardCellDelegate?

    func turnDownAfterTimer() {
        // Call the method on your delegate (which is really your view controller) 
        self.delegate?.cardDidTurnDown()
    }
}

// Add protocol conformance to type declaratio
class MemoryController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, MemoryCardCellDelegate {

    // Implement the protocol method
    func cardDidTurnDown() {
        memoryCollectionViewUserInteractionEnabled(true)
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        if let cell = memoryCollectionView.dequeueReusableCellWithReuseIdentifier("MemoryCardCell", forIndexPath: indexPath) as? MemoryCardCell {
            let image = UIImage(named: "Mem Card Back")
            cell.cardImageView.image = image
            cell.delegate = self  //< Set the delegate
            return cell
        }
        fatalError( "Could not dqueue MemoryCardCell." )
    }
}
Community
  • 1
  • 1
Patrick Lynch
  • 2,742
  • 1
  • 16
  • 18
  • Thank you so much Patrick, you are amazing. I was lucky you crossed my path. I'll implement this in the morning (my morning, I'm from the Netherlands, hence my broken English, though not as broken as my code). Hopefully you didn't see too much weird stuff. Once I find all the logic and everything is in place, I'll start cleaning up and trying to figure out different and more efficient ways of doing things. Now that you corrected it with the delegate, I know where to pick up my books en work on it some more. Again, thanks soon much! :) – HugoLXO Jul 29 '15 at 23:56
  • Couldn't wait till morning... works like a charm <3 Will study it. – HugoLXO Jul 30 '15 at 00:13