Ios UITableView滚动时的奇怪行为

Ios UITableView滚动时的奇怪行为,ios,cocoa-touch,uitableview,Ios,Cocoa Touch,Uitableview,我创建了一个UITableView,其中包含显示用户的单元格。每个单元格都添加到此方法中-tableView:cellforrowatinexpath:。每个单元格都有链接到特定用户的内容,如UIImageView和UILabel 只要显示的单元格不超过9-10个,UITableView就可以正常工作。但是当单元格数量增加时,用户必须向下滚动才能查看所有单元格,这时奇怪的行为就开始了。来自第一、第二、第三等的内容被添加到第十一、十二、十三等单元格中。当用户向上滚动时,应该在数字11、12、13上

我创建了一个
UITableView
,其中包含显示
用户的单元格。每个单元格都添加到此方法中
-tableView:cellforrowatinexpath:
。每个单元格都有链接到特定用户的内容,如
UIImageView
UILabel

只要显示的单元格不超过9-10个,
UITableView
就可以正常工作。但是当单元格数量增加时,用户必须向下滚动才能查看所有单元格,这时奇怪的行为就开始了。来自第一、第二、第三等的内容被添加到第十一、十二、十三等单元格中。当用户向上滚动时,应该在数字11、12、13上的内容现在在第一、第二和第三个单元格中

我希望有人能理解我的问题,知道这里出了什么问题

下面是我向用户添加单元格的代码。。尽管忽略了解析的内容,但我认为这并不相关

- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = @"SimpleTableCell";

UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:simpleTableIdentifier];

if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];

    if (tableview == commentViewTableView) {
        //Ignore this
    } else if (tableview == tableView) {
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, 34, 34)];
        imageView.contentMode = UIViewContentModeScaleAspectFill;
        imageView.clipsToBounds = YES;
        [cell addSubview:imageView];

        UILabel *usernameLabel = [[UILabel alloc] initWithFrame:CGRectMake(44, 0, 160, 44)];
        usernameLabel.textAlignment = NSTextAlignmentLeft;
        usernameLabel.font = [UIFont systemFontOfSize:17];
        usernameLabel.backgroundColor = [UIColor clearColor];
        [cell addSubview:usernameLabel];


        UIImageView *hitImageView = [[UIImageView alloc] initWithFrame:CGRectMake(245, 9.5, 25, 25)];
        hitImageView.contentMode = UIViewContentModeScaleAspectFill;
        hitImageView.clipsToBounds = YES;
        hitImageView.image = [UIImage imageNamed:@"hit.png"];
        [cell addSubview:hitImageView];

        NSString *key = //Code to retrieve userKey

        PFQuery *query = [PFUser query];
        [query whereKey:@"objectId" equalTo:key];
        [query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
            if (!error) {
                [[object objectForKey:@"image1"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
                        if (!error) {

                            NSString *ageString = [[NSString alloc] initWithFormat:@"%li", (long)age];


                            imageView.image = [UIImage imageWithData:data];
                            usernameLabel.text = [NSString stringWithFormat:@"%@, %@", [object objectForKey:@"username"], ageString];

                        }
                    }];
            }
        }];
    }
}

return cell;

}

如下更改代码:

- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath {
   static NSString *simpleTableIdentifier = @"SimpleTableCell";

   UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:simpleTableIdentifier];

   if (cell == nil) {
      cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
   } // CLOSED PARANTHESES HERE!!!!

   if (tableview == commentViewTableView) {
       //Ignore this
   } else if (tableview == tableView) {
      // ... rest of your code here
   }
 }
@{@"user": aPFUser, @"image": aUIImage };

代码有几个问题。一是必须特别注意CellForRowatinex:datasource方法内部的异步调用。另一个原因是单元是重用的,因此在每次进入视图时向它们添加子视图将在子视图上堆积子视图

让我们从异步操作开始@恩伯克正确地指出了这个问题,但说你“做不到”则言过其实。您可以预加载所有内容,但用户必须等待整个表准备就绪,然后才能看到任何内容。这里的一个好策略是延迟加载

延迟加载取决于缓存加载结果的位置。因此,让我们将数据源数组设置为一组可变字典,如下所示:

- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath {
   static NSString *simpleTableIdentifier = @"SimpleTableCell";

   UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:simpleTableIdentifier];

   if (cell == nil) {
      cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
   } // CLOSED PARANTHESES HERE!!!!

   if (tableview == commentViewTableView) {
       //Ignore this
   } else if (tableview == tableView) {
      // ... rest of your code here
   }
 }
