Background: A single ViewController contains 4 custom prototype cells. The first one is always visible and has a UITextField
.
Prototype cells 2, 3 and 4 contain an image, label and label respectively. However, prototype cell 4 is based on an array and can have 1 or multiple rows depending on the input in the first cell.
When a certain String is entered in the first cell, a check is made to see if an Object exists with that String in one of its properties. If the value is incorrect, the first cell changes height and lay-out. If the value is correct, the first cell changes height, and most importantly the 3 other prototype cells expand to show the details of the object that corresponds to the String input. If the user then enters another String that is incorrect, the cells collapse.
Problem: Animating this expand/collapse of the 3 other cells. I'm having trouble figuring out how to define the numberOfRowsInSection() method and the code block (step 2 in my code below) between beginUpdates() and endUpdates(). Even without the arraycells implementation, calling the reloadRows() after insertRows() does not seem to work.
What I tried:
- reloadData() = correctly shows the data, but I cannot use it because it won't give the necessary animation.
- beginUpdates() and endUpdates() without anything in between = gives the following error:
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (6) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
I believe this is related to the fact that the last prototype cell is based on an array and I'm not reloading the actual data of the cells, just the view.
- other combinations with insertRowsAtIndexPath, reloadRowsAtIndexPath, ... = give similar errors related to the number of rows.
Any help would be immensely appreciated!
Simplified Code:
class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, updateUITableView {
var people: [Person] = [
Person(name: "name", image: "imagename1", description: "description1", children: ["name1","name2","name3"]),
Person(name: "name2", image: "imagename3", description: "description2", children: ["name4", "name5", "name6"])
]
enum Flag {
case start
case match
case noMatch
}
var flag = Flag.start
var globalObject: Person?
@IBOutlet var tableView: UITableView!
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch (flag) {
case .start :
return 1
case .noMatch:
return 1
case .match:
return 3 + (globalObject?.children.count)! //PROBLEM!
}
}
//START - Input received from tableViewCell
func checkName(senderCell: SearchInputTableViewCell, name: String) {
// STEP 1: Check if person exists
let person = checkPersonNameMatch(nameInput: name)
globalObject = person //save globally for cellForRowsAtIndexPath
// STEP 2: Show/hide cells
toggleCellsVisibility(person: person)
}
//STEP 1:
func checkPersonNameMatch(nameInput: String) -> Person? {
guard let index = people.index(where: {$0.name == nameInput}) else {
flag = .noMatch
return nil
}
let personFound = people[index]
flag = .match
globalObject = personFound
return personFound
}
//STEP 2 = PROBLEM!
func toggleCellsVisibility(person: Person?) {
if person != nil { //Cells appear
UIView.animate(withDuration: 0.7, animations: {
self.tableView.beginUpdates()
let indexPath: IndexPath = IndexPath(row: 1, section: 0) //for Image cell
let indexPath2: IndexPath = IndexPath(row: 2, section: 0) //for Description cell
//let indexPath3: IndexPath = IndexPath[....] //for array in Children cell
self.tableView.insertRows(at: [indexPath], with: .bottom)
self.tableView.insertRows(at: [indexPath2], with: .bottom)
self.tableView.reloadRows(at: [indexPath], with: .bottom)
self.tableView.reloadRows(at: [indexPath2], with: .bottom)
//... a for-loop to reload the array cells here?
self.tableView.endUpdates()
})
} else { //Cells disappear
UIView.animate(withDuration: 0.4, animations: {
self.tableView.beginUpdates()
//... same code as above?
self.tableView.endUpdates()
})
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cellIdentifier = "InputCell"
let inputCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTableViewCell
inputCell.delegate = self
switch (flag) {
case .start:
self.tableView.rowHeight = 187
case .match:
self.tableView.rowHeight = 170
case .noMatch:
self.tableView.rowHeight = 200
}
return inputCell
case 1:
let cellIdentifier = "ImageCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ImageTableViewCell
cell.image?.image = UIImage(named: (globalObject?.image)!)
switch (flag) {
case .start:
self.tableView.rowHeight = 0
case .match:
self.tableView.rowHeight = 170
case .noMatch:
self.tableView.rowHeight = 0
}
return cell
case 2:
let cellIdentifier = "DescriptionCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! DescriptionTableViewCell
cell.description?.text = globalObject?.description
switch (flag) {
case .start:
self.tableView.rowHeight = 0
case .match:
self.tableView.rowHeight = 54
case .noMatch:
self.tableView.rowHeight = 0
}
return cell
default:
let cellIdentifier = "ChildrenCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ChildrenTableViewCell
cell.childName?.text = globalObject?.children[(indexPath as IndexPath).row - 3]
switch (flag) {
case .start:
self.tableView.rowHeight = 0
case .match:
self.tableView.rowHeight = 44
case .noMatch:
self.tableView.rowHeight = 0
}
return cell
}
}
}