He pasado un tiempo loco para corregir este error en nuestra aplicación. Actualmente estamos trabajando en un chat y hemos utilizado una vista de tabla para ello.

Nuestra vista de tabla funciona bien hasta que hay una cierta cantidad de mensajes, después de eso, la tabla comienza a moverse. Codifiqué sin guiones gráficos y por esta razón pensé que las limitaciones eran la causa de los problemas. Así que decido hacer una vista de tabla realmente simple con algunas características de nuestra vista de tabla de chat (en realidad, nuestra vista de tabla está vinculada a datos básicos y con muchos elementos gráficos).

Debido a que sospechaba de las limitaciones, no lo usé en el código a continuación solo para ver que todo funciona bien, pero no fue el caso. En la imagen gif podemos ver dos comportamientos indeseables, el primero es que en ocasiones la tabla se vuelve a generar por completo, por lo que las celdas desaparecen y aparecen en muy poco tiempo (esto provoca un flick muy molesto). El segundo no es menos molesto: las celdas están duplicadas (creo que esto es para la función de reutilización de celdas) pero después de un corto período de tiempo se acomodan y todo va bien.

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

Intenté agregar el método prepareForReuse () y eliminé las vistas y las creo en la celda nuevamente, pero sin resultados.

Este es el código de ejemplo, puedes copiarlo y ejecutarlo sin problemas en un Patio de juegos :

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

Gracias de antemano.

Editar:

Cambié la base del código en la respuesta @Scriptable para que sea compatible con el patio de recreo. En este punto, estoy empezando a pensar que el error de celda de duplicación es en realidad normal para tableview. Para ver el problema, el botón debe presionarse varias veces rápidamente.

1
Hugo Reyes 13 nov. 2017 a las 17:34

2 respuestas

La mejor respuesta

Este código está recargando la vista de tabla, pero está eliminando esa "película molesta" y es demasiado suave.

Para solucionar ese molesto movimiento, simplemente cambie la función addRow () a

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

Función 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)
    }
}

Solo inténtalo.

1
nuridin selimko 13 nov. 2017 a las 15:05

Usar su código actual en un patio de juegos me produjo un error fatal.

2017-11-13 15: 10: 46.739 MyPlayground [4005: 234029] *** Finalizando la aplicación debido a una excepción no detectada 'NSRangeException', motivo: '- [UITableView _contentOffsetForScrollingToRowAtIndexPath: atScrollPosition:]: fila (200) más allá de los límites (0) para la sección (0). '

Cambié la función dataGeneration al siguiente código que solo llama a reloadData una vez que se han generado los datos y se desplaza hasta la parte inferior.

Creo que el error fue que sin recargar, no había tantas filas para desplazarse.

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

Una vez que superé el accidente, tableView funcionó bien para mí. Sin parpadeo ni duplicación.

1
Scriptable 16 nov. 2017 a las 13:57