如何在 uitableview for IOS 中使用可重用单元

Posted

技术标签:

【中文标题】如何在 uitableview for IOS 中使用可重用单元【英文标题】:How to use Reusable Cells in uitableview for IOS 【发布时间】:2014-08-12 06:16:08 【问题描述】:

好的。我似乎无法对 tableviews 的工作方式有一个坚定的理解。有人可以向我解释一下如何在表格视图中重用单元格,尤其是在滚动时?我对此的主要痛点之一是,当我在一个单元格中创建一个动作时,当我滚动时其他单元格会受到影响。我尝试使用数组作为模型的后端,但我仍然得到了不应该改变的单元格。很难弄清楚为什么当数组中的模型没有改变时它们会改变。

一个简单的例子:

带有“喜欢”按钮的表格视图单元格。当我单击其中一个单元格中的按钮时,按钮文本变为“不喜欢”(到目前为止一切都很好)。但是当我向下滚动时,即使我没有选择它们,其他单元格也会显示“不像”。当我向上滚动时,我最初选择的单元格会再次更改,并且更新的单元格也会更改。

我似乎无法弄清楚这一点。如果你能给我看一个工作示例源代码,那就太棒了!!!谢谢!

- (void)viewDidLoad

    [super viewDidLoad];


    likeState = [[NSMutableArray alloc]init];

    int i =0;
    for (i=0; i<20; i++) 
        [likeState addObject:[NSNumber numberWithInt:0]];
    
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [myButton setTitle:@"Like" forState:UIControlStateNormal];
    [myButton addTarget:self action:@selector(tapped:) forControlEvents:UIControlEventTouchUpInside];
    myButton.frame = CGRectMake(14.0, 10.0, 125.0, 25.0);
    myButton.tag =indexPath.row;
    [cell.contentView addSubview:myButton];

    if (cell ==nil) 


    

    if ([[likeState objectAtIndex:indexPath.row]boolValue]==NO) 
        [myButton setTitle:@"Like" forState:UIControlStateNormal];

    
    else
        [myButton setTitle:@"Unlike" forState:UIControlStateNormal];


    


    return cell;

-(void)tapped:(UIButton *)sender

[likeState replaceObjectAtIndex:sender.tag withObject:[NSNumber numberWithInt:1]];

    [sender setTitle:@"Unlike" forState:UIControlStateNormal];


【问题讨论】:

你能分享一些关于单元格创建、选择等的代码吗? 您的问题似乎在于-cellForRowAtIndexPath:-willDisplayingCell:forRowAtIndexPath:。分享你在其中的代码......还有一点点在你的backend array @Alladinian 我用代码更新了问题。我对if (cell ==nil) 下的内容更加困惑。让我知道你的想法。谢谢! @staticVoidMan 我用代码更新了问题。 @user3926564 : 你是通过 Storyboard 还是 xib 做的? 【参考方案1】:

我假设您是通过Storyboard 执行此操作的,并且由于您尚未通过Interface Builder 创建按钮,因此您需要检查正在重复使用的单元格是否已经具有该按钮。 根据您当前的逻辑,每次单元格重新出现时,您都会创建一个新的按钮实例。

