为TableView中的所有单元格设置标签的正确方法
我正在使用一个tableView
中的button,当我按下时,我得到的indexPath.row
。 但是,当单元格可以在屏幕上显示而不scroll
时,它才能正常工作。
一旦tableView可以滚动,我滚动tableview, indexPath.row
返回是一个错误的值,我注意到,最初设置20个对象,例如Check
只是打印9次没有20。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; lBtnWithAction = [[UIButton alloc] initWithFrame:CGRectMake(liLight1Xcord + 23, 10, liLight1Width + 5, liLight1Height + 25)]; lBtnWithAction.tag = ROW_BUTTON_ACTION; lBtnWithAction.titleLabel.font = luiFontCheckmark; lBtnWithAction.tintColor = [UIColor blackColor]; lBtnWithAction.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; [cell.contentView addSubview:lBtnWithAction]; } else { lBtnWithAction = (UIButton *)[cell.contentView viewWithTag:ROW_BUTTON_ACTION]; } //Set the tag lBtnWithAction.tag = indexPath.row; //Add the click event to the button inside a row [lBtnWithAction addTarget:self action:@selector(rowButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; //This is printed just 9 times (the the number of cells that are initially displayed in the screen with no scroll), when scrolling the other ones are printed NSLog(@"Check: %li", (long)indexPath.row); return cell; }
要点击索引来做些事情:
-(void)rowButtonClicked:(UIButton*)sender { NSLog(@"Pressed: %li", (long)sender.tag); }
Constants.h
#define ROW_BUTTON_ACTION 9
什么是正确的方式来获得indexPath.row
里面rowButtonClicked
或设置一个标签,当我有很多的单元格在我的tableView
?
我对这类问题的解决方法是不要以这种方式使用标签。 这是一个完全滥用标签(在我看来),并可能导致麻烦(正如你所发现的),因为细胞被重复使用。
通常情况下,解决的问题是:单元中的一块界面与用户交互(例如点击一个button),现在我们想要知道该单元当前对应的行是什么 ,以便我们可以对其进行响应到相应的数据模型。
我在我的应用程序中解决这个问题的方式是,当button被轻敲或任何我收到一个控制事件或委托事件,从这个界面(button或任何)走向视图层次结构,直到我来到单元格,然后调用表视图的indexPath(for:)
,它将一个单元格并返回相应的索引path。 控件事件或委托事件始终将接口对象作为参数包含在内,因此很容易从该对象到单元格以及从那里到该行。
因此,例如:
UIView* v = // sender, the interface object do { v = v.superview; } while (![v isKindOfClass: [UITableViewCell class]]); UITableViewCell* cell = (UITableViewCell*)v; NSIndexPath* ip = [self.tableView indexPathForCell:cell]; // and now we know the row (ip.row)
[ 注:一种可能的替代方法是使用一个自定义的单元格子类,其中有一个特殊的属性,将行存储在cellForRowAt
。 但是在我看来,这完全没有必要,因为indexPath(for:)
给了你完全相同的信息! 另一方面,对于页眉/页脚没有indexPath(for:)
,所以在这种情况下,我使用一个自定义的子类来存储段号,如本例所示 (参见viewForHeaderInSection
的实现)。]
我同意@matt,这不是一个很好的使用标签,但稍微不同意他的解决scheme。 除了直到find一个单元格之前,button的高级视图,我更喜欢获取button的原点,将其转换为表视图坐标,然后向表视图索取包含这些坐标的单元格的indexPath。
我希望苹果将添加一个函数indexPathForView(_:)
到UITableView。 这是一个普遍的需求,而且易于实施。 为此,下面是对UITableView的一个简单的扩展,它可以让你在表视图中查询位于tableView的单元格内的任何视图的indexPath。
在Objective-C和Swift中,下面是扩展的关键代码。 GitHub上有一个名为TableViewExtension-Obj-C的工作项目,演示了下面的表视图扩展的用法。
编辑
在Objective-C中:
头文件UITableView_indexPathForView.h:
#import <UIKit/UIKit.h> @interface UIView (indexPathForView) - (NSIndexPath *) indexPathForView: (UIView *) view; @end
UITableView_indexPathForView.m文件:
#import "UITableView_indexPathForView.h" @implementation UITableView (UITableView_indexPathForView) - (NSIndexPath *) indexPathForView: (UIView *) view { CGPoint origin = view.bounds.origin; CGPoint viewOrigin = [self convertPoint: origin fromView: view]; return [self indexPathForRowAtPoint: viewOrigin]; }
和button上的IBAction:
- (void) buttonTapped: (UIButton *) sender { NSIndexPath *indexPath = [self.tableView indexPathForView: sender]; NSLog(@"Button tapped at indexpPath [%ld-%ld]", (long)indexPath.section, (long)indexPath.row); }
在Swift中:
import UIKit public extension UITableView { func indexPathForView(_ view: UIView) -> IndexPath? { let origin = view.bounds.origin let viewOrigin = self.convert(origin, from: view) let indexPath = self.indexPathForRow(at: viewOrigin) return indexPath } }
我把它作为一个文件“UITableView + indexPathForView”添加到一个testing项目,以确保我得到的一切正确。 然后在IBAction中的一个单元格内的button:
func buttonTapped(_ button: UIButton) { let indexPath = self.tableView.indexPathForView(button) print("Button tapped at indexPath \(indexPath)") }
我在任何 UIView上做了扩展,而不仅仅是button,所以它更通用。
关于这个扩展的indexPathForView(_:)
是你可以把它放到任何项目中,并且把新的indexPathForView(_:)
函数添加到所有的表格视图中,而根本不需要改变你的其他代码。
您正在遇到细胞重复使用的问题。
当你为视图创build一个button时,你为它设置一个标签,但是你重写这个标签来设置它的行号。
当单元格被重用时,由于行号更长ROW_BUTTON_ACTION,因此不会将标记重置为正确的行号,从而导致错误。
使用标签从视图中获取信息几乎总是一个坏主意,而且非常脆弱,正如您在这里所看到的。
正如马特所说,走等级制是一个更好的主意。
而且,你的方法不需要这样写。 如果您创build自己的自定义单元格,则不需要用于创build和添加button和标签的代码,您可以在xib,故事板或甚至是类中的代码中执行该代码。 此外,如果使用采用索引path的出队方法,则将始终获得循环单元格或新创build的单元格,因此不需要检查返回的单元格是否为零。