ボタン付きのテーブルビューがあり、そのうちの1つがタップされたときにindexpath.rowを使用したい。これは私が現在持っているものですが、常に0です
var point = Int()
func buttonPressed(sender: AnyObject) {
let pointInTable: CGPoint = sender.convertPoint(sender.bounds.Origin, toView: self.tableView)
let cellIndexPath = self.tableView.indexPathForRowAtPoint(pointInTable)
println(cellIndexPath)
point = cellIndexPath!.row
println(point)
}
giorashcはほとんど答えを持っていましたが、セルには余分なcontentView
層があるという事実を見落としていました。したがって、1階層深くする必要があります。
guard let cell = sender.superview?.superview as? YourCellClassHere else {
return // or fatalError() or whatever
}
let indexPath = itemTable.indexPath(for: cell)
これは、ビュー階層内でtableViewがサブビューとしてセルを持ち、その後に独自の「コンテンツビュー」を持つため、セル自体を取得するためにこのコンテンツビューのスーパービューを取得する必要があるためです。この結果、ボタンがセルのコンテンツビューに直接ではなくサブビューに含まれている場合、アクセスするにはさらに多くのレイヤーを移動する必要があります。
上記はそのようなアプローチの1つですが、必ずしも最良のアプローチではありません。機能的ではありますが、ビュー階層など、Appleが必ずしも文書化したことがないUITableViewCell
に関する詳細を想定しています。これは将来変更される可能性があり、上記のコードは結果として予期しない動作をする可能性があります。
上記の結果として、寿命と信頼性の理由から、別のアプローチを採用することをお勧めします。このスレッドには多くの選択肢がリストされており、読み進めることをお勧めしますが、私の個人的なお気に入りは次のとおりです。
セルクラスでクロージャのプロパティを保持し、ボタンのアクションメソッドにこれを呼び出させます。
class MyCell: UITableViewCell {
var button: UIButton!
var buttonAction: ((Any) -> Void)?
@objc func buttonPressed(sender: Any) {
self.buttonAction?(sender)
}
}
次に、cellForRowAtIndexPath
にセルを作成するときに、クロージャーに値を割り当てることができます。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! MyCell
cell.buttonAction = { sender in
// Do whatever you want from your button here.
}
// OR
cell.buttonAction = buttonPressed(closure: buttonAction, indexPath: indexPath) // <- Method on the view controller to handle button presses.
}
ハンドラコードをここに移動すると、既に存在するindexPath
引数を利用できます。これは、文書化されていない特性に依存しないため、上記のリストよりもはるかに安全なアプローチです。
この種の問題に対する私のアプローチは、セルとテーブルビューの間でデリゲートプロトコルを使用することです。これにより、ボタンハンドラーをセルサブクラスに保持し、View Builderにボタンハンドラーロジックを保持したまま、Interface Builderでプロトタイプセルにタッチアップアクションハンドラーを割り当てることができます。
また、ビュー階層をナビゲートしたり、セルインデックスが(挿入、削除、並べ替えの結果として)変更されたときに問題となるtag
プロパティの使用の脆弱なアプローチを回避します。
CellSubclass.Swift
protocol CellSubclassDelegate: class {
func buttonTapped(cell: CellSubclass)
}
class CellSubclass: UITableViewCell {
@IBOutlet var someButton: UIButton!
weak var delegate: CellSubclassDelegate?
override func prepareForReuse() {
super.prepareForReuse()
self.delegate = nil
}
@IBAction func someButtonTapped(sender: UIButton) {
self.delegate?.buttonTapped(self)
}
ViewController.Swift
class MyViewController: UIViewController, CellSubclassDelegate {
@IBOutlet var tableview: UITableView!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CellSubclass
cell.delegate = self
// Other cell setup
}
// MARK: CellSubclassDelegate
func buttonTapped(cell: CellSubclass) {
guard let indexPath = self.tableView.indexPathForCell(cell) else {
// Note, this shouldn't happen - how did the user tap on a button that wasn't on screen?
return
}
// Do whatever you need to do with the indexPath
print("Button tapped on row \(indexPath.row)")
}
}
UPDATE:ボタンを含むセルのindexPathを取得します(セクションと行の両方):
ボタン位置の使用
buttonTapped
メソッド内で、ボタンの位置を取得し、tableViewで座標に変換してから、その座標で行のindexPathを取得できます。
func buttonTapped(_ sender:AnyObject) {
let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
}
NOTE:関数view.convert(CGPointZero, to:self.tableView)
を使用すると、場合によっては行のnil
を見つけるときにEdgeケースに遭遇することがありますそこにtableViewセル。これを修正するには、次のように、Originからわずかにオフセットされた実際の座標を渡します。
let buttonPosition:CGPoint = sender.convert(CGPoint.init(x: 5.0, y: 5.0), to:self.tableView)
前の回答:タグプロパティの使用(行のみを返す)
UIViewを保持するセルへのポインターを取得するためにスーパービューツリーに登るのではなく、上記のAntonioが言及したbutton.tagプロパティを利用する、より安全で再現性の高い手法があります。これについては this answer 、以下に示す:
cellForRowAtIndexPath:
で、タグプロパティを設定します。
button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
次に、buttonClicked:
関数で、そのタグを参照して、ボタンが配置されているindexPathの行を取得します。
func buttonClicked(sender:UIButton) {
let buttonRow = sender.tag
}
スーパービューツリーをスイングすることは、アプリを設計する上で危険な方法になる可能性があることがわかったため、この方法を好みます。また、objective-Cについては、過去に この手法 を使用しており、結果に満足しています。
テーブルビューにメッセージを送信するデリゲートプロパティを使用してカスタムセルタイプを設定するという@ Paulw11の答えは良い方法ですが、設定するにはある程度の作業が必要です。
テーブルビューセルのビュー階層を歩いてセルを探すのは悪い考えだと思います。壊れやすい-後でレイアウトの目的でビューでボタンを囲むと、そのコードが壊れる可能性があります。
ビュータグの使用も脆弱です。セルを作成するときにタグを設定することを忘れないでください。別の目的でビュータグを使用するView Controllerでそのアプローチを使用すると、タグ番号が重複する可能性があり、コードが期待どおりに機能しなくなる可能性があります。
テーブルビューセルに含まれるビューのindexPathを取得できるUITableViewの拡張機能を作成しました。渡されたビューが実際にテーブルビューセル内にない場合は、nilになるOptional
を返します。以下は、拡張ソースファイル全体です。このファイルをプロジェクトに配置し、付属のindexPathForView(_:)
メソッドを使用して、任意のビューを含むindexPathを見つけることができます。
//
// UITableView+indexPathForView.Swift
// TableViewExtension
//
// Created by Duncan Champney on 12/23/16.
// Copyright © 2016-2017 Duncan Champney.
// May be used freely in for any purpose as long as this
// copyright notice is included.
import UIKit
public extension UITableView {
/**
This method returns the indexPath of the cell that contains the specified view
- Parameter view: The view to find.
- Returns: The indexPath of the cell containing the view, or nil if it can't be found
*/
func indexPathForView(_ view: UIView) -> IndexPath? {
let center = view.center
let viewCenter = self.convert(center, from: view.superview)
let indexPath = self.indexPathForRow(at: viewCenter)
return indexPath
}
}
使用するには、セルに含まれているボタンのIBActionでメソッドを呼び出すだけです。
func buttonTapped(_ button: UIButton) {
if let indexPath = self.tableView.indexPathForView(button) {
print("Button tapped at indexPath \(indexPath)")
}
else {
print("Button indexPath not found")
}
}
(indexPathForView(_:)
関数は、渡されたビューオブジェクトが現在画面上にあるセルに含まれている場合にのみ機能することに注意してください。画面上にないビューは実際には特定のindexPathに属していないため、含まれているセルがリサイクルされるときに別のindexPathに割り当てられます。)
上記の拡張機能を使用する動作中のデモプロジェクトをGithubからダウンロードできます。 TableViewExtension.git
Swift2.1
の場合
私はそれを行う方法を見つけました。うまくいけば、それが助けになるでしょう。
let point = tableView.convertPoint(CGPoint.zero, fromView: sender)
guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
fatalError("can't find point in tableView")
}
Swift 4では、これを使用するだけです:
func buttonTapped(_ sender: UIButton) {
let buttonPostion = sender.convert(sender.bounds.Origin, to: tableView)
if let indexPath = tableView.indexPathForRow(at: buttonPostion) {
let rowIndex = indexPath.row
}
}
Swift 4ソリューション:
セルにボタン(myButton)またはその他のビューがあります。 cellForRowAtでこのようにタグを割り当てます
cell.myButton.tag = indexPath.row
ここで、Functionまたはその他をタップします。このように取り出して、ローカル変数に保存します。
currentCellNumber = (sender.view?.tag)!
この後、このcurrentCellNumberの任意の場所を使用して、選択したボタンのindexPath.rowを取得できます。
楽しい!
イベントハンドラーの送信者はボタン自体なので、ボタンのtag
プロパティを使用して、cellForRowAtIndexPath
で初期化されたインデックスを格納します。
しかし、もう少し作業があれば、まったく別の方法でやることになります。カスタムセルを使用している場合、これは私が問題にアプローチする方法です:
cellForRowAtIndexPath
で初期化しますデリゲートコールバックの使用に関するPaulw11の提案を見た後、少し詳しく説明し、別の同様の提案を送りたいと思いました。デリゲートパターンを使用したくない場合は、次のようにSwiftのクロージャーを利用できます。
セルクラス:
class Cell: UITableViewCell {
@IBOutlet var button: UIButton!
var buttonAction: ((sender: AnyObject) -> Void)?
@IBAction func buttonPressed(sender: AnyObject) {
self.buttonAction?(sender)
}
}
cellForRowAtIndexPath
メソッド:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
cell.buttonAction = { (sender) in
// Do whatever you want from your button here.
}
// OR
cell.buttonAction = buttonPressed // <- Method on the view controller to handle button presses.
}
#selectorを使用してIBactionを呼び出してみてください。
cell.editButton.tag = indexPath.row
cell.editButton.addTarget(self, action: #selector(editButtonPressed), for: .touchUpInside)
これにより、メソッドeditButtonPressed内のインデックスパスにアクセスできます。
func editButtonPressed(_ sender: UIButton) {
print(sender.tag)//this value will be same as indexpath.row
}
Modelクラスを使用してtableViewとcollectionViewのセルを管理するための非常に簡単で十分な方法を見つけました。これは完璧に機能します。
実際にこれを処理するより良い方法があります。これはセルと値を管理するために機能します
ここに私の出力(スクリーンショット)があるのでこれを参照してください
こちらが私のコードです
クラスRNCheckedModel:NSObject {
var is_check = false
var user_name = ""
}
クラスInviteCell:UITableViewCell {
@IBOutlet var imgProfileImage: UIImageView!
@IBOutlet var btnCheck: UIButton!
@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
}
クラスRNInviteVC:UIViewController、UITableViewDelegate、UITableViewDataSource {
@IBOutlet var inviteTableView: UITableView!
@IBOutlet var btnInvite: UIButton!
var checkArray : NSMutableArray = NSMutableArray()
var userName : NSMutableArray = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
btnInvite.layer.borderWidth = 1.5
btnInvite.layer.cornerRadius = btnInvite.frame.height / 2
btnInvite.layer.borderColor = hexColor(hex: "#512DA8").cgColor
var userName1 =["Olivia","Amelia","Emily","Isla","Ava","Lily","Sophia","Ella","Jessica","Mia","Grace","Evie","Sophie","Poppy","Isabella","Charlotte","Freya","Ruby","Daisy","Alice"]
self.userName.removeAllObjects()
for items in userName1 {
print(items)
let model = RNCheckedModel()
model.user_name = items
model.is_check = false
self.userName.add(model)
}
}
@IBAction func btnInviteClick(_ sender: Any) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return userName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: InviteCell = inviteTableView.dequeueReusableCell(withIdentifier: "InviteCell", for: indexPath) as! InviteCell
let image = UIImage(named: "ic_unchecked")
cell.imgProfileImage.layer.borderWidth = 1.0
cell.imgProfileImage.layer.masksToBounds = false
cell.imgProfileImage.layer.borderColor = UIColor.white.cgColor
cell.imgProfileImage.layer.cornerRadius = cell.imgProfileImage.frame.size.width / 2
cell.imgProfileImage.clipsToBounds = true
let model = self.userName[indexPath.row] as! RNCheckedModel
cell.lblName.text = model.user_name
if (model.is_check) {
cell.btnCheck.setImage(UIImage(named: "ic_checked"), for: UIControlState.normal)
}
else {
cell.btnCheck.setImage(UIImage(named: "ic_unchecked"), for: UIControlState.normal)
}
cell.btnCheck.tag = indexPath.row
cell.btnCheck.addTarget(self, action: #selector(self.btnCheck(_:)), for: .touchUpInside)
cell.btnCheck.isUserInteractionEnabled = true
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
@objc func btnCheck(_ sender: UIButton) {
let tag = sender.tag
let indexPath = IndexPath(row: tag, section: 0)
let cell: InviteCell = inviteTableView.dequeueReusableCell(withIdentifier: "InviteCell", for: indexPath) as! InviteCell
let model = self.userName[indexPath.row] as! RNCheckedModel
if (model.is_check) {
model.is_check = false
cell.btnCheck.setImage(UIImage(named: "ic_unchecked"), for: UIControlState.normal)
checkArray.remove(model.user_name)
if checkArray.count > 0 {
btnInvite.setTitle("Invite (\(checkArray.count))", for: .normal)
print(checkArray.count)
UIView.performWithoutAnimation {
self.view.layoutIfNeeded()
}
} else {
btnInvite.setTitle("Invite", for: .normal)
UIView.performWithoutAnimation {
self.view.layoutIfNeeded()
}
}
}else {
model.is_check = true
cell.btnCheck.setImage(UIImage(named: "ic_checked"), for: UIControlState.normal)
checkArray.add(model.user_name)
if checkArray.count > 0 {
btnInvite.setTitle("Invite (\(checkArray.count))", for: .normal)
UIView.performWithoutAnimation {
self.view.layoutIfNeeded()
}
} else {
btnInvite.setTitle("Invite", for: .normal)
}
}
self.inviteTableView.reloadData()
}
func hexColor(hex:String) -> UIColor {
var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if (cString.hasPrefix("#")) {
cString.remove(at: cString.startIndex)
}
if ((cString.count) != 6) {
return UIColor.gray
}
var rgbValue:UInt32 = 0
Scanner(string: cString).scanHexInt32(&rgbValue)
return UIColor(
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: CGFloat(1.0)
)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
In Swift 3.また、ガードステートメントを使用して、ブレースの長いチェーンを回避しました。
func buttonTapped(sender: UIButton) {
guard let cellInAction = sender.superview as? UITableViewCell else { return }
guard let indexPath = tableView?.indexPath(for: cellInAction) else { return }
print(indexPath)
}
ConvertPointメソッドを使用してtableviewからポイントを取得し、このポイントをindexPathForRowAtPointメソッドに渡してindexPathを取得しました
@IBAction func newsButtonAction(sender: UIButton) {
let buttonPosition = sender.convertPoint(CGPointZero, toView: self.newsTableView)
let indexPath = self.newsTableView.indexPathForRowAtPoint(buttonPosition)
if indexPath != nil {
if indexPath?.row == 1{
self.performSegueWithIdentifier("alertViewController", sender: self);
}
}
}
ボタンがUITableViewCellの別のビュー内にある場合があります。その場合、superview.superviewはセルオブジェクトを提供しない可能性があるため、indexPathはnilになります。
その場合、セルオブジェクトを取得するまでスーパービューを見つけ続ける必要があります。
スーパービューでセルオブジェクトを取得する関数
func getCellForView(view:UIView) -> UITableViewCell?
{
var superView = view.superview
while superView != nil
{
if superView is UITableViewCell
{
return superView as? UITableViewCell
}
else
{
superView = superView?.superview
}
}
return nil
}
次のようにボタンタップでindexPathを取得できます
@IBAction func tapButton(_ sender: UIButton)
{
let cell = getCellForView(view: sender)
let indexPath = myTabelView.indexPath(for: cell)
}