2017-11-13 13 views
1

私はアプリケーションでこのバグを修正するために非常識な時間を費やしました。私たちは現在チャットの仕事をしており、テーブルビューを使っています。シンプルなTableView Swiftを使用したセル構築での不思議な動作

メッセージの量が一定量になるまで、テーブルビューは正常に動作します。その後、テーブルがフリックし始めます。私はストーリーボードなしでコード化しました。このため、制約がトラブルの原因であると私は考えました。だから私はchatview tableviewのいくつかの機能を使って、本当にシンプルなtableviewを作ることにしました(実際には、tableviewはcoredataがリンクされていて、たくさんのグラフィックスがあります)。

私は制約があると思っていたので、すべてうまくいくのを見るためにコードを使っていませんでしたが、そうではありませんでした。 gif画像では、2つの望ましくない動作を見ることができます。最初は、テーブルが完全に再生成されることがあるため、非常に短時間でセルが消えて表示されます(これは非常に面倒なフリックの原因となります)。 2番目はそれほど厄介なことではありません。セルは重複しています(これはセルの再利用性機能のためだと思いますが)。短期間でそれらは収容され、すべてがうまく行きます。

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

Iは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解答のコードベースを変更しました。この時点で、重複セルバグは実際にはテーブルビューでは正常であると考え始めています。問題を見るには、ボタンを数回すばやく押す必要があります。

答えて

1

このコードはtableviewをリロードしていますが、その "煩わしいフリック"を除去していて、滑らかすぎます。

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

ちょうどそれを試してみてください:迷惑なフリックだけで

@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()関数にのAddRow()関数を変更することを修正するには

1

現在のコードをプレイグラウンドで使用すると、私にとって致命的なエラーが発生しました。

2017年11月13日15:10:46.739 MyPlayground [4005:234029] ***終了によるキャッチされない例外のアプリ 'NSRangeException'、理由:「 - [のUITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:]:行(200 )セクション(0)の境界(0)を超えています。 '

は、私は、データが生成され、下にスクロールされた後だけ reloadDataを呼び出して、次のコードに dataGeneration機能を変更しました。

私はエラーが再ロードせずに、スクロールする行がそれほど多くなかったと思います。

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

クラッシュを乗り越えたら、tableViewがうまくいきました。ちらつきや重複はありません。

+0

ありがとう@scriptable、私は質問の上記のコードを変更しましたが、私はまだ同じ問題を提示します。実際には、ボタンを非常に短時間でクリックすると問題が表示されます(チャット中で、ルーム参加者がメッセージを非常に速く書く) –

+0

ボタンをすばやく入力していて、1分で約30行追加しました問題はなかった。物理的なデバイスで実行しようとしましたか?シミュレータは実際のデバイスよりも遅く実行できます – Scriptable