Ios 使用简单的TableView Swift构建单元格时的奇怪行为

Ios 使用简单的TableView Swift构建单元格时的奇怪行为,ios,swift,uitableview,Ios,Swift,Uitableview,我花了一段疯狂的时间来修复我们应用程序中的这个错误。我们目前正在聊天,我们使用了tableview 在出现一定数量的消息之前,我们的tableview工作正常,之后,表开始翻转。我编码时没有故事板,因此我认为约束是问题的原因。因此,我决定用聊天表视图的一些功能制作一个非常简单的表视图(实际上,我们的表视图是coredata链接的,并且有很多图形) 因为我怀疑约束,所以我在下面的代码中没有使用它,只是为了看到一切正常,但事实并非如此。在gif图像中,我们可以看到两种不受欢迎的行为,第一种是有时表格

我花了一段疯狂的时间来修复我们应用程序中的这个错误。我们目前正在聊天,我们使用了tableview

在出现一定数量的消息之前,我们的tableview工作正常,之后,表开始翻转。我编码时没有故事板,因此我认为约束是问题的原因。因此,我决定用聊天表视图的一些功能制作一个非常简单的表视图(实际上,我们的表视图是coredata链接的,并且有很多图形)

因为我怀疑约束,所以我在下面的代码中没有使用它,只是为了看到一切正常,但事实并非如此。在gif图像中,我们可以看到两种不受欢迎的行为,第一种是有时表格会完全重新生成,因此单元格会在很短的时间内消失并出现(这会导致非常恼人的翻转)。第二个同样令人恼火:单元是重复的(我认为这是为了单元的可重用性特性),但在很短的一段时间后,它们被容纳了,一切都很好

我尝试添加prepareForReuse()方法并删除视图,然后再次在单元格中创建它们,但没有结果

这是示例代码,您可以在游乐场中复制并运行它,不会出现任何问题

//: A UIKit based Playground for presenting user interface

import UIKit
import PlaygroundSupport


class Modelito{
    // This is the class tableview model
    var celda: String
    var valor: String
    init(celda: String, valor: String){
        self.celda = celda
        self.valor = valor
    }
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{

    let tableview: UITableView = {
        let table = UITableView()
        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()

    let button: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Click me to add new cell", for: .normal)
        button.setTitle("Click me to add new cell", for: .highlighted)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()


    var model: [Modelito] = []

    let tipoDeCelda = ["MyCustomCell", "MyCustomCell2"]

    override func viewDidLoad() {
        super.viewDidLoad()

        self.setupViews()

        self.tableview.register(MyCustomCell.self, forCellReuseIdentifier: "MyCustomCell")
        self.tableview.register(MyCustomCell2.self, forCellReuseIdentifier: "MyCustomCell2")

        self.tableview.dataSource = self
        self.tableview.delegate = self

        self.button.addTarget(self, action: #selector(self.addRow), for: .touchUpInside)

        // Here I generate semi random info
        self.dataGeneration()
    }

    func setupViews(){

        self.view.addSubview(self.tableview)
        self.tableview.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
        self.tableview.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -50).isActive = true
        self.tableview.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 1).isActive = true
        self.tableview.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true

        self.tableview.backgroundColor = .gray

        self.view.addSubview(self.button)
        self.button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
        self.button.topAnchor.constraint(equalTo: self.tableview.bottomAnchor).isActive = true
        self.button.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
        self.button.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
        self.button.backgroundColor = .orange

    }

    func dataGeneration(){
        let number = 200
        // Based in the cell types availables and the senteces, we create random cell info
        for _ in 0...number{

            self.model.append(
                Modelito(celda: tipoDeCelda[Int(arc4random_uniform(UInt32(self.tipoDeCelda.count)))], valor: "\(self.model.count)")
            )
        }

        self.tableview.reloadData()
        // After we insert elements in the model we scroll table
        let indexPaths: [IndexPath] = [IndexPath(row: self.model.count - 1, section: 0)]
        self.tableview.scrollToRow(at: indexPaths[0], at: .bottom, animated: false)
    }

    @objc func addRow(){
        // This function insert a new random element
        self.tableview.beginUpdates()
        self.model.append(
            Modelito(celda: tipoDeCelda[Int(arc4random_uniform(UInt32(self.tipoDeCelda.count)))], valor: "\(self.model.count)")
        )

        // After inserting the element in the model, we insert it in the tableview
        let indexPaths: [IndexPath] = [IndexPath(row: self.model.count - 1, section: 0)]
        self.tableview.insertRows(at: indexPaths, with: .none)
        self.tableview.endUpdates()
        // Finally we scroll to last row
        self.tableview.scrollToRow(at: indexPaths[0], at: .bottom, animated: false)

    }

}


extension ViewController{
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.model.count
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 150
    }

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

