web-dev-qa-db-ja.com

迅速:セル内のボタンがタップされたときにindexpath.rowを取得する方法は?

ボタン付きのテーブルビューがあり、そのうちの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)
}
80
Vincent

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引数を利用できます。これは、文書化されていない特性に依存しないため、上記のリストよりもはるかに安全なアプローチです。

134
Jacob King

この種の問題に対する私のアプローチは、セルとテーブルビューの間でデリゲートプロトコルを使用することです。これにより、ボタンハンドラーをセルサブクラスに保持し、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)")
    }
} 
46
Paulw11

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については、過去に この手法 を使用しており、結果に満足しています。

44

UITableViewの拡張機能を使用して、ビューのセルを取得します。


テーブルビューにメッセージを送信するデリゲートプロパティを使用してカスタムセルタイプを設定するという@ 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

12
Duncan C

Swift2.1の場合

私はそれを行う方法を見つけました。うまくいけば、それが助けになるでしょう。

let point = tableView.convertPoint(CGPoint.zero, fromView: sender)

    guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
        fatalError("can't find point in tableView")
    }
9
specialvict

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
        }
}
5
DEEPAK KUMAR

Swift 4ソリューション:

セルにボタン(myButton)またはその他のビューがあります。 cellForRowAtでこのようにタグを割り当てます

cell.myButton.tag = indexPath.row

ここで、Functionまたはその他をタップします。このように取り出して、ローカル変数に保存します。

currentCellNumber = (sender.view?.tag)!

この後、このcurrentCellNumberの任意の場所を使用して、選択したボタンのindexPath.rowを取得できます。

楽しい!

4
Sajid Zeb

イベントハンドラーの送信者はボタン自体なので、ボタンのtagプロパティを使用して、cellForRowAtIndexPathで初期化されたインデックスを格納します。

しかし、もう少し作業があれば、まったく別の方法でやることになります。カスタムセルを使用している場合、これは私が問題にアプローチする方法です:

  • カスタムテーブルセルに「indexPath」プロパティを追加します
  • cellForRowAtIndexPathで初期化します
  • タップハンドラーをView Controllerからセル実装に移動します
  • 委任パターンを使用して、タップイベントについてView Controllerに通知し、インデックスパスを渡します
3
Antonio

デリゲートコールバックの使用に関する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.
}
2
Jacob King

#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

}
1

Modelクラスを使用してtableViewとcollectionViewのセルを管理するための非常に簡単で十分な方法を見つけました。これは完璧に機能します。

実際にこれを処理するより良い方法があります。これはセルと値を管理するために機能します

ここに私の出力(スクリーンショット)があるのでこれを参照してください

こちらが私のコードです

  1. モデルclasを作成するのは非常に簡単です。以下の手順に従ってください。 「RNCheckedModel」という名前のSwiftクラスを作成し、次のようにコードを記述します。

クラスRNCheckedModel:NSObject {

var is_check = false
var user_name = ""

}
  1. セルクラスを作成する

クラスInviteCell:UITableViewCell {

@IBOutlet var imgProfileImage: UIImageView!
@IBOutlet var btnCheck: UIButton!
@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
}
  1. 最後に、UIViewControllerUITableViewを使用するときにモデルクラスを使用します。

クラス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()

}

 }
0

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)
}
0
sean

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);
            }   
        }
    }
0
Avijit Nagare

ボタンが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)
}
0
Teena nath Paul