我建议如下:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    //following is required when using XIB but not needed when using Storyboard
    /*
     if (cell == nil) 
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
     
     */
    //Reason:
    //[1] When using XIB, dequeueReusableCellWithIdentifier does NOT create a cell so (cell == nil) condition occurs
    //[2] When using Storyboard, dequeueReusableCellWithIdentifier DOES create a cell and so (cell == nil) condition never occurs

    //check if cell is being reused by checking if the button already exists in it
    UIButton *myButton = (UIButton *)[cell.contentView viewWithTag:100];

    if (myButton == nil) 
        myButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [myButton setFrame:CGRectMake(14.0,10.0,125.0,25.0)];
        [myButton setTag:100]; //the tag is what helps in the first step
        [myButton setTitle:@"Like" forState:UIControlStateNormal];
        [myButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
        [myButton addTarget:self action:@selector(tapped:andEvent:) forControlEvents:UIControlEventTouchUpInside];
        [cell.contentView addSubview:myButton];

        NSLog(@"Button created");
    
    else 
        NSLog(@"Button already created");
    

    if ([likeState[indexPath.row] boolValue]) 
        [myButton setTitle:@"Unlike" forState:UIControlStateNormal];
    
    else 
        [myButton setTitle:@"Like" forState:UIControlStateNormal];
    

    return cell;


-(void)tapped:(UIButton *)sender andEvent:(UIEvent *)event

    //get index
    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    CGPoint currentTouchPosition = [touch locationInView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:currentTouchPosition];

    //toggle "like" status
    if ([likeState[indexPath.row] boolValue]) 
        [likeState replaceObjectAtIndex:indexPath.row withObject:@(0)];
        [sender setTitle:@"Like" forState:UIControlStateNormal];
    
    else 
        [likeState replaceObjectAtIndex:indexPath.row withObject:@(1)];
        [sender setTitle:@"Unlike" forState:UIControlStateNormal];
    

【讨论】:

哦,我明白了。它现在似乎工作正常。我确实有一个问题是您实际上是在重复使用这些细胞还是每次都在制作一个新细胞?我问是因为你没有包括cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 并且想知道为什么?假设这就是启动重用单元格的方法。 @user3926564 :对不起,我想我应该提到一点-dequeueReusableCellWithIdentifier:。无论如何,这里... -dequeueReusableCellWithIdentifier: 将返回一个 CAN 可以重用的单元格。在XIB 中,当NO 单元格为reusable 时,它将返回nil,我们需要分配/初始化一个新单元格以供使用。就像在故事板设置中一样,在需要时会自动为我们创建一个单元格,因此我们不必手动分配/初始化单元格。 @user3926564 : 还有...[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 将创建一个新的单元格实例,并且 NOT 与重用逻辑本身有任何关系。重用逻辑位于-dequeueReusableCellWithIdentifier:。在 XIB 中,当可重用单元不存在时,我们手动分配/初始化它。一段时间后,if (cell == nil) 不会发生,所以这个 alloc/init 事情发生的次数与重用逻辑工作的次数一样多。 谢谢伙计。你的方法是最好的。我很感激。请原谅我的“菜鸟”。刚进ios @user3926564 : 嗯......一半的工作已经由你完成了。只需要绑几个松散的末端。但是,我建议您在掌握了以编程方式进行操作后,通过 IB(故事板)创建视图按钮/标签。无论如何...很高兴它对你有用:)【参考方案2】:

最大的问题是每次更新单元格时都要创建按钮。

例如,如果您在屏幕上看到 4 个鱼子,如下所示:

*-----------------------*
| cell A   with button  |
*-----------------------*
| cell B   with button  |
*-----------------------*
| cell C   with button  |
*-----------------------*
| cell D   with button  |
*-----------------------*

现在,当您向下滚动时,单元格 A 不再可见,它会被重复使用并放置在下面:

*-----------------------*
| cell B   with button  |
*-----------------------*
| cell C   with button  |
*-----------------------*
| cell D   with button  |
*-----------------------*
| cell A   with button  |
*-----------------------*

但对于单元格 A,它再次被称为 cellForRowAtIndexPath。 您所做的是在其上放置另一个按钮。所以你实际上有:

*-----------------------*
| cell B   with button  |
*-----------------------*
| cell C   with button  |
*-----------------------*
| cell D   with button  |
*-----------------------*
| cell A with 2 buttons |
*-----------------------*

您可以看到很快就会有很多按钮堆积起来。您可以按照@Timur Kuchkarov 的建议通过情节提要来解决此问题,或者您可以通过

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];



    if (cell ==nil) 
         UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [myButton setTitle:@"Like" forState:UIControlStateNormal];
        [myButton addTarget:self action:@selector(tapped:) forControlEvents:UIControlEventTouchUpInside];
        myButton.frame = CGRectMake(14.0, 10.0, 125.0, 25.0);
        myButton.tag = 10;
        [cell.contentView addSubview:myButton];

    

    UIButton * myButton = (UIButton * )[cell.contentView viewWithTag:10]


    if ([[likeState objectAtIndex:indexPath.row]boolValue]==NO) 
        [myButton setTitle:@"Like" forState:UIControlStateNormal];

    
    else
        [myButton setTitle:@"Unlike" forState:UIControlStateNormal];
    


    return cell;

如果单元格没有被重复使用(因此它上面什么都没有),那么您只需添加 1 个按钮。

这样你就不能依赖羊肉标签号来挖掘功能了,(反正我不会),所以你必须改变它。

这部分未经测试:

您可以检查按钮的父级以查看它所属的女巫单元。

UITableViewCell * cell = [[button superview] superview] /// superview of button is cell.contentView
NSIndexPath * indexPath = [yourTable indexPathForCell:cell];

【讨论】:

