Как узнать номер строки UITableview

У меня есть UITableViewCell с UISwitch как аксессуар для каждой ячейки. Когда я изменяю значение переключателя в ячейке, как я могу узнать, в какой строке находится переключатель? Мне нужен номер строки в событии изменения значения переключателя.


person Jean Paul Scott    schedule 14.02.2012    source источник
comment
Вы можете создать подкласс переключателя, добавить индекс свойства и во время создания ячейки (и переключателя) установить для этого свойства текущий индекс ячейки. Нажав переключатель, вы можете прочитать это свойство, и это ваш индекс ячейки.   -  person TRD    schedule 14.02.2012
comment
@TRD Вам придется обновлять свойство index каждый раз, когда вы возвращаете ячейку из tableView:cellForRowAtIndexPath:, потому что ячейку можно повторно использовать для разных строк.   -  person rob mayoff    schedule 14.02.2012
comment
проверьте эту ссылку: stackoverflow.com/a/2562367/845115   -  person Mudit Bajpai    schedule 14.02.2012


Ответы (10)


Теги, подклассы или навигация по иерархии представлений - это слишком много работы!. Сделайте это в своем методе действий:

CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.tableView]; 
NSIndexPath *hitIndex = [self.tableView indexPathForRowAtPoint:hitPoint];

Работает с любым типом представления, многосекционными таблицами, всем, что вы можете в него вложить - пока источник вашего отправителя находится в пределах фрейма ячейки (спасибо, rob!), Что обычно и бывает.

И вот оно в расширении UITableView Swift:

extension UITableView {
    func indexPath(for view: UIView) -> IndexPath? {
        let location = view.convert(CGPoint.zero, to: self)
        return self.indexPathForRow(at: location)
    }
}
person jrturton    schedule 14.02.2012
comment
@GajendraKChauhan - как сказано в ответе, в вашем методе действий. Под этим я подразумеваю тот метод, который вызывается при нажатии кнопки в вашей ячейке, где сама кнопка передается как sender - person jrturton; 25.06.2013
comment
@ Zac24 отправителем является любая кнопка или элемент управления, связанный с методом действия. Этот код не входит в cellForRowAtIndexPath, он переходит в метод действия с помощью кнопки или переключателя или чего-то еще, что добавлено в ячейку. - person jrturton; 14.10.2013
comment
Я пытаюсь добавить расширение Swift и получаю сообщение: «Декларация действительна только в области файлов». Что-то мне не хватает? - person deebs; 02.12.2014
comment
@deebs Вы должны добавить расширение на верхнем уровне файла, например не внутри другого класса или чего-то еще - person jrturton; 02.12.2014
comment
Я получаю сообщение об ошибке: terminating with uncaught exception of type NSException Я знаю, что не использую быстрое расширение правильно. Если кто-то может внести свой вклад в расширение этого ответа и добавить, как его использовать, сделайте это. - person Jesus Rodriguez; 29.01.2016
comment
Можем ли мы использовать [sender center] вместо CGPointZero? Также не забудьте задействовать какие-либо подпредставления, sender может быть обернут в вашем convertTo: (в моем случае кнопка находится в скользящем UIView меньшего размера, чем родительская ячейка). - person David Jirman; 06.04.2016
comment
@DavidJirman Center находится в неправильном координатном пространстве, а иерархия представлений уже учтена методом convert. - person jrturton; 06.04.2016
comment
@jrturton Понятно, в конце концов я делал то же самое, но более сложным образом. Спасибо за ответ! - person David Jirman; 06.04.2016
comment
Я думаю, что этот ответ приемлем, но если мы действительно измеряем слишком много работы, то несколько раундов цикла while, проходящего по списку супервизора, являются выигрышным подходом. Учитывайте количество сравнений, происходящих в HitTest (всей иерархии ячейки таблицы), не говоря уже об исходном преобразовании координатного пространства. Не уверен, почему некоторые на SO выступают против зацикливания супервизоров. Это безопасно сейчас и в будущем. - person danh; 10.01.2017
comment
@danh, это не тестирование попаданий, слишком много работы связано с объемом кода, а наблюдение за ходьбой небезопасно, поскольку иерархия меняется с каждым выпуском iOS. - person jrturton; 10.01.2017
comment
О работе: поиск листового представления в иерархии под CGPoint - это хит-тест. Я подозреваю, что после нахождения подпредставления код indexPathAtPoint табличного представления затем просматривает супервизоры, пока не найдет ячейку. О безопасности в будущем: ожидаете ли вы когда-нибудь релиза, когда представления в ячейках tableview перестанут быть потомками ячеек tableview? Это почти тавтологично. - person danh; 10.01.2017
comment
Я только что заметил здесь ваше обсуждение с @robmayoff и его ответ. Итак, вы уже были на этой территории раньше. В любом случае, я согласен с тем, что Apple могла бы исправить это с помощью четко названного метода tableview и оставила неоднозначное, как приложения должны решать эту общую проблему. - person danh; 10.01.2017
comment
@danh да, правильная поддержка SDK была бы прекрасна. Основной причиной моего ответа было то, что я видел слишком много кода, который просто пошелcell = button.superview.superview, а теги бесполезны и должны погибнуть в огне. - person jrturton; 10.01.2017

Если вы установите для свойства tag номер строки (как предлагается в других ответах), вам придется обновлять его каждый раз в tableView:cellForRowAtIndexPath: (поскольку ячейку можно повторно использовать для разных строк).

Вместо этого, когда вам нужен номер строки, вы можете пройти вверх по цепочке superview от UISwitch (или любого другого представления) к UITableViewCell, а затем к UITableView, и запросить у табличного представления путь индекса ячейки:

static NSIndexPath *indexPathForView(UIView *view) {
    while (view && ![view isKindOfClass:[UITableViewCell class]])
        view = view.superview;
    if (!view)
        return nil;
    UITableViewCell *cell = (UITableViewCell *)view;
    while (view && ![view isKindOfClass:[UITableView class]])
        view = view.superview;
    if (!view)
        return nil;
    UITableView *tableView = (UITableView *)view;
    return [tableView indexPathForCell:cell];
}

В tableView:cellForRowAtIndexPath: для этого ничего не требуется.

person rob mayoff    schedule 14.02.2012
comment
Я согласен насчет тегов, но ваше решение кажется немного запутанным. Видите ли вы недостатки в предлагаемом мной методе? Кажется, существует так много уродливых уловок для решения этой распространенной проблемы, и я очень доволен своим, но я не вижу, чтобы он использовался где-либо еще! - person jrturton; 14.02.2012
comment
@jrturton Это интересный подход. Он потерпит неудачу, если происхождение UISwitch окажется за пределами его ячейки, что, по общему признанию, маловероятно. - person rob mayoff; 14.02.2012
comment
Да, в таком случае было бы сложно постучать! - person jrturton; 14.02.2012
comment
Если исходная точка находится в (-1,0), большая ее часть все еще видна и доступна для прикосновения. - person rob mayoff; 14.02.2012
comment
Я думаю, что приведенный здесь код отпугивает потенциальных покупателей вторым циклом, который ищет представление таблицы. Вызывающий лучше знает представление таблицы, когда он звонит, иначе он застрянет в недоумении, к какой таблице он должен применить полученный путь. Это отличное расширение для просмотра таблиц. Я думаю, что этот подход здесь ошибочно дискредитировали как небезопасный по отношению к изменениям SDK. Это так же безопасно, как и все в UIKit, IMO - и (возможно, хотя, вероятно, тривиально, так что кого это волнует) меньше вычислений, чем принятое решение. (Я добавил ответ, чтобы выразить свою точку зрения) - person danh; 10.01.2017

в cellForRowAtIndexPath: установите для свойства tag вашего элемента управления значение indexPath.row

person wattson12    schedule 14.02.2012
comment
Это работает, когда вам нужно знать только строку или раздел, но становится сложнее, если вам нужны оба - person simon_smiley; 05.01.2016
comment
НЕТ. тег не будет обновляться при вставке или удалении строк - person Isaak Osipovich Dunayevsky; 30.11.2016

Принятое решение - хитрый прием.

Однако зачем нам использовать хитпоинт, если мы можем использовать уже доступное свойство tag в UIView? Можно сказать, что тег может хранить только строку или раздел, поскольку это единственный Int.

Что ж ... Не забывайте свои корни, ребята (CS101). Один Int может хранить два целых числа вдвое меньшего размера. А вот расширение для этого:

extension Int {

    public init(indexPath: IndexPath) {
        var marshalledInt: UInt32 = 0xffffffff

        let rowPiece = UInt16(indexPath.row)
        let sectionPiece = UInt16(indexPath.section)
        marshalledInt = marshalledInt & (UInt32(rowPiece) << 16)
        marshalledInt = marshalledInt + UInt32(sectionPiece)

        self.init(bitPattern: UInt(marshalledInt))
    }

    var indexPathRepresentation: IndexPath {
        let section = self & 0x0000ffff

        let pattern: UInt32 = 0xffff0000
        let row = (UInt32(self) & pattern) >> 16
        return IndexPath(row: Int(row), section: Int(section))
    }
}

Затем в вашем tableView(_:, cellForRowAt:) вы можете:

cell.yourSwitch.tag = Int(indexPath: indexPath)

А затем в обработчике действий вы можете:

func didToogle(sender: UISwitch){
    print(sender.tag.indexPathRepresentation)
}

