Reputation: 1442
Here is a sample I want to achieve. In general I have a dataSource like that an array of [Category]
:
public struct Category {
let name: String
let image: UIImage
let id: Int
let subCategories: [SubCategory]
var onCategorySelected: (Int) -> Void
}
public struct SubCategory {
let name: String
let id: Int
var onSubCategorySelected: (Int) -> Void
}
I've started with approach that Category
is a section and SubCategory
is a row. So in numberOfSections
I return category array count
and in numberOfRowsInSection
I return 1
if category is not selected and subcategory.count + 1
if selected. On cellForRowAt
I setup proper cell from a custom class.
And I stuck on tableview delegate method: didSelectRowAt indexPath
. How can I collapse or expand rows in specific section. I am trying at the moment:
public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath)
if indexPath.section == selectedCategory {
var indexToRemove: [IndexPath] = []
(1...props.categories[selectedCategory].subCategories.count).forEach { idx in
indexToRemove.append(IndexPath(row: idx, section: selectedCategory))
}
tableView.beginUpdates()
tableView.deleteRows(at: indexToRemove, with: .top)
tableView.endUpdates()
}
}
I got a crush because I don't update a data source but it seems that I don't actually need to remove these elements from a data source but only hide if first row in section is not selected and show immediately if selected. Any help or even general approach for displaying such structure is welcomed! Thanks!
Upvotes: 1
Views: 774
Reputation: 4200
I think you'll struggle in this way as category headers are don't have built in handlers for tap events (although you could build them into custom header views) but also when the numberOfRows for a section = 0 the section header isn't shown.
A better approach would be to put all entries in a single tableView section with two types of custom cell: one cell design for the categories and one for the sub-categories. Add a Bool
variable to Category
called something like expanded
to track when a section has been expanded. Create an array that hold an entry for each visible cell using an enum with associated values to show whether its a Category or subCategory. Then in didSelectRowAt
for a Category cell you can check the expanded property and then either insert or delete the sub-category cells as required.
The outline of the solution will look something like the below (all typed from memory, so may well have some syntax issues, but it should be enough to get you going)
public struct Category {
let name: String
let image: UIImage
let id: Int
let subCategories: [SubCategory]
var expanded = false
var onCategorySelected: (Int) -> Void
}
class CategoryCell: UITableViewCell {
static let cellID = "CategoryCell"
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
//set up imageViews (main & up/down) and textLabel
}
func configure(with category: Category {
//populate cell fields from category
}
}
class CategoryCell: UITableViewCell {
static let cellID = "SubcategoryCell"
// same things as above
}
Then in the view controller
enum RowType {
case category (Category)
case subcategory (SubCategory)
func toggled() -> RowType {
switch self {
case .subcategory: return self
case .category (let category):
category.expanded.toggle()
return .category(category)
}
}
}
//create an array of rows currently being displayed. Start with just Categories
var visibleRows: [RowType] = ArrayOfCategories.map{RowType.Category($0)}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CategoryCell.self, forCellReuseIdentifier: CategoryCell.cellID )
tableView.register(SubCategoryCell.self, forCellReuseIdentifier: SubCategoryCell.cellID)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return visibleRows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch visibleRows[indexPath.row]
case .Category (let category):
let cell = tableView.dequeueReusableCell(withIdentifier: CategoryCell.CellID, for: indexPath) as! CategoryCell
cell.configure(with: category)
return cell
case .subCategory (let subCategory):
let cell = tableView.dequeueReusableCell(withIdentifier: SubCategoryCell.CellID, for: indexPath) as! SubCategoryCell
cell.configure(with: subCategory)
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch visibleRows[indexPath.row] {
case .category(let category):
if category.expanded {
tableView.deleteRows(... //delete the subCategory rows
visibleRows.remove( ... //delete the same rows from visibleRows
} else {
visibleRows.insert( ... //insert the .subcategory rows corresponding to the category struct's [subCategory] array
tableView.insertRows(... //insert the appropriate subCategory rows
}
visibleRows[indexPath.row] = visibleRows[indexPath.row].expanded.toggled()
tableView.reloadRows(at:[indexPath.row], with: .fade)
case .subCategory (let subCategory):
//do anything you want for a click on a subCat row
}
}
Upvotes: 1