Як отримати indexpath.row, коли елемент активований?


104

У мене є перегляд таблиці з кнопками, і я хочу використовувати 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)
}

чи слід використовувати IndexPathForSelectedRow () замість змінної точки? або де я повинен його використовувати?
Вінсент

Відповіді:


164

giorashc ледь не мав це своєю відповіддю, але він ігнорував той факт, що клітини мають додатковий contentViewшар. Таким чином, ми повинні піти на один шар глибше:

guard let cell = sender.superview?.superview as? YourCellClassHere else {
    return // or fatalError() or whatever
}

let indexPath = itemTable.indexPath(for: cell)

Це тому, що всередині ієрархії перегляду tableView містить комірки як підпогляди, які згодом мають власні "перегляди вмісту", тому для отримання осередку потрібно отримати огляд цього перегляду вмісту. В результаті цього, якщо ваша кнопка міститься в підвиді, а не безпосередньо у вікні вмісту комірки, вам доведеться пройти скільки завгодно глибших шарів, щоб отримати доступ до неї.

Вищезазначене є одним із таких підходів, але не обов’язково найкращим. Хоча він функціональний, він передбачає деталі про UITableViewCellте, що Apple ніколи не обов'язково документувала, наприклад, це ієрархія перегляду. Це може бути змінено в майбутньому, і вищезазначений код цілком може поводитись непередбачувано як результат.

В результаті вищесказаного, з міркувань довговічності та надійності, я рекомендую застосувати інший підхід. У цій темі перелічено багато альтернатив, і я закликаю вас прочитати, але моїм улюбленим є наступне:

Утримуйте властивість закриття для вашого класу комірки, нехай метод дії кнопки викликає це.

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аргументом. Це набагато безпечніший підхід, ніж той, що перерахований вище, оскільки не покладається на недокументовані ознаки.


2
Добре помічений. Я грамотний розробник, обіцяю;) - Змінив свою відповідь.
Яків Цар

12
Це не правильний спосіб отримати клітинку з кнопки. Макет клітини змінювався з роками і такий код не працюватиме, коли це станеться. Не застосовуйте цей підхід.
rmaddy

11
Це погане рішення. Він передбачає деталі про UITableViewCells, на які Apple ніколи не обов'язково погоджувалася. Хоча UITableViewCells має властивість contentView, немає гарантії, що наглядом contentView завжди буде Осередок.
bpapa

1
@PintuRajput Чи можете ви, будь ласка, описати свою ієрархію поглядів? Ви, ймовірно, це бачите, оскільки ваша кнопка не є прямим підглядом перегляду вмісту комірки.
Яків Король

2
@ymutlu Я повністю згоден, я це заявив у відповіді. Я також запропонував набагато більш надійне рішення. Причиною я залишив оригінал на місці, тому що я відчуваю, що краще показати іншим дияволам проблеми з підходом, ніж ухилятися від них взагалі, це навчає їх нічого, чого ти не бачиш. :)
Яків Король

61

Мій підхід до подібного роду проблем полягає у використанні протоколу делегата між коміркою та табличним переглядом. Це дозволяє зберігати обробник кнопок у підкласі комірок, що дає змогу призначити обробник дій дотику до комірки прототипу в Інтерфейсі 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)")
    }
} 

buttonTappedє функцією делегування і знаходиться в контролері перегляду. У моєму прикладі someButtonTappedце метод дії в клітині
Paulw11

@ paulw11 Я отримав клітинку не має жодної кнопки Увімкнено цей метод@IBAction func someButtonTapped(sender: UIButton) { self.delegate?.buttonTapped(self) }
EI Captain v2.0

1
Це досить гарне рішення (ніде не так вже й погано, як у двох, на даний момент з більшим числом голосів, використовуючи тег, що дивиться на супервізор), але здається, що занадто багато додаткового коду для додавання.
bpapa

2
Це правильне рішення і має бути прийнятою відповіддю. Він не зловживає властивістю тегу, не передбачає побудови комірок (яку Apple може легко змінити) і все одно працюватиме (без зайвого кодування), коли клітинки переміщуються або нові комірки додаються між існуючими комірками.
Роботизований кіт

1
@ Paulw11 Я спочатку думав, що це багато коду, але він виявився набагато стійкішим, ніж те, що я використовував раніше. Дякуємо, що опублікували це надійне рішення.
Адріан

53

ОНОВЛЕННЯ : Отримання індексного шляху комірки, що містить кнопку (і розділ, і рядок):

