(Swift) How to (animate) show/hide cells based on

2019-09-15 01:57发布

I'm a beginning iOS developer and I've been stuck on an issue for quite some time now.

Background: I have a single viewcontroller with a tableview. It holds 4 dynamic prototype cells: prototypecell 1 has an UITextField and a couple of labels, prototypecell 2 has an image, prototypecell 3 and 4 only have a single label. (Note: prototype cell 4 is based on an array in the object and can have 1 or more cells)

Upon opening the screen, only the first cell with the UITextField should be visible. When an user enters a number (max. 3 digits) in this UITextField, the number has to be compared to check if it is an existing number in an array of objects. If this number proves correct, 2 things will have to happen:

  1. The labels in the first cell will need to change layout (colour, font, size, ...). And the first cell changes rowHeight.
  2. The other 3 cells will have to appear and show data that corresponds to the number.

If the number is wrong, no extra cells appear and the first cell updates to tell the user it was incorrect.

Problem: I'm having issues with animating the way the 3 cells appear and disappear based on the numberinput in the first cell. More specifically the method "toggleCellsVisibility" and how it relates to the creation of the cells.

I tried multiple combinations of: beginUpdates(), endUpdates(), reloadData(), reloadRowsAtIndexPath(), DispatchQueue.main.async {} and UIView.Animate(). But the only way my code works is if I implement the toggleCellsVisibility method as below, which does not give me any animation options.

I seem to be doing something wrong with the creation and reloading of the data. I think this is related to the use of the global variables indexOfObject and tempObject which hold dummy data when the screen is loaded, and are then shown and overridden with the actual data when a correct number is given.

Summary: Could I somehow insert/create the 3 cells when a correct number is inputted in the first cell, while also animating this change with this particular setup?

Additional: Can I show/hide the extra cells by not using the heightForRowAtIndexPath? It would be better if the cells could self-size.

I selected and simplified the relevant pieces of code:

1) TableView Class:

class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, updateUITableView {

var objects: [Object] = [
    name: "test1", number: "test2", image: "imageName1", color: UIColor.black, array: ["test3", "test4"],
    [name: "test1", number: "test2", image: "imageName2", color: UIColor.white, array: ["test3", "test4", "test5"]
    //...
]

var cellsRowHeight = false

var indexOfObject: Int = 0
var tempObject = Object[name: "test1", number: "test2", color: UIColor.blue, image: "imageName3", array: ["test3"]]

@IBOutlet var tableView: UITableView!

// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let numberOfRows = 3 + tempObject.subArray.count
    return numberOfRows
}

//START - Input received from textfield in first cell
func checkNumberInput(senderCell: SearchInputTableViewCell, number: String) {
    // step 1: Check if number exists
    let match = checkNumberMatch(numberInput: number)

    // step 2: Change lay-out of input cell
    senderCell.changeLayout(numberMatch: match.0, name: match.1?.name, color: match.1?.color)

    // step 3: Show/hide cells
    toggleCellsVisibility(numberMatch: match.0)
}

//STEP 1: Check if the number corresponds to an existing number
func checkNumberMatch(numberInput: String) -> (Bool, Object?) {
    var returnObject: Object?
    var tuple = (false, returnObject)

    for (index, object) in objects.enumerated() where object.number == numberInput {
        returnObject = object

        tuple = (true, returnObject)

        tempObject = object
        indexOfObject = index
    }
    return tuple
}

//STEP 3 = !!PROBLEM!!
func toggleCellsVisibility(numberMatch: Bool) {
    cellsRowHeight = numberMatch
    // if/else because of additional future implementation
    if numberMatch == true { //Cells appear
        DispatchQueue.main.async {
            self.tableView?.reloadData()
        }

    } else { //Cells disappear
        DispatchQueue.main.async {
            self.tableView?.reloadData()
        }
    }
}

//STEP 4:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    switch (indexPath.row) {
    case 0 where !cellsRowHeight:
        return 187
    case 0 where cellsRowHeight:
        return 170
    case 1 where !cellsRowHeight:
        return 0
    case 1 where cellsRowHeight:
        return 170
    case 2 where !cellsRowHeight:
        return 0
    case 2 where cellsRowHeight:
        return 54
    case 3...200 where !cellsRowHeight:
        return 0
    case 3...200 where cellsRowHeight:
        return 44
    default:
        return 44
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if indexPath.row == 0 {
        let cellIdentifier = "InputCell"
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputCell

        cell.delegate = self

        return cell
    }
    else if indexPath.row == 1 {
        let cellIdentifier = "Cell2"
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell2

        cell.image?.image = UIImage(named: objects[indexOfObject].image)

        return cell
    }
    else if indexPath.row == 2 {
        let cellIdentifier = "Cell3"
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell3

        cell.name?.text = objects[indexOfObject].name

        return cell
    }
    else {
        let cellIdentifier = "Cell4"
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell4

        cell.name?.text = objects[indexOfObject].subArray[(indexPath as IndexPath).row - 3]

        return cell
    }
}}

2) Cell Class:

protocol updateUITableView: class {
    func checkNumberInput(senderCell: SearchInputTableViewCell, number: String)
}

class InputCell: UITableViewCell, UITextFieldDelegate {

    var delegate: updateUITableView?

    @IBOutlet var textField: UITextField!
    @IBOutlet var nameLabel: UILabel!
    //... and other labels

    func changeLayout(numberMatch: Bool, name: String?, color: UIColor?) {
        if numberMatch == true {
            //lay-out changes to labels
        }
        else {
            //lay-out changes to labels
        }
    }

    //Set maximum character limit in textField and dismiss keyboard when character limit (3) is reached.
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let currentCharacterCount = textField.text?.characters.count ?? 0
        let newLength = currentCharacterCount + string.characters.count - range.length

        if (newLength == 3) {
            textField.text = (textField.text! as NSString).replacingCharacters(in: range, with: string)

            //Get text from textField
            let numberInput: String? = textField.text! //Get number if 3 characters entered
            if numberInput != nil {
                delegate?.checkNumberInput(senderCell: self, number: numberInput!)
            }
            textField.resignFirstResponder()

            if (range.length + range.location > currentCharacterCount) {
                return false
            } else {
                return true
            }
        }
        return true
    }
}

class Cell2: UITableViewCell {
    @IBOutlet var image: UIImageView!
}

class Cell3: UITableViewCell {
    @IBOutlet var name: UILabel!
}

class Cell4: UITableViewCell {
    @IBOutlet var name: UILabel!
}

Any help would be immensely appreciated! Thank you!

1条回答
Lonely孤独者°
2楼-- · 2019-09-15 02:35

When your action is done (checking that the input match whatever you want), populate the data source you're using for your tableview (you should declare an empty array of objects you want to use as a datasource, and use this), then use reloadData() on your tableview.

For the height of your cells, you can use tableView.rowHeight = UITableViewAutomaticDimension (https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html)

查看更多
登录 后发表回答