6

Usually I store data in an array. Then, when cellForRowAtIndexPath is called I just look at row and select an item on the array based on row and process.

But UITableView as we know can do group view.

So what should I do?

Should I have an array of array? An NSDictionary of array? What would be the most elegant way to store data in UITableView structure?

user4951
  • 32,206
  • 53
  • 172
  • 282

3 Answers3

12

For example an array of dictionaries, where each dictionary holds the title and all items of one section:

NSArray *dataSource = @[
                    @{@"title": @"Section 0",
                      @"rows" : @[ item00, item01, item02] },
                    @{@"title": @"Section 1",
                      @"rows" : @[ item10, item11, item12] },
                    @{@"title": @"Section 2",
                      @"rows" : @[ item20, item21, item22] },
                    ];

The items can be strings or objects of a custom class. Then you can access each item in cellForRowAtIndexPath like

Item *item = dataSource[indexPath.section][@"rows"][indexPath.row];

and all other data source methods are also easily implemented.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
1

@Martin answer is correct for Objective-C but in Swift, we don't have the luxury of having variable types for a dictionary. The types are predefined.

We will need use struct or custom data type to work around.

struct Model<Item>{
  let title: String
  let rows: [Item]

  subscript(index: String) -> [Item] {
    get {
      return rows
    }
  }
}

let model1 = Model(title: "Secton 0", rows: ["A", "B", "C"])
let model2 = Model(title: "Secton 1", rows: ["D", "E", "F"])

let dataSource = [model1, model2]

// You can query
dataSource[indexPath.section][rows][indexPath.row]
Rahul
  • 2,056
  • 1
  • 21
  • 37
0

I like this pattern enabled by Swift:

let sections = [(title: "Alpha", rows: ["A", "B", "C"]),
               (title: "Numeric", rows: ["1", "2", "3"])]()

Then in cell for row at index path:

let data = sections[indexPath.section].rows[indexPath.row]

Thanks to Martin R's answer for inspiration. My actual implementation uses another pattern that I have to use disparate cell classes and data types like this:

protocol BaseViewModel {
    var cellIdentifier: String
}

class BaseCell: UITableViewCell {
    func setupWith(viewModel: BaseViewModel) {}
}

class SpecificCell: BaseCell {
    override func setupWith(viewModel: BaseViewModel {
        if let viewModel = viewModel as? SpecificCellViewModel {
            // set properties from object conforming to my vm protocol
        }
    }
}

Implementation:

let sections: [(title: String, rows: [BaseViewModel])]

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let viewModel = sections[indexPath.section].rows[indexPath.row]
    let cell = let tableCell = tableView.dequeueReusableCell(withIdentifier: viewModel.cellIdentifier , for: indexPath) as? BaseTableViewCell
    tableCell?.setupWith(viewModel: viewModel)

    return tableCell ?? UITableViewCell()
}

Happy coding! Let me know with a comment if this needs further elaboration or has any errors.

Eric Mentele
  • 864
  • 9
  • 16