0

I'm building a custom interface for the user to enter preference settings in my app. I'm using expandable rows following an example I found at AppCoda. I've reworked that example to use Swift 3/4 and to use cell information from code rather than read from a plist.

I'm having a problem with the way some cell content appears on the screen. The rows that expand and collapse contain textfields to allow user entry. There are four such rows in the example code below.

When an entry is made in one of those cells, it may or may not cause the last-entered value to appear in all four cells when they are expanded. The 'extra' text will even overwrite the information that belongs there.

I've tried everything I can think of to get rid of this offending text but I'm banging my head against the wall. What am I missing?

FWIW, I am now looking at similar solutions elsewhere. Here's one I like quite a bit:

https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section

This one looks interesting but is not in Swift:

https://github.com/singhson/Expandable-Collapsable-TableView

Same comment:

https://github.com/OliverLetterer/SLExpandableTableView

This looks very interesting - well supported - but I haven't had time to investigate:

https://github.com/Augustyniak/RATreeView

A similar request here:

Expand cell when tapped in Swift

A similar problem described here, but I think I'm already doing what is suggested?

http://www.thomashanning.com/the-most-common-mistake-in-using-uitableview/

Here is my table view controller code. I believe the problem is in the...

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):

... function, but for the life of me I can't see it.

    override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    test = defineCellProps()  // This loads my hard-coded cell properties into array "test"
    configureTableView()
}

func configureTableView() {
    loadCellDescriptors()
    tblExpandable.delegate = self
    tblExpandable.dataSource = self
    tblExpandable.tableFooterView = UIView(frame: CGRect.zero)
    tblExpandable.register(UINib(nibName: "NormalCell", bundle: nil), forCellReuseIdentifier: "idCellNormal")
    tblExpandable.register(UINib(nibName: "TextfieldCell", bundle: nil), forCellReuseIdentifier: "idCellTextfield")  // There are additional cell types that are not shown and not related to the problem
}

func loadCellDescriptors() {  // Puts the data from the "test" array into the format used in the original example
    for section in 0..<ACsections.count {
        var  sectionProps = findDict(matchSection: ACsections[section], dictArray: test)
        cellDescriptors.append(sectionProps)
    }
    cellDescriptors.remove(at: 0)  // Removes the empty row
    getIndicesOfVisibleRows()        
    tblExpandable.reloadData()  // The table
}

func getIndicesOfVisibleRows() {
    visibleRowsPerSection.removeAll()
     for currentSectionCells in cellDescriptors {  // cellDescriptors is an array of sections, each containing an array of cell dictionaries
        var visibleRows = [Int]()
        let rowCount = (currentSectionCells as AnyObject).count as! Int
        for row in 0..<rowCount {  // Each row is a table section, and array of cell dictionaries
            var testDict = currentSectionCells[row]
            if testDict["isVisible"] as! Bool == true {
                visibleRows.append(row)
            }  // Close the IF
        }  //  Close row loop
        visibleRowsPerSection.append(visibleRows)
    }  //  Close section loop
}  //  end the func


func getCellDescriptorForIndexPath(_ indexPath: IndexPath) -> [String: AnyObject] {
   let indexOfVisibleRow = visibleRowsPerSection[indexPath.section][indexPath.row]
    let cellDescriptor = (cellDescriptors[indexPath.section])[indexOfVisibleRow]
    return cellDescriptor as [String : AnyObject]
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
    let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
    cell.textLabel?.text = nil
    cell.detailTextLabel?.text = nil
    cell.textField?.placeholder = nil
    if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
        if let primaryTitle = currentCellDescriptor["primaryTitle"] {
            cell.textLabel?.text = primaryTitle as? String
        }

        if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
            cell.detailTextLabel?.text = secondaryTitle as? String
        }
    }
    else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
        if let primaryTitle = currentCellDescriptor["primaryTitle"] {
            if primaryTitle as! String == "" {
                cell.textField.placeholder = currentCellDescriptor["secondaryTitle"] as? String
                cell.textLabel?.text = nil

            } else {
                cell.textField.placeholder = nil
                cell.textLabel?.text = primaryTitle as? String
            }
        }

        if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
            cell.detailTextLabel?.text = "some text"
        }
        cell.detailTextLabel?.text = "some text"
//  This next line, when enabled, always puts the correct row number into each cell.
//            cell.textLabel?.text = "cell number \(indexPath.row)."
    }

    cell.delegate = self

    return cell
}

Here is the CustomCell code with almost no changes by me:

import UIKit

protocol CustomCellDelegate {
func textfieldTextWasChanged(_ newText: String, parentCell: CustomCell)
}

class CustomCell: UITableViewCell, UITextFieldDelegate {

@IBOutlet weak var textField: UITextField!

let bigFont = UIFont(name: "Avenir-Book", size: 17.0)
let smallFont = UIFont(name: "Avenir-Light", size: 17.0)
let primaryColor = UIColor.black
let secondaryColor = UIColor.lightGray

var delegate: CustomCellDelegate!

override func awakeFromNib() {
    super.awakeFromNib()          // Initialization code

    if textLabel != nil {
        textLabel?.font = bigFont
        textLabel?.textColor = primaryColor
    }

    if detailTextLabel != nil {
        detailTextLabel?.font = smallFont
        detailTextLabel?.textColor = secondaryColor
    }

    if textField != nil {
        textField.font = bigFont
        textField.delegate = self
    }        
}

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    // Configure the view for the selected state
}

override func prepareForReuse() {  // I added this and it did not help
    super.prepareForReuse()
    textLabel?.text = nil
    detailTextLabel?.text = nil
    textField?.placeholder = nil
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    if delegate != nil {
        delegate.textfieldTextWasChanged(textField.text!, parentCell: self)
    }
    return true
  }
}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Wayne Henderson
  • 237
  • 3
  • 9

1 Answers1

0

OMG, I'm slapping my palm to my forehead. There is one very important line missing from this code from above:

override func prepareForReuse() {
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}

Can you see what's missing?

textField?.text = nil

That's all it took! I was mucking about with the label but not the textfield text itself.

Wayne Henderson
  • 237
  • 3
  • 9