Однако обратите внимание на это ограничение: строка и раздел не должны быть больше 65535. (UInt16.max)

Я сомневаюсь, что ваши индексы tableView будут такими высокими, но если они это сделают, бросьте вызов себе и реализуйте более эффективную схему упаковки. Скажем, если у нас есть очень маленький раздел, нам не нужны все 16 бит для представления раздела. У нас может быть наш int-макет, например:

{section area length}{all remaining}[4 BITS: section area length - 1]

то есть наши 4 LSB указывают длину области раздела - 1, при условии, что мы выделяем как минимум 1 бит для раздела. Таким образом, если наш раздел равен 0, строка может занимать до 27 бит ([1] [27] [4]), чего определенно должно быть достаточно.

person ambientlight    schedule 01.07.2017

Я предпочитаю использовать subviews, если вы знаете свой макет, он, как правило, очень простой и короткий в 1 строку ...

    NSIndexPath *indexPath = [tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview]];

То есть, если он более вложен, добавьте больше супервизоров.

Немного больше информации:

все, что вы делаете, - это запрос родительского представления и его родительского представления, которым является ячейка. Затем вы запрашиваете у своего tableview путь к индексу только что полученной ячейки.

person Andres Canella    schedule 26.01.2013
comment
Это, безусловно, лучшее решение. Я видел, как другие решения не работают при переработке ячеек - person Alejandro Luengo; 09.08.2013
comment
В XCode 5 / SDK7 с использованием Interface Builder для создания ячеек (в качестве прототипов) вы сталкиваетесь с проблемой, заключающейся в том, что на устройствах iOS6 и iOS7 встроенный элемент управления вложен на разных уровнях подпредставления. Шутки в сторону. Я делал это таким образом, но мне пришлось перейти на хакинг позиции попадания. - person jimkberry; 09.10.2013

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

Например, в tableView:cellForRowAtIndexPath: установите для свойства tag переключателя значение indexPath.row, а в своем методе действия вы можете получить тег от отправителя.

Лично мне такой подход не нравится, и я предпочитаю создать подкласс от UITableViewCell. Кроме того, может быть хорошей идеей добавить «смещение» к тегу, чтобы предотвратить любые конфликты с тегами других представлений.

person Daniel Rinser    schedule 14.02.2012

Принятый ответ на этот пост совершенно нормален. Я хотел бы предложить читателям, что следующее, заимствованное из @robmayoff в этом посте, также совершенно нормально:

- (NSIndexPath *)indexPathForView:(UIView *)view inTableView:(UITableView *)tableView {
    while (view && ![view isKindOfClass:[UITableViewCell class]])
        view = view.superview;
    UITableViewCell *cell = (UITableViewCell *)view;
    return [tableView indexPathForCell:cell];
}

Некоторые утверждали, что этот подход содержит слишком много вычислительной работы из-за цикла while. Альтернатива, преобразование источника представления в координатное пространство табличного представления и вызов indexPathForRowAtPoint:, скрывает еще больше работы.

Некоторые утверждали, что этот подход небезопасен по отношению к возможным изменениям SDK. Фактически, Apple уже однажды изменила иерархию ячеек tableview, добавив contentView к ячейке. Этот подход работает до и после такого изменения. Пока предки представлений могут быть найдены через цепочку супервизоров (что так же важно, как и все в UIKit), это хороший код.

person danh    schedule 10.01.2017
comment
У некоторых аллергия на циклы while: D - person jrturton; 10.01.2017
comment
@jrturton, от этого ты, наверное, чихаешь еще хуже: for (; view && ![view isKindOfClass:[UITableViewCell class]]; view = view.superview) - person danh; 10.01.2017
comment
бежит к холмам - person jrturton; 10.01.2017

Коллега предложил следующее, которое я сделал в категории UITableView:

+(UITableViewCell*)findParentCellForSubview:(UIView*)view
{
    while (([view isKindOfClass:[UITableViewCell class]] == NO) && ([view superview] != nil))
        view = [view superview];

    if ([view superview] != nil)
        return (UITableViewCell*)view;

    return nil;
}

По-прежнему хакерски - но работает.

person jimkberry    schedule 08.10.2013

Еще один вариант использования superView. Работает как категория для UIView.

- (UITableViewCell *)superCell
{
    if (!self.superview) {
        return nil;
    }

    if ([self.superview isKindOfClass:[UITableViewCell class]]) {
        return (UITableViewCell *)self.superview;
    }

    return [self.superview superCell];
}
person serj    schedule 15.05.2014

я не знаю о нескольких разделах, но могу дать вам по одному разделу ...

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index=indexPath.row;
NSString *string=[[NSString alloc]initWithFormat:@"%ld",(long)index];
}

отсюда вы можете получить номер строки и сохранить его в строке ....

person Smit    schedule 28.09.2014