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


現在、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行続けて表示されるなど、一部おかしくなってしまいます。

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

以下ソース

[code]

    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()
        }
    }
    
[/code]




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

[code]

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

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

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

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

[code]

    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))
        }
    }
    
[/code]

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

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

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




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

[code]

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

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

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

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

[code]

    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))
        }
    }
    
[/code]

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

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

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

