[swift]挫折しながら覚えるiOS開発その7 UITableViewのセクション分け


前回はレイアウト調整やディレクトリ構造の整理を行いました。

今回はUITableViewのセクション分けの対応をしたいと思います。

週刊Qiita」はその名の通り、週間更新なので、週ごとにセクション分けをしてセルの表示を行いたいです。

swift7-1

1. セクション表示用のメソッドを追加

セクション表示を行うには

  • セクションの数を返す numberOfSectionsInTableView(tableView: UITableView) メソッド
  • セクションのタイトルを返す tableView(tableView: UITableView, titleForHeaderInSection section: Int) メソッド

を追加します。

本来はAPIの内容を元に動的にセクション数やセクションタイトルを表示するようにしたいのですが、まずは仮の値を入れてビルドしてみます。

# ViewController.swift

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 2
    }

    func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "section name"
    }

ビルドを実行すると「section name」というタイトルで、各セクション2個ずつのセルを表示できました。

swift7-2

2. playgroundで試しながらデータ構造を考える

仮実装ができたので、動的にセクション情報を変更できるようにしていきます。

現状は以下のようにAPIから取得したリンク情報をlinks変数にすべて格納しています。

var links = [Link]()
・・・
self.links.append(Link(省略))

このデータ構造だと、セクション毎にリンクが何件あるかが分からないので、データ構造を見直します。

以下のようにplaygroundでどういうデータ構造にしようか色々試していました。

var linkList = [
    ["Link", "Link"],
    ["Link", "Link"]
]

var sections = [
    "2015-07-22",
    "2015-07-15"
]

var sectionIdx = 0
linkList[sectionIdx]
sections[sectionIdx]

ちょっとした確認をしたいときにはplaygroundを使うと手軽で良いですね。

swift7-3

とりあえず、sections変数に週ごとの日付情報を配列で保持するようにして、linkList変数にはセクション毎のLinkモデルの配列を保持することにしました。

一つの変数でなんとかしようともしたのですが途中で挫折して、上記のやり方にしました。

3. APIの内容を元に動的にセクション数、セクションタイトルを切り替える

データ構造が決まったので、ViewControllerを新しいデータ構造に切り替えていきます。

まずは変数宣言。

    var linkList = [[Link]]()
    var sections = [String]()

続いてAPIからデータを取得するsetupLinksメソッドを変更します。

for文で各セクション毎のリンク情報をlinks配列に保持し、セクション毎にsections配列、linkList配列を更新します。

    func setupLinks() {
        Alamofire.request(.GET, "http://qiita-stock.info/api.json")
            .responseJSON { _, _, data, _ in
                var json = JSON(data!)
                for (i, topic) in json {
                    # セクション毎のLink配列を作成
                    var links = [Link]()
                    for (k, link) in topic["links"] {
                        links.append(Link(
                            title: link["title"].asString!,
                            username: link["author"].asString!,
                            userIconUrl: link["icon"].asString!,
                            stockCount: link["stock"].asInt!,
                            isNew: link["is_new"].asBool!
                        ))
                    }
                    # 日付でセクションを分ける
                    self.sections.append(topic["send_date"].asString!)
                    # linksをセクション毎に分けて保持
                    self.linkList.append(links)
                }
                self.tableView.reloadData()
        }
    }

セクション数を返すnumberOfSectionsInTableViewメソッドも更新します。

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return sections.count
    }

セクションタイトルを返すtableViewメソッドはsectionのindexを元にセクション名を取得するようにします。

    func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        let sectionName = sections[section]
        return "\(sectionName)のランキング"
    }

セクションに含まれるセルの数を返すtableViewメソッドも、セクション毎のlinkListのcountをするように変更します。

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return linkList[section].count
    }

セルの内容を設定しているtableViewメソッドでは、indexPath.sectionで現在のsectionがわかるので、indexPath.sectionを使って現在のセル情報を設定するように修正します。

   func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
       var cell: LinkViewCell = tableView.dequeueReusableCellWithIdentifier("LinkViewCell") as! LinkViewCell
       cell.setCell(linkList[indexPath.section][indexPath.row])
       return cell
   }

これでセクション分けの設定ができました。

最終的なViewController.swiftは以下のようになります。

# ViewController.swift

import UIKit
import Alamofire

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

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

        self.setupLinks()

        tableView.delegate = self
        tableView.dataSource = self
    }

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

    var linkList = [[Link]]()
    var sections = [String]()

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return linkList[section].count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell: LinkViewCell = tableView.dequeueReusableCellWithIdentifier("LinkViewCell") as! LinkViewCell

        // 偶数行の場合は色をグレーに
        let altCellColor = UIColor(red:243.0/255, green:243.0/255, blue:243.0/255, alpha:1.0)
        if (indexPath.row % 2 == 0) {
            cell.backgroundColor = altCellColor;
        } else {
            cell.backgroundColor = UIColor.whiteColor();
        }

        cell.setCell(linkList[indexPath.section][indexPath.row])
        return cell
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return sections.count
    }

    func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        let sectionName = sections[section]
        return "\(sectionName)のランキング"
    }

    func setupLinks() {
        Alamofire.request(.GET, "http://qiita-stock.info/api.json")
            .responseJSON { _, _, data, _ in
                var json = JSON(data!)
                for (i, topic) in json {
                    var links = [Link]()
                    for (k, link) in topic["links"] {
                        links.append(Link(
                            title: link["title"].asString!,
                            username: link["author"].asString!,
                            userIconUrl: link["icon"].asString!,
                            stockCount: link["stock"].asInt!,
                            isNew: link["is_new"].asBool!
                        ))
                    }
                    self.sections.append(topic["send_date"].asString!)
                    self.linkList.append(links)
                }
                self.tableView.reloadData()
        }
    }
}

この状態で再ビルドを行うとセクション分けが正しく行われるようになります。

swift7-1

これでトップ画面の表示がひと通りできてきました。

次回はリンクをクリックした場合の対応ということで、WebView周りの実装を行います。

参考