简体   繁体   English

使用简单TableView Swift进行单元格构造时的奇怪行为

[英]Strange behavior with cell construction with Simple TableView Swift

I have spent an insane time to fix this bug in our application. 我花了疯狂的时间来修复我们应用程序中的这个错误。 We are currently working in a chat and we have used a tableview for it. 我们目前正在聊天,并且已经使用了表格视图。

Our tableview works fine until there is a certain amount of messages, after that, the table starts to flick. 我们的tableview可以正常工作,直到有一定数量的消息为止,此后,表开始滑动。 I coded without storyboards and for this reason I thought that constraints were the cause of trouble. 我编码时没有情节提要,因此我认为约束是麻烦的原因。 So I decide to make a really simple tableview with some features of our chat tableview (actually our tableview is coredata linked and with a lot of graphic stuffs). 因此,我决定使用聊天表视图的某些功能制作一个非常简单的表视图(实际上,我们的表视图是与核心数据链接的,并且包含许多图形内容)。

Because I suspected about constraints, I didn't use it in the code bellow just to see everything works fine, but it wasn't the case. 因为我怀疑约束,所以我没有在下面的代码中使用它,只是为了看到一切正常,但事实并非如此。 In the gif image we can see two undesirable behaviors, the first one is that sometimes the table is re-generated completely, so cells disappear and appear in a very short time (this cause a very annoying flick). 在gif图像中,我们可以看到两个不良行为,第一个是有时表会完全重新生成,因此单元格会在很短的时间内消失并出现(这会引起非常烦人的轻弹)。 The second is no less annoying: cells are duplicated (I think this is for the cell reusability feature) but after a short period of time they are accommodated and everything goes fine. 第二点同样令人讨厌:细胞被复制(我认为这是为了细胞的可重用性功能),但是在很短的时间之后,它们就可以容纳了,一切都很好。

https://github.com/hugounavez/pizzaWatch/blob/master/videoBug.gif https://github.com/hugounavez/pizzaWatch/blob/master/videoBug.gif

I tried adding the prepareForReuse() method and delete the views and creates them in the cell again but no results. 我尝试添加prepareForReuse()方法并删除视图,并再次在单元格中创建它们,但没有结果。

This is the example code, you can copy and run it with no problems in a Playground : 这是示例代码,您可以在Playground中复制并运行它,而不会出现问题:

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

Thanks in advance. 提前致谢。

Edit: 编辑:

I changed the code base in the @Scriptable answer in order to be compatible with playground. 为了与游乐场兼容,我更改了@Scriptable答案中的代码库。 At this point, I am starting to think that duplication cell bug is actually normal for tableview. 在这一点上,我开始认为复制单元错误对于tableview实际上是正常的。 In order to see the problem, the button should be pressed several times fastly. 为了看到问题,应该快速按下按钮几次。

This code is reloading the tableview but it's removing that "annoying flick" and it's too smooth. 这段代码正在重新加载tableview,但是它消除了“烦人的轻拂”,并且它太平滑了。

To fix that annoying flick just change addRow() function to 要解决这一烦人的轻弹,只需将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() function: 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)
    }
}

Just try it. 去尝试一下。

Using your current code in a playground produced a fatal error for me. 在操场上使用您当前的代码对我来说是致命的错误。

2017-11-13 15:10:46.739 MyPlayground[4005:234029] *** Terminating app due to uncaught exception 'NSRangeException', reason: '-[UITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:]: row (200) beyond bounds (0) for section (0).' 2017-11-13 15:10:46.739 MyPlayground [4005:234029] ***由于未捕获的异常'NSRangeException'而终止应用程序,原因:'-[UITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:]:行(200)超出范围(0) (0)。”

I changed the dataGeneration function to the following code which just calls reloadData once the data has been generated and scrolls to the bottom. 我将dataGeneration函数更改为以下代码,一旦生成数据并滚动到底部,该代码仅调用reloadData

I think the error was that without reloading, there wasn't that many rows to scroll to. 我认为错误是没有重新加载,滚动到的行就不多了。

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

Once I got around the crash, the tableView worked fine for me. 一旦我解决了崩溃问题,tableView就可以正常工作了。 No flickering or duplication. 没有闪烁或重复。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM