Вместо того, чтобы бросать код на вас, есть два понятия, которые являются ключом к пониманию того, как 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()
. Надеюсь, это будет иметь смысл. Когда вы сталкиваетесь с такими вещами, которые кажутся запутанными, я настоятельно рекомендую полностью прочитать документацию, чтобы хотя бы понять ее. Это сделает вас намного лучшим разработчиком.
Увидев предложение 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.
}
Поскольку отправитель обработчика событий является самой кнопкой, я бы использовал свойство кнопки tag
для хранения индекса, инициализированного в cellForRowAtIndexPath
.
Но с немного большей работой я «Совершенно иначе. Если вы используете пользовательскую ячейку, я как-то подхожу к проблеме:
cellForRowAtIndexPath
Я использовал метод 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);
}
}
}
@ Ответ 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
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 я использовал эту технику в прошлом и был доволен результатом.
В 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
}
}
Мой подход к этой проблеме заключается в использовании протокола делегата между ячейкой и табличным представлением. Это позволяет вам удерживать обработчик кнопки в подклассе ячейки, что позволяет назначить обработчик действия касания к ячейке прототипа в 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)")
}
}
Swift 4 Решение:
blockquote>У вас есть кнопка (myButton) или любое другое представление в ячейке. Назначить тег в cellForRowAt, как это
cell.myButton.tag = indexPath.row
Теперь у вас tapFunction или любой другой. Выполните его так, как это, и сохраните его в локальной переменной.
currentCellNumber = (sender.view?.tag)!
После этого вы можете использовать в любом месте этот currentCellNumber, чтобы получить indexPath.row выбранной кнопки.
Наслаждайтесь!
В 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)
}
Для Swift2.1
я нашел способ сделать это, надеюсь, это поможет.
let point = tableView.convertPoint(CGPoint.zero, fromView: sender)
guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
fatalError("can't find point in tableView")
}
Иногда кнопка может находиться внутри другого представления 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)
}
Попробуйте использовать #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
}