8

I have a subclass of UICollectionViewCell. The cell is visually designed in the storyboard with lots of components and these components are bound to the variables in Swift subclass using the storyboard.

The Swift class just provides logic for populating the components from dat retrieved from the data-source.

For example:

class InfoCollectionViewCell : UICollectionViewCell {

    @IBOutlet weak var mainPanel : UIView!
    @IBOutlet weak var panel1 : UIView!

    @IBOutlet weak var firstName : UILabel!
    @IBOutlet weak var lastName : UILabel!
    @IBOutlet weak var address : UILabel!
    etc ...

    func setVariousProperties(etc) {
        firstName.text = ... etc

The data-source does the usual thing:

let cell = collectionView.dequeueReusableCellWithReuseIdentifier("InfoCell", forIndexPath:indexPath)
if let c : InfoCollectionViewCell = cell as? InfoCollectionViewCell {
    c.setVariousProperties(...)
}

I originally only implemented decode/encode methods containing a "not implemented" assertion, but it became apparent that occasionally the framework was encoding and decoding the class. I implemented dummy encode/decode methods without saving the components resulted in predictable problems with nil values when the components were accessed.

So it appears that I am required either to implement encoding and decoding of all the controls in the UICollectionViewCell subclass one-by-one, or I have to find a better way.

It seems a waste of time as I don't actually need (I don't think) to save the contents of the components since they are just going to be for re-use anyway by the subclass: I will overwrite the component contents will values from the datasource.

Obviously, all of the controls are defined in the storyboard. I could manually fetch them by name from the storyboard in the init method, but that seems equally tedious and makes the graphical linking of the controls and variables redundant.

Is there a better way?

Can I just say "restore connections" or something like that?

EDIT:

Somewhere between posting the question and adding the bounty, the problem stopped happening. I now notice that the encode method of my components is not getting called. So for some reason, the framework was deciding to serialise my objects and deserialise them, but now it isn't. Hence the problem is not occurring and I am not able to supply a stack-trace.

It is conceivable that some update to XCode has fixed this issue, or it could be something else.

I am obviously still concerned there is some bug lurking there somewhere.

rghome
  • 8,529
  • 8
  • 43
  • 62

1 Answers1

2

You shouldn't need to implement encoding/decoding for any of the controls that have been laid out and connected in the Storyboard. This will be taken care of at runtime and you will get a cell back after the collectionView.dequeueReusableCellWithReuseIdentifier as long as everything else has been connected correctly with the UI components ready to use. The things to check are:

  1. The subclass is specified in the storyboard custom class section
  2. The reuseIdentifier is defined to match the string you expect
  3. All the IBOutlets are connected and should also show the connections in the code next the IBOutlet var lines.

Once you have ensured these are right try printing out the components to check if they are still nil and placing some simple text in there to eliminate any dataSource problems:

  func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("InfoCell", forIndexPath:indexPath)
    if let c = cell as? InfoCollectionViewCell{
      print(c.address)
      print(c.firstName)
      print(c.lastName)

      c.firstName.text = "firstName Test"
      c.lastName.text = "lastName Test"
      c.address.text = "address Test"
    }
    return cell
  }
}
Dallas Johnson
  • 1,546
  • 10
  • 13
  • Of course, I can't reproduce it now. It was a problem that was fairly easily reproducible on a view with 100 cells when a scroll was executed, but not on views with fewer cells and not on the first screen shown. I presume something triggered serialisation of cells. It now bothers me that there is an intermittent bug there, as all of the suggestions you made must have been applied anyway, otherwise I would not have had the first screen of cells working. The errors I got showed clearly that init(coder) was being called and clearly that the IBOutlets were not being wired up after the decode. – rghome Jan 27 '16 at 22:12
  • I'd like this to be the right answer. I am just not convinced that it is. As I said above, the encode/decode calls stopped happening for reasons unknown. Even if it was a bug in some version of IOS (or in the simulator), I can't be sure that it is fixed on all devices. It sounds like the best strategy is to play it safe and implement decode and encode for all components. – rghome Feb 01 '16 at 14:39
  • When you say implement the encode/decode methods do you mean ```init(coder decoder: NSCoder)``` or are you talking about something different/custom? If it is the init I would expect that to be called only when a new instance is being created/allocated and not each time a cell is dequeued and used by the UICollectionview. The point of dequeuing is prevent excess deallocation and then reallocation of cells that are basically the same. I have never needed to implement encode/decode methods on cells but maybe I've just been lucky. – Dallas Johnson Feb 01 '16 at 14:59
  • Yes - I mean `init(coder decoder: NSCoder)` and `encodeWithCoder(_ aCoder: NSCoder)`. I am sure I saw these being called and I certainly didn't call them directly. It occurs to me that if a component was referenced indirectly from by my app-saved-state (which I do save) then it could happen. But it seems unlikely. There would have to be a back-reference of some kind. – rghome Feb 01 '16 at 15:07
  • I would expect all the components to have ```init(coder decoder: NSCoder)``` called as they need to be instantiated from the compiled Nib/Storyboard file but I would not expect you need to override it unless you are doing something quite custom (I can't even think of a good example but maybe if you were a subclassing a control). I would not expect this to be called when dequeuing since the cell will have already be instantiated and waiting in a queue except on the first appearance. – Dallas Johnson Feb 01 '16 at 15:27
  • However, If you do override the init without calling ```super.init...``` then I would expect you to see nil's on your components. Unless you really need to customise the init then just allow the default init to run without overriding. – Dallas Johnson Feb 01 '16 at 15:29
  • There could be some gross human error on my part. I could have forgotten super.init, then assumed init and encodeWithEncoder needed to be implemented, which fixed the problem. Then imagined at some point I saw encodeWithEncoder being called. – rghome Feb 01 '16 at 15:36