将 TableView 与许多自定义单元格和自动布局一起使用
Using TableView with many custom Cells and autolayout
我想重构一些代码以获得更好的性能,但我的问题是我不确定如何去做。目前我有一个 UIViewController
上面有一个 UIScrollView
。
我还有 20 个不同的视图(每个视图都有自己的 .h 和 .m 文件)可以完全动态地放置在我的 UIScrollView
上。每次我启动 UIViewController
时,我都会向我的服务器发送一个请求,然后我得到响应,然后我知道我必须在 UIScrollView
上放置多少视图。
所以你可以想象,当我的 UIScrollView
上有很多不同的视图时,在用户最终可以与它们交互之前,所有视图都已完全加载需要几秒钟。
所以我的想法是用 UITableView
替换 UIScrollView
并将所有 CustomViews (UIViews
) 更改为 Custom UITableViewCells
。所以只有可见的单元格会在第一次启动时加载!
知道我有几个问题。
目前,我的 CustomViews 中的大部分代码都是使用 Frames
构建的,但我想将其完全更改为 Autolayout
,我不认为使用 IB
(xib 文件 ...)来构建它们是有意义的。所以我必须在代码中完成整个自动布局?
一些自定义视图非常大,所以它们变得非常高,而一些可以非常小。我担心的是滚动性能会非常糟糕......因为我不能真正使用 estimatedRowHeight
(一个例子:有时一个单元格可以获得 1000.0f 的高度而下一个单元格只有 40.0f)。并且在组合中使用 Autolayout 和时间我必须等到我的服务器的响应到达,我认为这对用户来说真的很烦人。
最多可以有20种不同!!自定义行,在这种情况下使用 UITableView
真的有意义吗?正如我之前提到的,它们的大小和内容都非常不同!
这是我的新代码的一小部分:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.node.blocks count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
GTBlockView *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
GFBlock *block = [self.node.blocks objectAtIndex:indexPath.row];
//createInterfaceforBlock -- Here the Cell gets called and the Content and the Size gets defined
cell = [[GTAppController sharedInstance] createInterfaceForBlock:block];
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//GTBlockView is the SuperView of all my Custom Cells
GTBlockView *cell = [self.offscreenCells objectForKey:CellIdentifier];
if (!cell)
{
cell = [[GTBlockView alloc] init];
[self.offscreenCells setObject:cell forKey:CellIdentifier];
}
GFBlock *block = [self.node.blocks objectAtIndex:indexPath.row];
//createInterfaceforBlock -- Here the Cell gets called and the Content and the Size gets defined
cell = [[GTAppController sharedInstance] createInterfaceForBlock:block];
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
height += 1;
return height;
}
也许你们中的一些人有更好的想法或一些好的资源?
我在我自己的应用程序中完全按照您的想法做了 - 我首先沿着 UIScrollview 路线前进,然后我改为具有多种自定义单元格的 UITableview。它确实非常有意义并且非常值得 - 我从 uitableview 获得了巨大的性能提升。 UIScrollview 的主要问题之一是它将为您的 contentView 中的所有内容制定自动布局,正如您所说,这可能需要几秒钟,但 UITableview 会更快更有效地处理这个问题。所以 - 去吧。
我强烈建议您为每个自定义单元格使用唯一的 XIB 文件。分别在每一个中进行自动布局,这样您就更有可能避免出现问题。编程约束更难维护,自动布局通常具有挑战性。
为了使其全部正常工作,我执行了以下操作:首先,我有一个 UITableviewCell 的子class,它是所有其他单元格对象的父 class。在我的例子中,这被称为 SNFormTableCell (UITableviewCell)。然后所有其他单元格对象都基于此 class。在我的 cellForRowAtIndexPath 方法中,我这样做:
ReportItem *objectForCell = [self.reportSet reportItemForSection:indexPath.section andRow:indexPath.row]; //my personal data class - just contains the cell data I need
NSString *identifier = [self getNibNameAndReusableIdentifierNameForObjectType:objectForCell.cellType.integerValue];
SNFormTableCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell)
{
[self registerNib:[UINib nibWithNibName:identifier bundle:nil] forCellReuseIdentifier:identifier];
cell = [tableView dequeueReusableCellWithIdentifier:identifier];
cell.contentView.clipsToBounds = YES;
}
[cell setSnFormTableCellDelegate:self]; //i have a delegate that calls back to the tableview when cells are interacted with
cell.reportItem = objectForCell; //put the data object onto the cell to do with as the cell requires
[cell refreshUI]; //update the UI using the data - this is over-ridden by the various subclasses of the cell
那里的方法称为 getNibNameAndReusableIdentifierNameForObjectType
.. 看起来像这样(只是获取我们需要的标识符以供 nib 和重新使用):
- (NSString*) getNibNameAndReusableIdentifierNameForObjectType:(SNFormTableObjectType)objectType {
if (objectType == SNFormTableObjectTypeBoolean) return @"SNBooleanCell";
if (objectType == SNFormTableObjectTypeDatePicker) return @"SNDatePickerCell";
if (objectType == SNFormTableObjectTypeDropDown) return @"SNDropDownCell";
if (objectType == SNFormTableObjectTypeDropDownPlusSingleLineText) return @"SNDropDownPlusSingleLineTextCell";
... etc
}
最后,父单元格 class 有一个名为 -(void) refreshUI 的方法。所以我把数据对象放到那个单元格上——它包含我可能需要的所有数据。 subclasses 以他们自己的特定方式覆盖此 refreshUI 方法,以根据需要使用数据。
重申一下,我从这条路中获益匪浅。包含大量内容的滚动视图,需要 5 秒或更长时间来加载笔尖和计算自动布局(也在主线程上,使应用程序无响应),将立即出现在 UITableview 版本上。所以去吧。如果您需要有关如何操作的更多详细信息,请告诉我。
我想重构一些代码以获得更好的性能,但我的问题是我不确定如何去做。目前我有一个 UIViewController
上面有一个 UIScrollView
。
我还有 20 个不同的视图(每个视图都有自己的 .h 和 .m 文件)可以完全动态地放置在我的 UIScrollView
上。每次我启动 UIViewController
时,我都会向我的服务器发送一个请求,然后我得到响应,然后我知道我必须在 UIScrollView
上放置多少视图。
所以你可以想象,当我的 UIScrollView
上有很多不同的视图时,在用户最终可以与它们交互之前,所有视图都已完全加载需要几秒钟。
所以我的想法是用 UITableView
替换 UIScrollView
并将所有 CustomViews (UIViews
) 更改为 Custom UITableViewCells
。所以只有可见的单元格会在第一次启动时加载!
知道我有几个问题。
目前,我的 CustomViews 中的大部分代码都是使用
Frames
构建的,但我想将其完全更改为Autolayout
,我不认为使用IB
(xib 文件 ...)来构建它们是有意义的。所以我必须在代码中完成整个自动布局?一些自定义视图非常大,所以它们变得非常高,而一些可以非常小。我担心的是滚动性能会非常糟糕......因为我不能真正使用
estimatedRowHeight
(一个例子:有时一个单元格可以获得 1000.0f 的高度而下一个单元格只有 40.0f)。并且在组合中使用 Autolayout 和时间我必须等到我的服务器的响应到达,我认为这对用户来说真的很烦人。最多可以有20种不同!!自定义行,在这种情况下使用
UITableView
真的有意义吗?正如我之前提到的,它们的大小和内容都非常不同!
这是我的新代码的一小部分:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.node.blocks count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
GTBlockView *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
GFBlock *block = [self.node.blocks objectAtIndex:indexPath.row];
//createInterfaceforBlock -- Here the Cell gets called and the Content and the Size gets defined
cell = [[GTAppController sharedInstance] createInterfaceForBlock:block];
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//GTBlockView is the SuperView of all my Custom Cells
GTBlockView *cell = [self.offscreenCells objectForKey:CellIdentifier];
if (!cell)
{
cell = [[GTBlockView alloc] init];
[self.offscreenCells setObject:cell forKey:CellIdentifier];
}
GFBlock *block = [self.node.blocks objectAtIndex:indexPath.row];
//createInterfaceforBlock -- Here the Cell gets called and the Content and the Size gets defined
cell = [[GTAppController sharedInstance] createInterfaceForBlock:block];
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
height += 1;
return height;
}
也许你们中的一些人有更好的想法或一些好的资源?
我在我自己的应用程序中完全按照您的想法做了 - 我首先沿着 UIScrollview 路线前进,然后我改为具有多种自定义单元格的 UITableview。它确实非常有意义并且非常值得 - 我从 uitableview 获得了巨大的性能提升。 UIScrollview 的主要问题之一是它将为您的 contentView 中的所有内容制定自动布局,正如您所说,这可能需要几秒钟,但 UITableview 会更快更有效地处理这个问题。所以 - 去吧。
我强烈建议您为每个自定义单元格使用唯一的 XIB 文件。分别在每一个中进行自动布局,这样您就更有可能避免出现问题。编程约束更难维护,自动布局通常具有挑战性。
为了使其全部正常工作,我执行了以下操作:首先,我有一个 UITableviewCell 的子class,它是所有其他单元格对象的父 class。在我的例子中,这被称为 SNFormTableCell (UITableviewCell)。然后所有其他单元格对象都基于此 class。在我的 cellForRowAtIndexPath 方法中,我这样做:
ReportItem *objectForCell = [self.reportSet reportItemForSection:indexPath.section andRow:indexPath.row]; //my personal data class - just contains the cell data I need
NSString *identifier = [self getNibNameAndReusableIdentifierNameForObjectType:objectForCell.cellType.integerValue];
SNFormTableCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell)
{
[self registerNib:[UINib nibWithNibName:identifier bundle:nil] forCellReuseIdentifier:identifier];
cell = [tableView dequeueReusableCellWithIdentifier:identifier];
cell.contentView.clipsToBounds = YES;
}
[cell setSnFormTableCellDelegate:self]; //i have a delegate that calls back to the tableview when cells are interacted with
cell.reportItem = objectForCell; //put the data object onto the cell to do with as the cell requires
[cell refreshUI]; //update the UI using the data - this is over-ridden by the various subclasses of the cell
那里的方法称为 getNibNameAndReusableIdentifierNameForObjectType
.. 看起来像这样(只是获取我们需要的标识符以供 nib 和重新使用):
- (NSString*) getNibNameAndReusableIdentifierNameForObjectType:(SNFormTableObjectType)objectType {
if (objectType == SNFormTableObjectTypeBoolean) return @"SNBooleanCell";
if (objectType == SNFormTableObjectTypeDatePicker) return @"SNDatePickerCell";
if (objectType == SNFormTableObjectTypeDropDown) return @"SNDropDownCell";
if (objectType == SNFormTableObjectTypeDropDownPlusSingleLineText) return @"SNDropDownPlusSingleLineTextCell";
... etc
}
最后,父单元格 class 有一个名为 -(void) refreshUI 的方法。所以我把数据对象放到那个单元格上——它包含我可能需要的所有数据。 subclasses 以他们自己的特定方式覆盖此 refreshUI 方法,以根据需要使用数据。
重申一下,我从这条路中获益匪浅。包含大量内容的滚动视图,需要 5 秒或更长时间来加载笔尖和计算自动布局(也在主线程上,使应用程序无响应),将立即出现在 UITableview 版本上。所以去吧。如果您需要有关如何操作的更多详细信息,请告诉我。