Получение индексного пути ячейки таблицы для обновления кнопок в каждой ячейке [дубликат]

Вместо того, чтобы бросать код на вас, есть два понятия, которые являются ключом к пониманию того, как JS обрабатывает обратные вызовы и асинхронность. (это даже слово?)

Модель цикла события и параллелизма

Есть три вещи, о которых вам нужно знать; Очередь; цикл события и стек

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

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

Как только он получает сообщение для запуска чего-то, он добавляет его в очередь. Очередь - это список вещей, которые ждут выполнения (например, ваш запрос AJAX). Представьте себе это так:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

Когда одно из этих сообщений будет исполнено, оно выталкивает сообщение из очереди и создает стек, стек - это все, что нужно выполнить JS для выполнения инструкции в сообщение. Таким образом, в нашем примере ему говорят позвонить foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

. Так что все, что foobarFunc должно выполнить (в нашем случае anotherFunction), будет вставлено в стек. исполняемый, а затем забытый - цикл события затем переместится на следующую вещь в очереди (или прослушивает сообщения)

. Главное здесь - порядок выполнения. Это

КОГДА что-то будет запущено

Когда вы совершаете вызов с использованием AJAX для внешней стороны или выполняете любой асинхронный код (например, setTimeout), Javascript зависит от ответ, прежде чем он сможет продолжить.

Большой вопрос, когда он получит ответ? Ответ в том, что мы не знаем, поэтому цикл событий ждет, когда это сообщение скажет: «Эй, забери меня». Если JS просто ждал этого сообщения синхронно, ваше приложение замерзнет, ​​и оно сосать. Таким образом, JS продолжает выполнение следующего элемента в очереди, ожидая, пока сообщение не будет добавлено обратно в очередь.

Вот почему с асинхронной функциональностью мы используем вещи, называемые обратными вызовами. Это похоже на обещание буквально. Как и в I , обещание что-то вернуть в какой-то момент jQuery использует специальные обратные вызовы, называемые deffered.done deffered.fail и deffered.always (среди других). Вы можете увидеть их все здесь

Итак, вам нужно передать функцию, которая в какой-то момент будет выполнена с переданными ей данными.

Поскольку обратный вызов не выполняется немедленно, но в более позднее время важно передать ссылку на функцию, которую она не выполнила. поэтому

function foo(bla) {
  console.log(bla)
}

, поэтому большую часть времени (но не всегда) вы пройдете foo не foo()

. Надеюсь, это будет иметь смысл. Когда вы сталкиваетесь с такими вещами, которые кажутся запутанными, я настоятельно рекомендую полностью прочитать документацию, чтобы хотя бы понять ее. Это сделает вас намного лучшим разработчиком.

60
задан giorashc 22 February 2015 в 16:55
поделиться

12 ответов

Увидев предложение 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.
}
104
ответ дан Jacob King 28 August 2018 в 04:41
поделиться

Поскольку отправитель обработчика событий является самой кнопкой, я бы использовал свойство кнопки tag для хранения индекса, инициализированного в cellForRowAtIndexPath.

Но с немного большей работой я «Совершенно иначе. Если вы используете пользовательскую ячейку, я как-то подхожу к проблеме:

  • добавить свойство 'indexPath` в пользовательскую ячейку таблицы
  • , инициализировать ее в cellForRowAtIndexPath
  • переместите обработчик перехода из контроллера представления в реализацию ячейки
  • , используя шаблон делегирования, чтобы уведомить диспетчер представлений о событии крана, передав путь указателя
3
ответ дан Antonio 28 August 2018 в 04:41
поделиться

Я использовал метод 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 28 August 2018 в 04:41
поделиться

Используйте расширение для 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, когда он содержит ячейку, перерабатывается.)

EDIT:

Вы можете загрузить рабочий демонстрационный проект, который использует указанное выше расширение из Github: TableViewExtension.git

9
ответ дан Duncan C 28 August 2018 в 04:41
поделиться

UPDATE: получение индексной ячейки ячейки, содержащей кнопку (оба раздела и строка):

Использование позиции кнопки

Внутри вашего метода buttonTapped вы можете захватить кнопки, преобразуйте его в координату в таблицеView, затем получите 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)

Предыдущий ответ: использование свойства Tag (только возвращает строку)

Вместо поднимаясь в деревья супервизора, чтобы захватить указатель на ячейку, которая содержит 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
}

Я предпочитаю этот метод, так как я обнаружил, что поворот в деревьях супервизора может быть рискованным способом разработки приложения. Кроме того, для object-C я использовал эту технику в прошлом и был доволен результатом.

37
ответ дан Hardik Thakkar 28 August 2018 в 04:41
поделиться

В 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
        }
}
0
ответ дан Joji Thomas Eapen 28 August 2018 в 04:41
поделиться

Мой подход к этой проблеме заключается в использовании протокола делегата между ячейкой и табличным представлением. Это позволяет вам удерживать обработчик кнопки в подклассе ячейки, что позволяет назначить обработчик действия касания к ячейке прототипа в Interface Builder, сохраняя при этом логику обработчика кнопок в контроллере представления.

Он также избегает потенциально хрупкого подхода к навигации по иерархии представлений или использованию свойства tag, у которого возникают проблемы при изменении индексов ячеек (в результате вставки, удаления или переупорядочения)

CellSubclass.swift

protocol CellSubclassDelegate: class {
    func buttonTapped(cell: CellSubclass)
}

class CellSubclass: UITableViewCell {

@IBOutlet var someButton: UIButton!

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)")
    }
} 
36
ответ дан Paulw11 28 August 2018 в 04:41
поделиться

Swift 4 Решение:

У вас есть кнопка (myButton) или любое другое представление в ячейке. Назначить тег в cellForRowAt, как это

cell.myButton.tag = indexPath.row

Теперь у вас tapFunction или любой другой. Выполните его так, как это, и сохраните его в локальной переменной.

currentCellNumber = (sender.view?.tag)!

После этого вы можете использовать в любом месте этот currentCellNumber, чтобы получить indexPath.row выбранной кнопки.

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

2
ответ дан Sajid Zeb 28 August 2018 в 04:41
поделиться

В 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 28 August 2018 в 04:41
поделиться

Для Swift2.1

я нашел способ сделать это, надеюсь, это поможет.

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

    guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
        fatalError("can't find point in tableView")
    }
7
ответ дан specialvict 28 August 2018 в 04:41
поделиться

Иногда кнопка может находиться внутри другого представления UITableViewCell. В этом случае superiew.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)
}
1
ответ дан Teena nath Paul 28 August 2018 в 04:41
поделиться

Попробуйте использовать #selector для вызова IBaction.In cellforrowatindexpath

            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

}
0
ответ дан Vineeth Krishnan 28 August 2018 в 04:41
поделиться