UITableViewのカスタムセルの描画について

投稿者: Anonymous

現在、SwiftでUITableViewを使ったiOSアプリを作っています.

カスタムセルを使って各行ごとに5項目のデータを表示するのですが、UILabelで表示すると行数が増えていくにつれ処理が重くなっていくので、UIViewにdrawRectで直接文字を描画し、そのUIViewをカスタムセルのContentViewへAddSubViewして表示することにしました。

しかし、最初に画面が出てきたときに、表示領域内の最下行に表示されるはずのデータが、他の行にも表示されてしまいます。

イメージとしては、

このように表示したいのに


20    2014-02-24    XXX   YYY   ZZZ
19    2014-02-23    XXX   YYY   ZZZ
18    2014-02-22    XXX   YYY   ZZZ
17    2014-02-21    XXX   YYY   ZZZ
16    2014-02-20    XXX   YYY   ZZZ


このように表示されてしまいます。


16    2014-02-20    XXX   YYY   ZZZ
16    2014-02-20    XXX   YYY   ZZZ
16    2014-02-20    XXX   YYY   ZZZ
16    2014-02-20    XXX   YYY   ZZZ
16    2014-02-20    XXX   YYY   ZZZ


一度スクロールすると正しく表示されるようになるのですが、スクロールが早すぎると、同じ値が2,3行続けて表示されるなど、一部おかしくなってしまいます。

どなたか、ご教示お願いします。

以下ソース

import UIKit

//drawRect用
public var tmpDrawData = [String]();

class ViewController: UIViewController,UITableViewDataSource, UITableViewDelegate, {

    //Outlet
    @IBOutlet weak var TableView: UITableView!

    //SwiftData(SQLite用ライブラリ)
    var _myDBData = DBData();   



    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        //TableView Delegate
        TableView.delegate = self;
        TableView.dataSource = self;

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func viewWillAppear(animated: Bool) {
        //TableView
        TableView.reloadData();
    }

    //行数
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return _myDBData.getAll()[0].count;
    }

    //行設定
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as CustomCell;

        //SQLiteに保存されたデータをすべて取り出す
        let allData: [[String]] = _myDBData.getAll()

        //drawRect用データを破棄、各行ごとに新しく用意
        tmpDrawData.removeAll();

        for (var i = 0; i < 5; i++){
            tmpDrawData.append(allData[i][indexPath.row])
        }

        //drawRect
        var cellView: UIView = DrawCustomCell(frame: cell.bounds)
        cellView.backgroundColor = UIColor.clearColor();

        //ContentViewに残っているSubviewを消して新しく追加
        removeAllSubViews(cell.ContentView);
        cell.ContentView.addSubview(cellView);

        return cell
    }

    //Header
    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        //drawRect
        let HeaderView: UIView = MakeHeader(frame: CGRectMake(0, 0, 475, 50))
        HeaderView.backgroundColor = UIColor.clearColor();

        return HeaderView;

    }

    //Header's Height
    func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 50;
    }

    //Cell's Height
    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return 40;
    }


}

//SubView全削除
func removeAllSubViews(parentView: UIView){
    var subviews = parentView.subviews
    for sv in subviews{
        sv.removeFromSuperview()
    }
}

解決

テーブルのスクロールが重くなる理由は、そこではないと思います。

//SQLiteに保存されたデータをすべて取り出す
let allData: [[String]] = _myDBData.getAll()

セルひとつひとつで、SQLiteの全データを呼び出すのが、原因ではないかと私には感じられます。それは、テーブルにひとつあればいいものであるはず。いいかえれば、View Controllerにひとつありさえすればいい。
UILabelのインスタンス5個の、テキストを書きかえることが、そんなに負担がかかるものとは、考えにくいです。すくなくとも、セルごとにSQLiteの全データを呼び出すことよりも、負担は小さいはずです。

あなたの質問には答えてませんが、テーブルの設計を最初から作り直せば、しぜん当面の問題も消えてくれるでしょう。

追加: サンプルコードを作りましたので、掲載します。
テーブルに、月名、英語名、和名を並べて表示するというコードです。
サンプルだけ作るのに、データベースを持ってくるのはたいへんなので、リソースにあるプロパティリストを読み込むことに変更してあります。

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet weak var tableView: UITableView!
    // 元データ(Data source)は、ViewControllerのプロパティとする。
    var monthNames: [[String]] = [[String]]()

    override func viewDidLoad() {
        super.viewDidLoad()

        // 元データを読み込むのはviewDidLoadで1回だけ。
        if let dataURL = NSBundle.mainBundle().URLForResource("DataList", withExtension: "plist") {
            monthNames = NSArray(contentsOfURL: dataURL) as [[String]]
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // 配列第1要素の配列の要素数を、テーブルの行数とする。
        return monthNames[0].count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        // Storyboardで設定したセルIdentifier(Cell)と合わせる。
        var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomCell
        // カスタムビューを作ったり削除したりという、「ご無体」なことはせず、固定。
        let drawView = cell.drawView as DrawView
        drawView.texts = (monthNames[0][indexPath.row], monthNames[1][indexPath.row], monthNames[2][indexPath.row])
        // カスタムビューの再描画、drawRect()メソッドを、強制で呼び出す。
        drawView.setNeedsDisplay()

        return cell
    }
}

// カスタムセルのクラス。カスタムビューの設置は、Storyboardで行っている。
class CustomCell: UITableViewCell {
    @IBOutlet weak var drawView: DrawView!
}

// カスタムビューのクラス。ここにテキストを描画する。
class DrawView: UIView {
    var texts: (String, String, String) = ("Text", "Text", "Text")
    private var attributes: [NSObject: AnyObject] = [:]

    override func awakeFromNib() {
        super.awakeFromNib()

        attributes[NSFontAttributeName] = UIFont(name: "HiraKakuProN-W3", size: 16.0)
        attributes[NSForegroundColorAttributeName] = UIColor.blackColor()
    }

    override func drawRect(rect: CGRect) {
        // いったんビュー全体を白色で塗りつぶす。
        let context = UIGraphicsGetCurrentContext()
        let fillColor = UIColor.whiteColor()
        fillColor.set()
        CGContextFillRect(context, rect)
        テキストの描画。
        let attrStr0 = NSAttributedString(string: texts.0, attributes: attributes)
        let attrStr1 = NSAttributedString(string: texts.1, attributes: attributes)
        let attrStr2 = NSAttributedString(string: texts.2, attributes: attributes)
        attrStr0.drawAtPoint(CGPoint(x: 16.0, y: 12.0))
        attrStr1.drawAtPoint(CGPoint(x: 88.0, y: 12.0))
        attrStr2.drawAtPoint(CGPoint(x: 206.0, y: 12.0))
    }
}

※テーブルのプロパティ「delegate」と「dataSource」は、Storyboard上で設定してあります。

私のいいたいポイントは、

  1. 元データの読み込みは1回だけ。
  2. UIViewのメソッド「setNeedsDisplay()」を呼びましょう。
  3. drawRect()で、消してから描くをくり返せば、なんどもカスタムビューを作らなくてもいいでしょう?
回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *