UITableView 仅加载在设备上可见的自定义单元格,并在添加更多单元格时崩溃

Posted

技术标签:

【中文标题】UITableView 仅加载在设备上可见的自定义单元格,并在添加更多单元格时崩溃【英文标题】:UITableView loads only custom cells that are visible on device and crashes when one more cell is added 【发布时间】:2019-04-18 12:51:09 【问题描述】:

我使用带有自定义单元格的空UITableView,并一一添加新项目,没有任何问题。 tableView 是可滚动的,但是当我将一个项目添加到比最后一个可见单元格多一个索引的单元格时,应用程序崩溃了。

当应用程序加载时,numberOfRowsinSection 为 1,并且随着每个新条目增长 1。如果设备有 10 个可见单元格,它会在 11 崩溃。如果设备有 6 个可见单元格,它会在 7 崩溃。应用在展开 Optional 值时意外发现 nil。

使用标题为 UITableview Not scrolling? 的问题中的建议 我在viewDidLoad 和我的函数中尝试了以下每一行:

   self.myTableView.delegate = self

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

  self.myTableView.rowHeight = UITableView.automaticDimension

   self.myTableView.bounces = true;

  self.myTableView.reloadData()

没有任何阳性结果。

代码如下:

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()
    


我希望UITableView 滚动并继续加载用户输入的尽可能多的单元格,就像它处理第一个/可见单元格一样。谢谢。

2个答案后代码为:

    @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()
           
    


使用上面的代码,新单元格看起来很漂亮,但 textField 只成为第一响应者一次,对于出现在视图中的第一个单元格。

我在下面声明我的自定义单元格类

          @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

如果我在 tableView 之外使用 textField 并保留 tableView 仅用于显示我输入的值,事情很简单,但是当我尝试让第一响应者成为新插入的单元格的 textField 时,对于 entryField,tableView 的最后一个单元格会产生问题.

【问题讨论】:

您能否编辑您的问题以使其更清楚,我想提供帮助,但我不明白。 【参考方案1】:

如果您需要添加新单元格,可以使用此行添加:

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

然后滚动到它

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

终于可以使用这个单元格了

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

【讨论】:

这段代码和 Hassan 的答案一起解决了这个问题,现在它更清楚了 UITableView 在添加单元格时是如何工作的。 我已经尝试了所有方法,但仍然无法使新单元格的 textField 成为第一响应者,除了第一个响应者。【参考方案2】:

它崩溃了,因为苹果只将单元格保留在内存中可见的单元格,在您的情况下,您访问的是不在内存中的单元格,而是使用可选的,您强制解包导致崩溃。 知道这一点后,您应该处理不可见的单元格的异常,例如下面

@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()



或者

如果您想让您的文本字段第一响应者首先通过滚动到索引或插入它然后访问它来获取内存中的单元格。

【讨论】:

应用“if let”和 Magdy 的答案一起解决了问题。 但是,正如您“预测”的那样,尽管我 insertRow 和 scrollToRow,但新的 textField 不能成为第一响应者。知道为什么会这样吗? 可能是导致问题的原因在显示的单元格上尝试了相同的代码? 也从输入函数中删除 self.myTableView.reloadData() 这一行为什么我们要重新加载? 或者它也可能不会成为第一响应者,因为您的单元格不在内存中。因此,如果让防止崩溃,请在这种情况下进行调试并检查。【参考方案3】:

如果您想在点击输入按钮时在表格视图中添加新行,我认为您应该尝试这样做

我假设你只想在你的 tableview 中有一个文本字段,但这也可以与其他东西一起使用 在您的自定义类中为您的文本字段创建一个出口,无论您想要什么,我都可以将它命名为 tf 并执行此操作

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

并像这样创建一个闭包变量

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

然后像这样将文本字段委托添加到您的 customcell 类中

extension CustomCell: uitextfielddelegate 

然后在你的扩展中写:

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

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

然后在你的视图控制器中创建一个空的字符串数组

var myarr = [String]()

为输入按钮和表格视图制作插座

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

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

行数 返回 myarr.count

在单元格中的行

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 

【讨论】:

感谢 Mohammad 的详细回答。我试图应用它,但在 完成您的问题,以便我进行调查 我尝试应用它,但我无法应用 enterButton 插座格式(IBOUTlet 属性要求属性是可变的)??我会尝试修改问题。看来我的问题是在表格视图中我有条目字段(最后一个单元格)以及结果(容纳我存储数组的单元格)。 另一种方法是在数据中添加一个空字符串,这将增加计数并重新加载数据

以上是关于UITableView 仅加载在设备上可见的自定义单元格,并在添加更多单元格时崩溃的主要内容,如果未能解决你的问题,请参考以下文章

仅加载 UITableView 中的可见单元格

UITableView 中的自定义单元格重叠

在单个 UIScrollview 中使用多个 UITableViews,如何仅加载可见单元格的单元格数据

更改单元格中的数据后,UITableView 中的自定义单元格将无法正确重新加载

UITableView 中的自定义单元格使图像在滚动时重叠

使用大型数据库时,如何避免 UITableView 中的自定义单元格加载时间过长