読者です 読者をやめる 読者になる 読者になる

【swift】無限スクロールするUItableViewを実装する方法メモ

はじめに

今回やりたかったのは、またありがちなこんな感じの画面です。
f:id:yoppy0066:20150903080226j:plain

といってもやりたかったのは、こちらで書いた内容をコードレベルにしたもので。
【swift】uitableviewが重いときに対処すべき2つのこと - とりあえずphpとか

UITableViewを使うときにパフォーマンス面で気をつけるということでした(なめらかになるにしたかった)

あとは、行によってテーブルセルの高さを動的に変更することでしょうか

上の記事とかぶりますがやっぱり以下がポイントかとおもいます
・1度高さを計算した行は何度も計算し直さない
・セルの高さ計算用のオブジェクトを用意して使い回す

実装方法

ViewController.swift

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
    var tableView: UITableView = UITableView()
    var tableData = [Dictionary<String, AnyObject>]()

    // API等からデータを取得するためのクラス
    var model: Model = Model()

    // セルの高さ計算用のテーブルセル ★ここがポイント
    var calcCell = CustomTableViewCell()
    
    // セルの高さ保持用の配列 ★ここがポイント
    var cacheCellHeight = [CGFloat]()
    
    // 読み込み中フラグ
    var isLoading: Bool = false
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        // データ取得
        self.isLoading = true
        model.get { (result) -> Void in
            self.callback(result)
        }
        
        // VIEWをセットします
        setView()
    }

    // VIEWをセットします
    func setView()
    {
        // ・・・
        //テーブルビューのおきまりの処理とか

        tableView.registerClass(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell")
        self.view.addSubview(tableView)
    }
    
    func callback(result: [Dictionary<String,AnyObject>])
    {
        for var i = 0; i < result.count; i++ {
            self.tableData.insert(result[i], atIndex: self.tableData.count)
        }
        self.isLoading = false
        self.tableView.reloadData()
    }
    
    // 1番したまでスクロールしたらデータ取得
    func scrollViewDidScroll(scrollView: UIScrollView)
    {
        var contentOffsetWidthWindow = self.tableView.contentOffset.y + self.tableView.bounds.size.height
        var eachToBottom = contentOffsetWidthWindow >= self.tableView.contentSize.height
        if (!eachToBottom || self.isLoading) {
            return;
        }
        
        self.isLoading = true
        model.get { (result) -> Void in
            self.callback(result)
        }
    }
    
    // テーブルセルの高さをかえします ★ここがポイント
    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
    {
        if cacheCellHeight.count - 1 >= indexPath.row {
            return cacheCellHeight[indexPath.row]
        } else {
            var height = self.calcCell.setData(tableData[indexPath.row])
            cacheCellHeight.insert(height, atIndex: indexPath.row)
            return height
        }
    }
    
    // テーブルの行数をかえします
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return self.tableData.count
    }
}

CustomTableViewCell.swift
カスタムテーブルビューセルで画面にデータをセットすることと、高さをかえすのが役割

class CustomTableViewCell: UITableViewCell
{
    var icon = UIImageView()
    var title = UILabel()
    var content = UILabel()
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String!)
    {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.setUp()
    }
    
    required init(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)
        self.setUp()
    }
    
    func setUp()
    {
        self.addSubview(icon)
        self.addSubview(title)
        self.addSubview(content)
    }

    // データをセットして高さを返します
    func setData(data: Dictionary<String,AnyObject>) -> CGFloat
    {
        var widthMax: CGFloat = self.frame.width
        var heightMax: CGFloat = self.frame.height
        
        icon.frame = CGRectMake(5, 5, 48, 48)
        icon.image = UIImage(named: data["icon"] as! String)
        
        title.frame = CGRectMake(icon.frame.origin.x + icon.frame.size.width + 5, 5, widthMax - (5 + icon.frame.size.width + 5 + 5), 20)
        title.font = UIFont.boldSystemFontOfSize(15)
        title.text = data["name"] as? String
        
        content.frame = CGRectMake(title.frame.origin.x, title.frame.origin.y + title.frame.size.height + 5, title.frame.size.width, 0)
        content.numberOfLines = 0
        content.font =  UIFont.systemFontOfSize(13)
        content.text = (data["content"] as? String)! + (data["content"] as? String)! + (data["content"] as? String)!
        content.sizeToFit()
        
        var height: CGFloat = content.frame.origin.y + content.frame.size.height + 5
        return height
    }
}

Model.swift
APIからデータを取得する擬似的なクラス

class Model: NSObject
{
    func get(callback:(result: [Dictionary<String,AnyObject>]) -> Void)
    {
        var result: [Dictionary<String, AnyObject>] = []
        for var i = 0; i < 50; i++
        {
            var n = Int(arc4random() % 5 + 1)
            var m = Int(arc4random() % 3 + 1)
            
            var content = ""
            for var j = 0; j < n; j++ {
                content = content + "コメントコメントコメント"
            }
            
            var dictionary: Dictionary<String, AnyObject> = [
                "icon": "icon" + String(m) + ".jpg",
                "name": "ユーザー名",
                "content": content,
            ]
            result.insert(dictionary, atIndex: result.count)
        }
        callback(result: result)
    }
}

ずらずらかきましたが以上です