@{@"user": aPFUser, @"image": aUIImage };
对用户进行预回迁是有意义的,否则,您甚至不知道自己拥有多少用户,因此,在视图中将显示:

// setup your model as @property(strong,nonatomic) NSMutableArray *users;
PFQuery *query = [PFUser query];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    if (!error) {
        // build the datasource
        self.users = [NSMutableArray array];
        for (PFUser *user in objects) {
            NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:
                @{ @"user": user };
            ];
        }
    [self.tableView reloadData];
    }
}];
现在,在cellForRowAtIndexPath中执行以下操作:

NSMutableDictionary *userDictionary = self.users[indexPath.row];

// in the lazy pattern, if the model is initialized, we're done
// start by assuming the best
imageView.image = userDictionary[@"image"];

// but the image might be nil (it will start out that way) so, load...
PFQuery *query = [PFUser query];
[query whereKey:@"objectId" equalTo:key];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
    if (!error) {
        [[object objectForKey:@"image1"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
            if (!error) {
                UIImage *image = [UIImage imageWithData:data];

                // this is the important part:  this code doesn't run when the rest
                // of the method runs.  It happens later, when the request completes
                // so don't assume anything about the state of the table.  Instead
                // treat the table like you would in other view controller methods
                userDictionary[@"image"] = image;
                // don't fool around with cell (it might be reused).  Instead 
                // just tell the table to reload this row
                [tableView reloadRowsAtIndexPaths:@[indexPath]
                                 withRowAnimation:UITableViewRowAnimationAutomatic];
            }
        }];
    }
}];
下次该行滚动到视图中时,异步请求中的数据将缓存在用户字典中

问题二更简单:代码无条件地构建子视图,即使(重用的)单元已经具有该子视图。答案是,懒惰是你的朋友。尝试从单元格中获取子视图,并且仅在必须时生成它

// change all of your subview-building code to do this:
UIImageView *imageView = (UIImageView *)[cell viewWithTag:32];
if (!imageView) {
   imageView = [[UIImageView alloc] init....
   // same code as you had here, adding...
   imageView.tag = 32;
}
// and so on for the cell's other subviews.  be sure to advance the tag (33, 34, etc)
总之,CellForRowatineXpath有几个部分

  • 退出该单元
  • 如上所述的延迟构建子视图
  • 如上所述:访问模型并乐观地初始化模型中的子视图
  • 如果缺少部分模型,请执行异步调用,更新模型, 完成后重新加载单元格

我通过将单元格标识符更改为唯一来解决问题。我不知道这是否真的是一种方法,或者这是否是一种好的做法,但当我这样做的时候,我的问题就解决了。所以,如果有一些反馈,知道这是否会导致我可能遗漏的任何其他问题,这将是一件好事

NSString *identifier = [NSString stringWithFormat:@"Cell%li", indexPath.row];
UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:identifier];

if (cell == nil) {
    //My code..
}

你能把密码寄出去吗?尤其是
tableView:cellforrowatinexpath:
对于不了解单元重用的人来说,这是一个极其常见的问题。由于滚动时会重复使用单元格,因此需要在CellForRowatineXpath中设置所有内容。我使用代码进行了更新,只需忽略Parse.com的内容,这不相关,我认为您已将所有代码放在if(cell==nil)子句中,因此滚动时不会更新内容(因为单元格不再为nil)但是当我把它放在
if(cell==nil)
下面时,当我滚动
UITableView
时,它会在原始内容之上添加新内容。因此,单元格包含双层、三层等内容。我尝试了这一点,现在当我滚动
UITableView
时,它会在原始内容的顶部添加新内容。因此,单元格包含双层、三层等内容。我坚信这是因为这样的比较:
tableview==commentViewTableView
。你在干什么?您应该知道Objective-C中的对象相等性是通过:[tableView isEqual:commentViewTableView]进行测试的,在这里进行这种比较似乎很奇怪。。。你真的需要这个吗?也许你应该考虑改变你的一些设置,如果是这样,因为这是一个非常常见的模式…我试图删除它,但仍然是相同的问题…所以我不认为这是因为这种比较。啊,好吧,现在我明白问题了。这是因为您在
cellforrowatinedexpath
中调用
getFirstObjectInBackgroundWithBlock:
,它将块作为回调,在查询完成后执行。您不能在
cellforrowatinedexpath
中执行此类异步操作。您应该在初始化视图时加载所需的数据,然后使用
indexPath.row的索引初始化当前单元格,谢谢这修复了我的问题:)