7029


現在、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]


CC BY-SA 3.0
2015-02-24T06:03:08.720
7011
2015-02-25T03:12:46.437
8521
question
ios swift uitableview
UITableViewのカスタムセルの描画について

[code]

    func numberOfSectionsInTableView(tableView: UITableView) -> Int
[/code]

上記の実装が不足していることと、

[code]

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
[/code]

の処理に不具合が出ていると思われます。  
”CustomCell”のクラスでどういった実装しているのかわかりませんが、上記のmethodでは、`addSubView`をするより、差し替えや値の設定をする形へ変更が必要です。

下記には、sampleを記載します。

[code]

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        //cellをリサイクルに対応させて生成
        let cellIdentifier = "Cell"
        var cell :CastamTableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? CastamTableViewCell
        if cell == nil
        {
            cell = CastamTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: cellIdentifier)
        }
        let array : Array = ["20","19","18","17","16"]
        let days : Array = ["2014-02-24","2014-02-23","2014-02-22","2014-02-22","2014-02-21"]
        //カスタムCellで作ったlabelへ設定
        cell!.label0.text = array[indexPath.row]
        cell!.label1.text = days[indexPath.row]
        cell!.label2.text = "xxx"
        cell!.label3.text = "yyy"
        cell!.label4.text = "zzz"
        return cell!
    }
    
    
[/code]


CC BY-SA 3.0
2015-02-24T07:26:29.490
7018
2015-02-24T07:26:29.490
4551
7011
answer


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

[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()で、消してから描くをくり返せば、なんどもカスタムビューを作らなくてもいいでしょう？


CC BY-SA 3.0
2015-02-24T10:55:33.393
7029
2015-02-25T03:12:46.437
2015-02-25T03:12:46.437
7362
7362
7011
answer


質問の主旨からは外れてしまうのでここに書くのは不適切かもしれませんが、  
パフォーマンスの面について私からも一点記載させていただきます。  
※DBアクセスの回数を減らす修正と比べたら微々たるものですが・・・。

[code]

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

ここの実装を見る限りだと

> [カラム1〜5][レコードの値 x n]

となっているように見受けられますが、  
SwiftDataを使ったことが無いのでもし可能なのであればですが、

> [レコード1〜n][カラム1〜5の値]

に変更した方が良いと思います。  
そうすると、例えば下記のような感じで書けるようになります。

[code]

    // データ格納用の配列
    var dataSource = [String]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        //TableView Delegate
        TableView.delegate = self;
        TableView.dataSource = self;
    
        //SwiftData(SQLite用ライブラリ)
        let myDBData = DBData();  
        // データ取得
        dataSource = myDBData.getAll()
    }
    
    //行数
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count;
    }
    
    //行設定
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
        // 〜 中略 〜
    
        // 表示データ取得
        let array = dataSource[indexPath.row]
    
        //カスタムCellで作ったlabelへ設定 ※iwashiroさんの回答をお借りしました。
        cell!.label0.text = array[0]
        cell!.label1.text = array[1]
        cell!.label2.text = array[2]
        cell!.label3.text = array[3]
        cell!.label4.text = array[4]
    
        return cell!
    }
    
[/code]

破棄、ループ、append、配列の階層を潜るといった処理が無くなるため、  
多少は改善につながるかなと思います。

また、これは本当にただのおまけですが、  
ラベルへの値設定をカスタムセルに任せればもっとシンプルになります。

[code]

    //行数
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count;
    }
    
    //行設定
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
        // 〜 中略 〜
    
        //カスタムCellに値を設定
        cell!.setValue(dataSource[indexPath.row])
    
        return cell!
    }
    
[/code]


CC BY-SA 3.0
2015-02-24T14:54:54.313
7042
2015-02-24T14:54:54.313
3516
7011
answer