12

I have a pretty complicated table view setup and I resolved to use a block structure for creating and selecting the cells to simplify the future development and changes.

The structure I'm using looks like this:

var dataSource: [(
    cells:[ (type: DetailSection, createCell: ((indexPath: NSIndexPath) -> UITableViewCell), selectCell: ((indexPath: NSIndexPath) -> ())?, value: Value?)], 
    sectionHeader: (Int -> UITableViewHeaderFooterView)?, 
    sectionFooter: (Int -> UITableViewHeaderFooterView)?
)] = []

I can then set up the table in a setup function and make my delegate methods fairly simple

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = dataSource[indexPath.section].cells[indexPath.row].createCell(indexPath:indexPath)
    return cell
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return dataSource[section].cells.count
}

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return dataSource.count
}

I have made a similar setup before in another TVC

var otherVCDataSource: [[ (type: DetailSection, createCell: ((indexPath: NSIndexPath) -> UITableViewCell), selectCell: ((indexPath: NSIndexPath) -> ())?)]] = []

This solution has worked great.

The current dataSource with the sectionHead and footer however gives me a EXC_BAD_ACCESS every time I try to access the indexPath in one of the createCell blocks.

createCell: {
    (indexPath) in
    let cell:CompactExerciseCell = self.tableView.dequeueReusableCellWithIdentifier(self.compactExerciseCellName, forIndexPath:indexPath) as! CompactExerciseCell
    cell.nameLabel.text = "\(indexPath.row)"
    cell.layoutMargins = UIEdgeInsetsZero
    return cell
}

The app always crashes on

self.tableView.dequeueReusableCellWithIdentifier(self.compactExerciseCellName, forIndexPath:indexPath)

What am I missing here? Why can't I access the indexPath in the new structure when it works fine in the old structure? What is different in the memory management between this tuple and the array?

UPDATE:

So I had a deadline to keep and I finally had to give up and rework the data structure.

My first attempt was to instead of sending the indexPath as a parameter send the row and section and rebuild an indexPath inside the block. This worked for everything inside the data structure but if I pushed another view controller on a cell click I got another extremely weird crash (some malloc error, which is strange as I use ARC) when dequeuing cells in the next VC.

I tried to dig around in this crash as well but there was no more time to spend on this so I had to move on to another solution.

Instead of this tuple-array [([],,)] I made two arrays; one for the cells and one for the headers and footers. This structure removed the problem of the indexPath crash but I still had the issue in the next VC that didn't stop crashing when dequeueing the cells.

The final solution, or workaround, was to access the cell creator and selector "safely" with this extension:

extension Array {
    subscript (safe index: Int) -> Element? {
        return indices ~= index ? self[index] : nil
    }
}

basically the return statement in the tableView delegate functions then looks like this:

return dataSource[safe:indexPath.section]?[safe:indexPath.row]?.createCell?(indexPath: indexPath)

instead of

return dataSource[indexPath.section][indexPath.row].createCell?(indexPath: indexPath)

I can't see how it makes any difference to the next VC as the cell shouldn't even exist if there was an issue with executing nil or looking for non existing indexes in the data structure but this still solved the problem I was having with the dequeueing of cells in the next VC.

I still have no clue why the change of data structure and the safe extension for getting values from an array helps and if someone has any idea I would be happy to hear it but I can not at this time experiment more with the solution. My guess is that the safe access of the values reallocated the values somehow and stopped them from being released. Maybe the tuple kept the compiler from understanding that the values should be kept in memory or maybe I just have a ghost in my code somewhere. I hope one day I can go back and dig through it in more detail...

Moriya
  • 7,750
  • 3
  • 35
  • 53
  • most probably the app crash in that line because the resulting cell is nil, not because your indexPath is not accessible. – user3441734 Dec 11 '15 at 10:30
  • Cell was not nil. I got around it by instead sending in row and section and creating the indexPath in the block but It's such an awful solution – Moriya Dec 11 '15 at 10:31
  • hm ... that is really interesting, i am not able to reproduce it. especially, because indexPath is reference type, the value must be captured from surrounding scope. did you try to access your indexPath before dequeueing the cell? simple print(indexPath) could be helpful to understand, what is wrong there ... – user3441734 Dec 11 '15 at 11:41
  • It's accessible before... However, After I made the ugly bypass by sending the row and section my next view that I push keeps failing when dequeueing a cell that has worked just fine before. I'm starting to consider a reinstallation of xcode or something. This just seem to strange... – Moriya Dec 11 '15 at 11:55
  • have you registered cell class for identifier in table view? – Mikhail Dec 11 '15 at 17:31
  • Yes, the cells are registered in both of the TVCs – Moriya Dec 11 '15 at 18:01
  • we need another code..where are you initialise the datasource i made a sample code using closure to create cells and it works fine – Mejdi Lassidi Dec 14 '15 at 08:00
  • Can you post the stack trace? – Peter Hornsby Dec 14 '15 at 13:00
  • 1
    `indexPath` wouldn't trigger a EXC_BAD_ACCESS on that line, as it isn't being dereferenced and is a non-optional. The easiest way to debug this would be to temporarily split that line up into as many separate lines as you can and see which one triggers the EXC_BAD_ACCESS. – adpalumbo Dec 18 '15 at 16:08
  • The most likely cause is that you don't have a table cell with the matching identifier (eg. a typo in IB). Change your `let cell` statement to be an `if let cell`, and then handle the unhappy path. – Michael Dec 21 '15 at 00:12
  • Adpalumbo, I tried the create an index path inside the block by instead sending in the row and section as int. This worked fine so it's definetly something strange happening with indexPath – Moriya Dec 21 '15 at 08:11

2 Answers2

0

This is NOT an answer to the question but rather a workaround if someone ends up in this hole and has to get out:

First use this extension for array:

extension Array {
    subscript (safe index: Int) -> Element? {
        return indices ~= index ? self[index] : nil
    }
}

And then in the table view delegate functions use the extension like this

let cell = dataSource[safe:indexPath.section]?[safe:indexPath.row]?.createCell?(indexPath: indexPath)

If this does not work remove the tuple from the data structure and you should have a working solution.

I wish you better luck with this issue than I had.

Moriya
  • 7,750
  • 3
  • 35
  • 53
-1

you have to register your tableview cell for particular cell idntifier in viewdidload. eg.tableview.registerNib(UINib(nibName: "cell_nib_name", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: "cell_identifier");

for deque cell

let cell:CompactExerciseCell = self.tableView.dequeueReusableCellWithIdentifier(self.compactExerciseCellName, forIndexPath:indexPath) as! CompactExerciseCell

like this.

Muthu Selvam
  • 350
  • 3
  • 12
  • As stated in the comment the cells are registered. The problem lays in the access of indexPath. I got around the problem by recreating the indexPath from row and section in the block. – Moriya Dec 21 '15 at 08:07