Використання кнопки Положення

Всередині вашого buttonTappedметоду ви можете схопити положення кнопки, перетворити її на координату в tableView, а потім отримати indexPath рядка за цією координатою.

func buttonTapped(_ sender:AnyObject) {
    let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:self.tableView)
    let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
}

ПРИМІТКА : Іноді ви можете зіткнутися з крайовим регістром, коли використання функції view.convert(CGPointZero, to:self.tableView)призводить до пошуку nilрядка в точці, навіть якщо там знаходиться комірка tableView. Щоб виправити це, спробуйте передати справжню координату, яка трохи зміщена від початку, наприклад:

let buttonPosition:CGPoint = sender.convert(CGPoint.init(x: 5.0, y: 5.0), to:self.tableView)

Попередній відповідь: Використання властивості тегу (лише повертає рядок)

Замість того, щоб залізти в дерева нагляду, щоб захопити вказівник на клітинку, яка містить UIButton, існує більш безпечна, повторювана техніка, яка використовує властивість button.tag, згадану вище в Антоніо, описану в цій відповіді та показану нижче:

В 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 я раніше використовував цю методику і був задоволений результатом.


5
Це хороший спосіб зробити це, і я висловлю його за те, щоб трохи розпочати роботу вашого представника, проте єдиний недолік полягає в тому, що він не дає доступу до indexPath.section, якщо це також потрібно. Хоча чудова відповідь!
Яків Кінг

Дякую Якову! Я ціную реп карму. Якщо ви хочете отримати indexPath.sectionдодаток до indexPath.row(без скидання властивості тегу як indexPath.section), cellForRowAtIndexPath:ви можете просто змінити тег на button.tag = indexPath, а потім у buttonClicked:функції, до якої ви могли отримати доступ як за допомогою, так sender.tag.rowі для sender.tag.section.
Айрон Джон Бонні

1
Це нова функція, тому що я впевнений, що пам’ятаю, що властивість тегу типу Int не є типу AnyObject, якщо це не змінилося в стрімкій 2.3?
Яків Кінг

@JacobKing ти маєш рацію! Мій поганий, що я повністю розставився, коли писав цей коментар, і думав, що тег має тип AnyObject. Дерп - не проти мене. Було б корисно, якщо ви можете передати indexPath як тег, хоча ...
Iron John Bonney

3
Не дуже хороший підхід. З одного боку, це працювало б лише в режимах перегляду таблиць, де є єдиний розділ.
bpapa

16

Використовуйте розширення для UITableView, щоб отримати комірку для будь-якого подання:


@ Відповідь Paulw11 про налаштування користувальницького типу комірок із властивістю делегата, який надсилає повідомлення на подання таблиці - це хороший спосіб, але для налаштування потрібна певна робота.

Я думаю, що ходити по ієрархії подання комірки у вигляді таблиці, шукаючи комірку, є поганою ідеєю. Це тендітно - якщо згодом ви вкладете свою кнопку у подання для макетування, цей код, ймовірно, зламається.

Використання тегів перегляду також неміцне. Ви повинні пам’ятати про теги під час створення комірки, і якщо ви використовуєте такий підхід у контролері перегляду, який використовує теги подання з іншою метою, ви можете мати дублюючі номери тегів, і ваш код може працювати не так, як очікувалося.

Я створив розширення до UITableView, яке дозволяє отримати indexPath для будь-якого подання, яке міститься в комірці табличного подання. Він повертає значення, 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


Дякую, я використав розширення, щоб отримати indexPath текстового перегляду в комірці - працював чудово.
Джеремі Ендрюс

9

Для Swift2.1

Я знайшов спосіб це зробити, сподіваюся, це допоможе.

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

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

Що означає, якщо помилка запускається? Які причини, чому він не може знайти точку в tableView?
OOProg

Це (або подібне, використовуючи методи перетворення UIView) має бути прийнятою відповіддю. Не впевнений, чому він зараз №4, оскільки він не робить припущень щодо приватної спадкоємності подання таблиці, він не використовує властивість тегів (майже завжди погана ідея) і не передбачає багато зайвого коду.
bpapa

9

Рішення Swift 4:

У вас є кнопка (myButton) або будь-який інший вигляд у комірці. Призначте тег у cellForRowAt, як це

cell.myButton.tag = indexPath.row

Тепер у вас торкнітьсяFunction або будь-якого іншого. Витягніть його так і збережіть у локальній змінній.