在调试器中你可以看到你的表likeState是否填对了。 感谢您的回复,但您的解决方法显示空表。有什么理由吗?谢谢! 嗯,你的意思是空的?没有按钮?尝试调试它是否在开始时进入 cell == nill 语句【参考方案3】:

要重新使用单元格,请将您的单元格标识符值放入界面生成器中。

【讨论】:

【参考方案4】:

首先,当您点击单元格按钮时,您总是在数组中设置@(1)([NSNumber numberWithInt:1],最好使用@(YES) 或@(NO))。这就是为什么你会看到不正确的结果。然后你总是在你的单元格中添加按钮,所以如果它被重复使用 10 次,你将在那里添加 10 个按钮。最好使用 xib 或故事板原型。

【讨论】:

【参考方案5】:

已接受答案的 Swift 2.1 版本。非常适合我。

        override func viewDidLoad() 
        super.viewDidLoad();

        self.arrayProducts = NSMutableArray(array: ["this", "that", "How", "What", "Where", "Whatnot"]); //array from api for example
        var i:Int = 0;
        for (i = 0; i<=self.arrayProducts.count; i++)
            let numb:NSNumber = NSNumber(bool: false);
            self.arrayFavState.addObject(numb); //array of bool values
            
        

TableView 数据源方法 ::

        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCellWithIdentifier("SubCategoryCellId", forIndexPath: indexPath) as! SubCategoryCell;


        cell.btnFavourite.addTarget(self, action: "btnFavouriteAction:", forControlEvents: UIControlEvents.TouchUpInside);

        var isFavourite: Bool!;
        isFavourite = self.arrayFavState[indexPath.row].boolValue;
        if isFavourite == true 
            cell.btnFavourite.setBackgroundImage(UIImage(named: "like-fill"), forState: UIControlState.Normal);
         else 
            cell.btnFavourite.setBackgroundImage(UIImage(named: "like"), forState: UIControlState.Normal);
        


        return cell;
    

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return self.arrayProducts.count;
    

按钮操作方法::

        func btnFavouriteAction(sender: UIButton!)

        let position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableProducts)
        let indexPath = self.tableProducts.indexPathForRowAtPoint(position)
        let cell: SubCategoryCell = self.tableProducts.cellForRowAtIndexPath(indexPath!) as! SubCategoryCell
        //print(indexPath?.row)
        //print("Favorite button tapped")
        if !sender.selected 
            cell.btnFavourite.setBackgroundImage(UIImage(named: "like-fill"), forState: UIControlState.Normal);
            sender.selected = true;
            self.arrayFavState.replaceObjectAtIndex((indexPath?.row)!, withObject:1);
         else 
            cell.btnFavourite.setBackgroundImage(UIImage(named: "like"), forState: UIControlState.Normal);
            sender.selected = false;
            self.arrayFavState.replaceObjectAtIndex((indexPath?.row)!, withObject:0);
        

    

【讨论】:

【参考方案6】:

我需要查看您的代码,但我可以说您没有更改您的模型,或者您更改了数组中的所有行。

您可以使用从 UITableViewCell 扩展的一些类,并在“cellForRowAtIndexPath”方法中将其用作可重用单元。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 
   MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell"];


    // Configure the cell...
    MyModel *model = [_models objectAtIndex:indexPath.row];
    cell.nameLabel.text = model.name;
    cell.image.image = model.image;
    cell.likeButton.text = model.likeButtonText;
    return cell;
  

然后在您的“didSelectRowAtIndexPath”方法中更改“model.likeButtonText”。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
   MyCell cell* =     [tableView cellForRowAtIndexPath:indexPath];
   MyModel *model = [_models objectAtIndex:indexPath.row];
   model.likeButtonText = [model.likeButtonText isEqual:@"like"]?@"unlike":@like;
   cell.likeButton.text = model.likeButtonText;

这将更新您的单元格

【讨论】:

感谢@Ali RP。我用代码更新了问题。我看到你做了什么,但 dedseleteRowIndexpath 是否适用于按钮点击?我虽然这仅用于当我点击单元格时?

以上是关于如何在 uitableview for IOS 中使用可重用单元的主要内容,如果未能解决你的问题,请参考以下文章

iOS - 允许在不编辑模式的情况下在“UITableView”中移动默认行

如何在iOS Obj-C中隐藏键盘触摸UITableView

如果用户在 ios 中点击 UITextfiled,我如何显示 UITableview

如何在 iOS 中从 UITableView 播放选定的媒体项?

如何在 UITableView 中添加搜索栏,就像 iOS 中的游戏中心一样?

如何检测 UITableView 仅在 iOS 中向下滚动?