UITableViewのIndexPathの管理について

# テーマ
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さん](http://qiita.com/koitaro)、[dokubeko](http://qiita.com/dokubeko)さん
ありがとうございました。