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的索引初始化当前单元格,谢谢这修复了我的问题:)