currentCellNumber = (sender.view?.tag)!

Після цього ви можете використовувати будь-де цей currentCellNumber, щоб отримати indexPath.row обраної кнопки.

Насолоджуйтесь!


Такий підхід працює, але теги перегляду тендітні, як згадується у моїй відповіді. Наприклад, простий цілочисельний тег не працюватиме для подання таблиці з розрізами. (IndexPath it 2 цілих числа.) Мій підхід завжди працюватиме, і немає необхідності встановлювати тег у кнопку (або інший вигляд, який можна натиснути.)
Duncan C

6

У 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
        }
}

Найясніша відповідь, повинна бути обрана. Єдине, на що слід звернути увагу - tableViewце вихідна змінна, на яку потрібно посилатися перед тим, як ця відповідь запрацює.
10000RubyPools

Працюй як шарм !!
Parthpatel1105

4

Дуже просте отримання швидкісного шляху індексу 4, 5

 let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
  cell.btn.tag = indexPath.row


  cell.btn.addTarget(self, action: "buttonTapped:", forControlEvents: 
UIControlEvents.TouchUpInside)

Як отримати IndexPath Inside Btn Клацніть:

    func buttonTapped(_ sender: UIButton) {`
          print(sender.tag) .  


}

3

Оскільки відправником обробника подій є сама кнопка, я б використовував tagвластивість кнопки для зберігання індексу, ініціалізованого в cellForRowAtIndexPath.

Але трохи більше роботи я би зробив зовсім по-іншому. Якщо ви використовуєте користувацьку клітинку, я б так підійшов до проблеми:

  • додати властивість 'indexPath' до клітинки власної таблиці
  • ініціалізувати його в cellForRowAtIndexPath
  • перемістити обробник дотику з контролера перегляду до реалізації комірки
  • використовуйте шаблон делегування, щоб повідомити контролер перегляду про подію, проходячи шлях індексу

Антоніо, у мене є власна клітина, і я хотів би зробити це по-своєму. Однак це не працює. Я хочу, щоб запустився мій код «проведіть пальцем, щоб відкрити кнопку видалення», який є методом tableView commitEditingStyle. Я видалив цей код із класу mainVC і помістив його у клас customCell, але тепер код більше не працює. Що я пропускаю?
Dave G

Я думаю, що це найкращий спосіб отримати indexPath клітинки з х секціями, однак я не бачу необхідності в пунктах 3 та 4 в підході MVC
Едвард

2

Побачивши пропозицію Paulw11 використовувати зворотний виклик делегата, я хотів трохи розробити його / висунути ще одну, подібну пропозицію. Якщо ви не хочете використовувати шаблон делегата, ви можете швидко закрити наступне:

Ваш клас клітин:

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

Я знайшов дуже простий спосіб використання для керування будь-якою коміркою в tableView та collectionView, використовуючи клас Model, і це робота настільки ж ідеально.

Зараз дійсно є набагато кращий спосіб впоратися з цим. Це буде працювати для управління коміркою та значенням.

Ось мій результат (знімок екрана), так що перегляньте це:

введіть тут опис зображення

  1. Створити модельний клас дуже просто , будь ласка, дотримуйтесь наведеної нижче процедури. Створіть швидкий клас з ім'ям RNCheckedModel, напишіть код, як показано нижче.
class RNCheckedModel: NSObject {

    var is_check = false
    var user_name = ""

    }
  1. створити свій клас комірок
class InviteCell: UITableViewCell {

    @IBOutlet var imgProfileImage: UIImageView!
    @IBOutlet var btnCheck: UIButton!
    @IBOutlet var lblName: UILabel!
    @IBOutlet var lblEmail: UILabel!
    }
  1. і, нарешті, використовувати клас моделі у своєму UIViewController, коли ви використовуєте свій UITableView .
    class 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()

    }

     }

1

Я використовував метод convertPoint, щоб отримати точку від перегляду таблиці і передати цю точку методу 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);
            }   
        }
    }

1

Спробуйте використати #selector для виклику IBaction

            cell.editButton.tag = indexPath.row
        cell.editButton.addTarget(self, action: #selector(editButtonPressed), for: .touchUpInside)

Таким чином ви можете отримати доступ до покажчика всередині методу editButtonPress

func editButtonPressed(_ sender: UIButton) {

print(sender.tag)//this value will be same as indexpath.row

}

Найбільш відповідна відповідь
Амаленду Кар

Ні, теги будуть вимкнені, коли користувач додає або видалить комірки.
koen

1

У моєму випадку у мене є декілька розділів, і індекс розділів і рядків є життєво важливим, тому в такому випадку я просто створив властивість на UIButton, який я встановив так, щоб цей індекс-шлях комірки:

fileprivate struct AssociatedKeys {
    static var index = 0
}

extension UIButton {

    var indexPath: IndexPath? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.index) as? IndexPath
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.index, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

Потім встановіть властивість у cellForRowAt так:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
    cell.button.indexPath = indexPath
}

Тоді в handleTapAction ви можете отримати indexPath таким чином:

@objc func handleTapAction(_ sender: UIButton) {
    self.selectedIndex = sender.indexPath

}

1

Свіфт 4 і 5

Спосіб 1 із використанням делегата протоколу

Наприклад, у вас є UITableViewCellім'я з ім'ямMyCell

class MyCell: UITableViewCell {
    
    var delegate:MyCellDelegate!
    
    @IBAction private func myAction(_ sender: UIButton){
        delegate.didPressButton(cell: self)
    }
}

Тепер створіть protocol

protocol MyCellDelegate {
    func didPressButton(cell: UITableViewCell)
}

Наступним кроком створіть розширення UITableView

extension UITableView {
    func returnIndexPath(cell: UITableViewCell) -> IndexPath?{
        guard let indexPath = self.indexPath(for: cell) else {
            return nil
        }
        return indexPath
    }
}

У вашій UIViewControllerреалізації протоколMyCellDelegate

class ViewController: UIViewController, MyCellDelegate {
     
    func didPressButton(cell: UITableViewCell) {
        if let indexpath = self.myTableView.returnIndexPath(cell: cell) {
              print(indexpath)
        }
    }
}

Спосіб 2 із застосуванням замикачів

В UIViewController

override func viewDidLoad() {
        super.viewDidLoad()
       //using the same `UITableView extension` get the IndexPath here
        didPressButton = { cell in
            if let indexpath = self.myTableView.returnIndexPath(cell: cell) {
                  print(indexpath)
            }
        }
    }
 var didPressButton: ((UITableViewCell) -> Void)

class MyCell: UITableViewCell {

    @IBAction private func myAction(_ sender: UIButton){
        didPressButton(self)
    }
}

Примітка: - якщо ви хочете отримати UICollectionViewindexPath, ви можете скористатися цим UICollectionView extensionі повторити описані вище кроки

extension UICollectionView {
    func returnIndexPath(cell: UICollectionViewCell) -> IndexPath?{
        guard let indexPath = self.indexPath(for: cell) else {
            return nil
        }
        return indexPath
    }
}

0

У 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)
}

Це не вийде. Нагляд за кнопкою не буде коміркою.
rmaddy

Це справді працює. Єдине, з чим слід бути обережним, так це те, що стеки поглядів у всіх різні. Це може бути sender.superview, sender.superview.superview або sender.superview.superview.superview. Але це працює дуже добре.
Шон

0

Іноді кнопка може знаходитися всередині іншого подання UITableViewCell. У цьому випадку superview.superview може не дати об'єкту комірки, а значить, indexPath буде нульовим.

У такому випадку нам слід продовжувати знаходити суперперегляд, поки не отримаємо об’єкт комірки.

Функція отримання об’єкта комірки шляхом суперперегляду

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
// CustomCell.swift

protocol CustomCellDelegate {
    func tapDeleteButton(at cell: CustomCell)
}

class CustomCell: UICollectionViewCell {
    
    var delegate: CustomCellDelegate?
    
    fileprivate let deleteButton: UIButton = {
        let button = UIButton(frame: .zero)
        button.setImage(UIImage(named: "delete"), for: .normal)
        button.addTarget(self, action: #selector(deleteButtonTapped(_:)), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    @objc fileprivate func deleteButtonTapped(_sender: UIButton) {
        delegate?.tapDeleteButton(at: self)
    }
    
}

//  ViewController.swift

extension ViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier, for: indexPath) as? CustomCell else {
            fatalError("Unexpected cell instead of CustomCell")
        }
        cell.delegate = self
        return cell
    }

}

extension ViewController: CustomCellDelegate {

    func tapDeleteButton(at cell: CustomCell) {
        // Here we get the indexPath of the cell what we tapped on.
        let indexPath = collectionView.indexPath(for: cell)
    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.