iKargo
iKargo

Reputation: 1

UITableView loads only custom cells that are visible on device and crashes when one more cell is added

I use an empty UITableView with custom cells and I add new items one by one without any problem. The tableView is scrollable, however when I add an item to the cell that is one index more from the last visible cell the app crashes.

When the app is loaded the numberOfRowsinSection is 1 and with every new entry it grows by 1. If the device has 10 visible cells it crashes on 11. If the device has 6 visible cells it crashes on 7. The app unexpectedly finds nil while unwrapping an Optional value.

Using advices from the question titled UITableview Not scrolling? I tried each of the following lines in viewDidLoad and in my function:

   self.myTableView.delegate = self

  self.myTableView.autoresizingMask = UIView.AutoresizingMask.flexibleHeight;

  self.myTableView.rowHeight = UITableView.automaticDimension

   self.myTableView.bounces = true;

  self.myTableView.reloadData()

without any positive result.

Here is the code:

var enterCounts: Int = 1 

public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return enterCounts
    }

  public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TextInputCell") as! TextInputTableViewCell

        return cell
    }        


  @IBAction func enter(_ sender: Any) {

    let activeRow = self.enterCounts - 1
    let index = IndexPath(row: activeRow, section: 0)
    let cell: TextInputTableViewCell  = self.myTableView.cellForRow(at: index) as! TextInputTableViewCell


    if cell.myTextField.text == ""  {
         "DO NOTHING"
    } else {

        "DO STUFF"

        enterCounts += 1

        self.myTableView.reloadData()

        let nextIndex = IndexPath(row: activeRow + 1, section: 0)


 "This is the line that finds nil and crashes when row is out of view"

        let nextCell: TextInputTableViewCell = self.myTableView.cellForRow(at: nextIndex) as! TextInputTableViewCell
        nextCell.myTextField.text = ""
        nextCell.myTextField.becomeFirstResponder()
    }

}

I would expect the UITableView to scroll and keep on loading as many cells the user enters, exactly as it does with the first/visible cells.Thank you.

After the 2 answers the code is:

    @IBAction func enter(_ sender: Any) {

    let activeRow = self.enterCounts - 1
    let index = IndexPath(row: activeRow, section: 0)
    let cell: TextInputTableViewCell  = self.myTableView.cellForRow(at: index) as! TextInputTableViewCell


    if cell.myTextField.text == ""  {
         "DO NOTHING"
    } else {

        "DO STUFF"

      enterCounts += 1
      let nextIndex = IndexPath(row: activeRow + 1, section: 0) 
      self.myTableView.insertRows(at: [nextIndex], with: .automatic)
      self.myTableView.scrollToRow(at: nextIndex,at: .middle, animated: true)

     //These lines are executed only when I am in visible cells 
     //when a new cell is added it is not ready to become first responder it is skipped and entered text is getting mixed up.
 if let nextCell: TextInputTableViewCell = self.myTableView.cellForRow(at: nextIndex) as? TextInputTableViewCell {
            nextCell.myTextField.text = ""
            nextCell.myTextField.becomeFirstResponder()
           }
    }

}

With the code above the new cells appear wonderfully but textField become first responder only once, for the first cell that appears in view.

I declare my custom cell class in as below

          @IBOutlet weak var myTextField: UITextField!

 public func configure(text: String?, placeholder: String) {


    myTextField.text = text
  //  myTextField.placeholder = placeholder

    myTextField.accessibilityValue = text
   // myTextField.accessibilityLabel = placeholder

}


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

override public func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
}

If I use a textField outside the tableView and keep the tableView only for displaying my entered values things are simple but having for entryField the last cell of the tableView creates problems when I try to make first responder the textField of the new inserted cell.

Upvotes: 0

Views: 395

Answers (3)

Mohammad Yunus
Mohammad Yunus

Reputation: 214

if you want to add new line in you tableview whenever a enter button is tapped this I think you should try doing it this way

I am assuming that u just want a textfield in your tableview but this could work with other thing also in you custom class make an outlet for your textfield name it whatever you want I am naimg it tf and do this

iboutlet weak var tf: uitextfield!{
didSet{
tf.delegate = self
}}

and create a closure var like this

var textsaved: ((String) -> Void)?

then add textfield delegate to your customcell class like this

extension CustomCell: uitextfielddelegate{ }

then in your extension write :

func textfieldshouldreturn(_ textfield: uitextfield) -> Bool{
tf.resignFirstResponder() 
retrun true }

func textfielddidendediting(_ textfield: uitextfield){
textsaved?(textfield.text!) }

then in your view controller create an empty array of string

var myarr = [String]()

make outlet for enter button and tableview

@iboutlet weak var mytableview: uitableView!{
didSet{
mytableview.delegate = self
mytableview.datasource = self }

@iboutlet weak var enterBtn(_ sender: uibutton) {
myarr.append("")
mytableview.reloaddata() }

in number of rows return myarr.count

in cell for row at

let cell = tableview.dequereuseablecell(withidentifier: "cell", for :indexpath) as! customcell
cell.tf.text = myarr[indexpath.row]
cell.textsaved = { [unowned self] (text) in 
self.myarr.remove(at: indexpath.row)
self.myarr.insert(text, at: indexpath.row)
sel.mytableview.reloaddata()
} return cell }

Upvotes: 0

Abu Ul Hassan
Abu Ul Hassan

Reputation: 1396

It is crashing because apple only keeps cells in memory that are visible, In your case you are access cell that is not in memory and instead to use optional you are forcing to unwrap which causes the crash. After knowing this you should handle exception for cells that are not visible, like bewlow

@IBAction func enter(_ sender: Any) {

let activeRow = self.enterCounts - 1
let index = IndexPath(row: activeRow, section: 0)
let cell: TextInputTableViewCell  = self.myTableView.cellForRow(at: index) as! TextInputTableViewCell


if cell.myTextField.text == ""  {
     "DO NOTHING"
} else {

    "DO STUFF"

    enterCounts += 1

    self.myTableView.reloadData()

    let nextIndex = IndexPath(row: activeRow + 1, section: 0)


 //"This is the line that finds nil and crashes when row is out of view"

   if let nextCell: TextInputTableViewCell = self.myTableView.cellForRow(at: nextIndex) as? TextInputTableViewCell
{
    nextCell.myTextField.text = ""
    nextCell.myTextField.becomeFirstResponder()
}

}

Or

if you want to make your textfield first responder first get cell in memory by scrolling to index Or by inserting it and then access it.

Upvotes: 1

Magdy Zamel
Magdy Zamel

Reputation: 476

if you need to add new cell you can use this line to add it:

let indexPath = IndexPath(row: ... , section: ...)
tableView.insertRows(at: [indexPath], with: .automatic)

after that scroll to it

   tableView.scrollToRow(at: indexPath,at: .middle, animated: true) 

finally, you can use this cell

let cell = tableView.cellForRow(at: nextIndex) as! YourCustomCellClass

Upvotes: 2

Related Questions