正确的方法将标签设置为TableView中的所有单元格

时间:2016-12-23 17:49:54

标签: ios objective-c uitableview

我在tableView内使用了一个按钮,按下按钮时我会得到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中有很多单元格时,rowButtonClickedtableView内或设置标记的正确方法是什么?

3 个答案:

答案 0 :(得分:7)

我对这类问题的解决方法是不要以这种方式使用标签。这是对标签的完全滥用(在我看来),并且很可能会引发麻烦(正如您所发现的那样),因为细胞会被重复使用。

通常,要解决的问题是:单元格中的一个界面与用户进行交互(例如,按下按钮),现在我们想知道哪一行该单元格目前对应,以便我们可以响应相应的数据模型。

我在我的应用程序中解决这个问题的方法是,当点击按钮或其他任何东西并从中接收控制事件或委托事件时,从界面的那一块(按钮或其他)向上走视图层次结构直到我来到单元格,然后调用表视图的indexPath(for:),它取一个单元格并返回相应的索引路径。控制事件或委托事件始终将接口对象作为参数包含在内,因此很容易从该对象到单元格,从那里到行。

因此,例如:

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:),所以在这种情况下我使用存储节号的自定义子类,如在this example中(参见viewForHeaderInSection的实现)。]

答案 1 :(得分:4)

我同意@matt认为这不是一个很好用的标签,但对他的解决方案略有不同意见。在找到单元格之前,我没有走按钮的超视图,而是选择按钮的原点,将其转换为表格视图坐标,然后向表格视图询问包含单元格的indexPath那些坐标。

我希望Apple为UITableView添加一个函数require。这是一个普遍的需求,并且易于实施。为此,这里是UITableView的一个简单扩展,它允许您向表视图询问位于其中一个tableView的单元格内的任何视图的indexPath。

以下是Objective-C和Swift中扩展的关键代码。 GitHub上有一个名为TableViewExtension-Obj-C的工作项目,它说明了下面表格视图扩展的用法。

修改

在Objective-C中:

头文件UITableView_indexPathForView.h:

indexPathForView(_:)

UITableView_indexPathForView.m文件:

#import <UIKit/UIKit.h>
@interface UIView (indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view;
@end

按钮上的IBAction:

#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];
}

在Swift中:

- (void) buttonTapped: (UIButton *) sender {
  NSIndexPath *indexPath = [self.tableView indexPathForView: sender];
  NSLog(@"Button tapped at indexpPath [%ld-%ld]",
        (long)indexPath.section,
        (long)indexPath.row);
}

我将其添加为文件&#34; UITableView + indexPathForView&#34;一个测试项目,以确保我得到一切正确。然后在IBAction中找到一个单元格内的按钮:

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

我将扩展程序设置为任何 UIView,而不仅仅是按钮,以便它更具通用性。

这个扩展的好处在于你可以将它放到任何项目中,它会将新的func buttonTapped(_ button: UIButton) { let indexPath = self.tableView.indexPathForView(button) print("Button tapped at indexPath \(indexPath)") } 函数添加到你的所有表视图中,而不必改变你的其他代码。

答案 2 :(得分:2)

您正在遇到细胞重用问题。

为视图创建按钮时,您可以为其设置标记,但是您可以覆盖此标记以将行号设置为该标记。

当重复使用单元格时,因为行号较长ROW_BUTTON_ACTION,您不会将标记重置为正确的行号,并且出现问题。

使用标记从视图中获取信息几乎总是一个坏主意,并且非常脆弱,正如您在此处所见。

正如马特已经说过的那样,走在等级制度是一个更好的主意。

此外,您的方法不需要以这种方式编写。如果您创建自己的自定义单元格,则不需要用于创建和添加按钮和标签的代码,您可以在xib,故事板或甚至类中的代码中执行此操作。此外,如果您使用采用索引路径的dequeue方法,您将始终获得一个回收的单元格或新创建的单元格,因此无需检查返回的单元格是否为零。