        let celldata = self.model[indexPath.row]

        switch celldata.celda {
        case "MyCustomCell":
            let cell = tableview.dequeueReusableCell(withIdentifier: "MyCustomCell", for: indexPath) as! MyCustomCell
            cell.myLabel.text = self.model[indexPath.row].valor
            return cell
        default:
            let cell = tableview.dequeueReusableCell(withIdentifier: "MyCustomCell2", for: indexPath) as! MyCustomCell2
            cell.myLabel.text = self.model[indexPath.row].valor
            return cell
        }

    }
}


class MyCustomCell: UITableViewCell {

    var myLabel = UILabel()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        myLabel.backgroundColor = UIColor.green
        self.contentView.addSubview(myLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        myLabel.frame = CGRect(x: 25, y: 0, width: 370, height: 30)
    }
}

class MyCustomCell2: UITableViewCell {

    var myLabel = UILabel()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        myLabel.backgroundColor = UIColor.yellow
        self.contentView.addSubview(myLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        myLabel.frame = CGRect(x: 0, y: 0, width: 370, height: 30)
    }

}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ViewController()
提前谢谢

编辑:


我更改了@Scriptable answer中的代码库,以便与playway兼容。在这一点上,我开始认为复制单元错误实际上对于tableview是正常的。要查看问题,应快速按下按钮数次。

此代码正在重新加载tableview,但它正在删除“恼人的轻弹”,而且太平滑

要修复这个恼人的flick,只需将addRow()函数更改为

   @objc func addRow(){

    self.model.append(
        Modelito(celda: tipoDeCelda[Int(arc4random_uniform(UInt32(self.tipoDeCelda.count)))], valor: "\(self.model.count)")
    )
    self.tableview.reloadData()
    self.scrollToBottom()
}
scrollToBottom()函数:

func scrollToBottom(){
    DispatchQueue.global(qos: .background).async {
        let indexPath = IndexPath(row: self.model.count-1, section: 0)
        self.tableview.scrollToRow(at: indexPath, at: .bottom, animated: true)
    }
}

试试看。

在操场上使用当前代码会给我带来致命错误

2017-11-13 15:10:46.739我的游乐场[4005:234029]***由于未捕获的异常“NSRangeException”而终止应用程序,原因:'-[UITableView _contentOffsetForScrollingToRowatineXPath:atScrollPosition::]:第(200)行超出第(0)节的边界(0)。'

我将
dataGeneration
函数更改为以下代码,在生成数据并滚动到底部后,它只调用
reloadData

我认为错误在于没有重新加载,没有那么多行可以滚动

func dataGeneration(){
        let number = 200
        // Based in the cell types available and the sentences, we create random cell info
        for _ in 0...number{

            self.model.append(
                Modelito(celda: tipoDeCelda[Int(arc4random_uniform(UInt32(self.tipoDeCelda.count)))], valor: "\(self.model.count)")
            )
        }

        self.tableview.reloadData()
        // After we insert elements in the model we scroll table
        let indexPaths: [IndexPath] = [IndexPath(row: self.model.count - 1, section: 0)]
        self.tableview.scrollToRow(at: indexPaths[0], at: .bottom, animated: false)
    }

一旦我躲过了撞车事故,桌面视图对我来说很好。没有闪烁或重复。

谢谢@scriptable,我在问题中更改了上面的代码,但仍然存在相同的问题。事实上,当你在很短的时间内点击按钮时,问题就出现了(想象你正在聊天,而聊天室的参与者写消息的速度非常快)。我很快地键入了按钮,一分钟内添加了大约30行,没有任何问题。你试过在物理设备上运行吗?模拟器的运行速度比实际设备慢很多