テーマ
UITableViewのセルを表示するときや、イベントをキャッチするときに、
利用するNSIndexPathの管理方法について書きます。
(アドバイスを受け、改定しました。)
経緯
iOSを始めた頃、TableViewのセルにボタンなどのコントロールをつけた時に、
UITableViewCell自身がNSIndexPathを保持していないため、
UITableViewCell自身がどのセル(セクションとロー)かわからないという
問題に遭遇しました。
非同期で画像を取得してセルに表示するときに、
画像がズレてしまうというのも同じように対応できます。
(今回は触れません。)
解決策
- UIViewのプロパティであるtag(Int)にセル番号を保持しておく
- UITableViewCellでNSIndexPathを管理する
- 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」を利用すれば、
管理できるようです。
(むやみに使うのは避けたほうが良いかもしれません。)
まずは、ソースコードは下記のとおりです。
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で管理できます
使い方は、下記のようなイメージです。
①カスタムセルを作る
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から利用してみる
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を利用する
こちらは、教えていただいた方法です。
おすすめ!
使い方は、下記のようなイメージです。
①カスタムセルを作る
import UIKit
class SDTTableViewCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
var title = "" {
didSet {
titleLabel.text = title
}
}
}
②ViewControllerから利用してみる
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を取得する方法試してみましたが、
今回記事を投稿してみて、最適な方法がわかりました。