LoginSignup
57
63

More than 5 years have passed since last update.

UITableViewのIndexPathの管理について

Last updated at Posted at 2015-12-16

テーマ

UITableViewのセルを表示するときや、イベントをキャッチするときに、
利用するNSIndexPathの管理方法について書きます。
(アドバイスを受け、改定しました。)

経緯

iOSを始めた頃、TableViewのセルにボタンなどのコントロールをつけた時に、
UITableViewCell自身がNSIndexPathを保持していないため、
UITableViewCell自身がどのセル(セクションとロー)かわからないという
問題に遭遇しました。

非同期で画像を取得してセルに表示するときに、
画像がズレてしまうというのも同じように対応できます。
(今回は触れません。)

解決策

  1. UIViewのプロパティであるtag(Int)にセル番号を保持しておく
  2. UITableViewCellでNSIndexPathを管理する
  3. UITableViewのindexPathForRowAtPointを利用する

ひとつずつ、ご紹介します。

1. UIViewのプロパティであるtag(Int)にセル番号を保持しておく

本ケースは、UIButtonなどUIViewを継承したクラスは、tagを保持することができるため、そこにセル番号(ロー)を保存しておく方法です。

テーブルのCellを作るときのイメージ

    //セル番号を覚えておく
    cell.okButton.tag = indexPath.row

問題点
セクションがある場合は、管理が難しい。
例えば、こんな感じ????
桁あふれする可能性もある??

    //上位2Byteにセクション番号、下位2Byteにロー番号
    cell.okButton.tag = indexPath.section << 16
    cell.okButton.tag |= indexPath.row

2. UITableViewCellでNSIndexPathを管理する。

UITableViewCellのExtension(Obj-Cはカテゴリ)を作り、
NSIndexPathをプロパティで持つ方法です。

通常、Extensionは、プロパティをもてませんが、
「objc_getAssociatedObject」と「objc_setAssociatedObject」を利用すれば、
管理できるようです。
(むやみに使うのは避けたほうが良いかもしれません。)

まずは、ソースコードは下記のとおりです。

UITableViewCell.swift
import UIKit

private var indexPathKey: UInt8 = 0

extension UITableViewCell {

    public var indexPath: NSIndexPath? {

        get {
            return objc_getAssociatedObject(self, &indexPathKey) as? NSIndexPath
        }

        set {
            objc_setAssociatedObject(self, &indexPathKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }        
    }

}

これでNSIndexPathをUITableViewCellで管理できます

使い方は、下記のようなイメージです。

①カスタムセルを作る

SDTTableViewCell.swift
import UIKit

class SDTTableViewCell: UITableViewCell {

    @IBOutlet weak var titleLabel: UILabel!
    var title = "" {
        didSet {
            titleLabel.text = title
        }        
    }

    @IBAction func onDidSelectedOKButton(sender: UIButton) {

        if let indexPath = indexPath {
            print("section : \(indexPath.section) - row : \(indexPath.row)")
        }
    }
}

②ViewContorollerから利用してみる

ViewController.swift
import UIKit

class ViewController: UIViewController, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!
    var items = ["株式会社","スマート","デバイス","テクノロジー"]

    override func viewDidLoad() {
        super.viewDidLoad()
        configureTableView()
    }

    private func configureTableView() {
        tableView.dataSource = self
    }

    //MARK:- UITableViewDataSource
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        if let cell = tableView.dequeueReusableCellWithIdentifier("SDTTableViewCell", forIndexPath: indexPath) as? SDTTableViewCell {
            cell.indexPath = indexPath
            cell.title = items[indexPath.row]            
            return cell
        }
        return UITableViewCell()
    }
}

3. UITableViewのindexPathForRowAtPointを利用する

こちらは、教えていただいた方法です。
おすすめ!

使い方は、下記のようなイメージです。

①カスタムセルを作る

SDTTableViewCell.h
import UIKit

class SDTTableViewCell: UITableViewCell {

    @IBOutlet weak var titleLabel: UILabel!
    var title = "" {
        didSet {
            titleLabel.text = title
        }
    }
}

②ViewControllerから利用してみる

ViewController.swift
import UIKit

class ViewController: UIViewController, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!
    var items = ["株式会社","スマート","デバイス","テクノロジー"]

    override func viewDidLoad() {
        super.viewDidLoad()
        configureTableView()
    }

    private func configureTableView() {
        tableView.dataSource = self
    }

    //MARK:- UITableViewDataSource
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        if let cell = tableView.dequeueReusableCellWithIdentifier("SDTTableViewCell", forIndexPath: indexPath) as? SDTTableViewCell {
            cell.title = items[indexPath.row]
            return cell
        }
        return UITableViewCell()
    }

    @IBAction func onDidSelectedOKButton(sender: UIButton) {

        let point = self.tableView.convertPoint(sender.frame.origin, fromView: sender.superview)
        if let indexPath = self.tableView.indexPathForRowAtPoint(point) {
            print("section: \(indexPath.section) - row: \(indexPath.row)")
        }
    }
}

まとめ

いかがでしたか?
試行錯誤して、NSIndexPathを取得する方法試してみましたが、
今回記事を投稿してみて、最適な方法がわかりました。

アドバイス頂いたkoitaroさんdokubekoさん
ありがとうございました。

57
63
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
57
63