【发布时间】:2024-01-11 07:38:01
【问题描述】:
我正在尝试实现一个表格视图,允许用户“显示更多”行来切换完整的行数或较少的行数。加载视图时我已经预先准备好所有数据,因此我不需要获取任何额外的数据。
我可以让它工作,但我遇到的问题是我的表格视图在展开或折叠时动画不流畅。发生的情况是,如果触发“显示更多”操作,表格的大小会立即更新到包含所有数据的表格的完整高度。然后行将动画。
同样在隐藏行时,表格高度将一次全部缩小到结束高度,然后行将动画。
这是正在发生的事情的图片。它只是“跳”到全高,然后为行设置动画。但我希望它能够平稳地扩展表的高度,从而在向下平稳地扩展时显示数据。我想要相反的,当按下“少显示”时它会平滑地向上滑动。
我的应用布局如下:
-
UIScrollView-
UIStackViewUIViewControllerUIViewController-
UITableView UIView- ...
-
基本上,我有一个不同数据部分的可滚动列表。有些部分是表格,有些只是使用 AutoLayout 排列的临时视图,其他部分具有集合视图或页面视图控制器或其他类型的视图。
但是这部分是由上一个列表中的 UITableView 来表示的。
#import "MyTableView.h"
#import "MyAutosizingTableView.h"
@interface MyTableView ()
@property (strong, nonatomic) UILabel *titleLabel;
@property (strong, nonatomic) MyAutosizingTableView *tableView;
@end
@implementation MyTableView
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self.titleLabel setText:@"Details"];
self.numberOfRows = 3;
[self addSubview:self.titleLabel];
[self addSubview:self.tableView];
[NSLayoutConstraint activateConstraints:@[
[self.titleLabel.leadingAnchor constraintEqualToAnchor:self.layoutMarginsGuide.leadingAnchor],
[self.titleLabel.topAnchor constraintEqualToAnchor:self.layoutMarginsGuide.topAnchor],
[self.titleLabel.trailingAnchor constraintEqualToAnchor:self.layoutMarginsGuide.trailingAnchor],
[self.tableView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[self.tableView.topAnchor constraintEqualToSystemSpacingBelowAnchor:self.titleLabel.bottomAnchor multiplier:1.0f],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[self.tableView.widthAnchor constraintEqualToAnchor:self.widthAnchor]
]];
}
- (UITableView *)tableView {
if (!self->_tableView) {
self->_tableView = [[MyAutosizingTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self->_tableView.translatesAutoresizingMaskIntoConstraints = NO;
self->_tableView.rowHeight = UITableViewAutomaticDimension;
self->_tableView.estimatedRowHeight = UITableViewAutomaticDimension;
self->_tableView.allowsSelection = NO;
self->_tableView.scrollEnabled = NO;
self->_tableView.delegate = self;
self->_tableView.dataSource = self;
[self->_tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:@"dataCell"];
}
return self->_tableView;
}
- (UILabel *)titleLabel {
if (!self->_titleLabel) {
self->_titleLabel = [[UILabel alloc] init];
self->_titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_titleLabel.numberOfLines = 0;
self->_titleLabel.userInteractionEnabled = YES;
self->_titleLabel.textAlignment = NSTextAlignmentNatural;
self->_titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
self->_titleLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_titleLabel.adjustsFontSizeToFitWidth = NO;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showMore)];
[self->_titleLabel addGestureRecognizer:tapGesture];
}
return self->_titleLabel;
}
- (void)showMore {
if (self.numberOfRows == 0) {
self.numberOfRows = 3;
} else {
self.numberOfRows = 0;
}
NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:0];
[self.tableView reloadSections:indexes withRowAnimation:UITableViewRowAnimationFade];
}
- (void)setData:(NSArray<NSString *> *)data {
self->_data = data;
[self.tableView reloadData];
}
- (void)didMoveToSuperview {
//this has no effect unless the view is already in the view hierarchy
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.numberOfRows <= 0) {
return self.data.count;
}
return MIN(self.data.count, self.numberOfRows);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *data = self.data[indexPath.row];
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"dataCell" forIndexPath:indexPath];
cell.data = data;
return cell;
}
@end
我有一个 UITableView 子类,我在之前的代码中使用过。我有表格视图子类的原因是让表格在内容更改时调整大小。如果有人知道没有子类的更好方法,我也会非常有兴趣学习。
#import "MyAutosizingTableView.h"
@implementation MyAutosizingTableView
- (CGSize)intrinsicContentSize {
return self.contentSize;
}
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
[self invalidateIntrinsicContentSize];
}
@end
有谁知道我可以做些什么来获得平滑的展开动画?我真的希望表格能够顺利展开并显示新行中的数据。
更新 - 我正在尝试做的高级概述
在这里,我将尝试描述我正在尝试完成的工作以及我将如何进行,因此,如果有人有任何替代方法或改进来解决这个问题,我将非常愿意接受建议。提前感谢您阅读本文。
所以我正在开发一个显示产品数据的视图控制器,例如汽车。视图控制器有不同的部分负责显示不同的数据。例如,第一部分显示商品的图片,对于这一部分,我使用的是 UIPageView,它基本上显示产品图像的轮播。
另一个部分显示有关当前产品的选项。例如,有人可能会为汽车或不同的车轮选择不同的颜色。对于本节,我使用表格视图来显示可用属性的列表,其中每个属性位于表格的不同部分。按下节标题后,表格视图会添加一行,显示该属性的可用选项。例如,假设汽车有不同的可用颜色(红色、绿色、蓝色和黄色)。按下节标题后,表格视图会添加一行并对其进行动画处理(使用“iOS 12 编程”第 8 章中讨论的技术)。然后显示的行包含一个水平滚动的 UICollectionView,允许用户在颜色(或滚轮选项或正在更改的任何属性)之间进行选择。
另一个部分显示其他客户对该产品的评价。例如,如果有人在汽车上留下了一篇文章,那么这将放在本节中。本部分有一个表格,显示产品在不同标准下的票价。仍然以汽车为例,它可能有一排是为了舒适,另一排是为了油耗,另一排是为了性能等等。然后还有一个水平的 UICollectionView 显示关于产品的文章。
还有一个部分是产品的属性列表。例如,对于汽车,它可能会在左侧显示发动机尺寸,在右侧显示 V12。
还有其他部分,但是为了将它们结合在一起,我有一个滚动视图,里面有一个垂直的 UIStackView。这就是我真正好奇的地方。显示或布局所有这些部分的最佳方式是什么?堆栈视图是要走的路,还是我应该使用表格视图,还是应该只使用滚动视图与这些视图挂钩,直接使用 AutoLayout,还是有比所有这些更好的方法?
非常重要的一点是,每个部分在产品之间可以有不同的尺寸。例如,某些部分可能是一种产品的一个高度,但对于其他产品可能是完全不同的高度。所以我需要它能够动态调整每个部分的大小,并且我还需要能够为一些大小更改设置动画(例如,它向表中添加一行然后删除它的部分,它需要动画并制作整个部分以动画方式放大)。
另外,我希望每个部分都是模块化的,并且最终不会有一个巨大的视图控制器来管理所有内容。我希望每个部分基本上都是独立的。我只是将数据传递给该部分,然后它处理与布局和交互相关的所有内容。但同样,真正的问题是让这些视图能够调整大小并在父视图(当前为堆栈视图)中进行更新。
但我对 iOS 开发真的很陌生,虽然我一直在努力尽可能快地学习,但我仍然不确定我目前这样做的方式(其中包含堆栈视图的滚动视图) 是最好的方法,或者如果有更好的方法。我最初在一个带有静态单元格的表格视图中尝试了这个,其中每一行都是不同的部分。但是随后让子视图控制器调整父表视图中的行大小并不能很好地工作。从那时起,我也从让每个部分成为单独的视图控制器,而只是让每个部分成为一个视图(而不是视图控制器),以使调整大小更容易,但我仍然遇到问题。我也考虑过集合视图,但我还需要能够支持 iOS 12(或者如果我真的需要并且只支持 iOS 13+,我可以放弃对 iOS 12 的支持)。
但重申一下,我的总体目标是有一些方法来布局这个界面,该界面由不同的视图组成,每个视图处理不同的数据。我希望每个视图都能够调整大小并且能够平滑。而且我希望界面的每个部分都是模块化的,以避免使用一个大视图控制器。
更新 2
按照建议将表格视图包装在 UIView 中,然后设置具有不同优先级的两个底部约束,这就是我得到的。每当我尝试为视图的高度约束等设置动画时,我也会得到这种效果。我认为这是因为这里的整体视图包含在堆栈视图中,所以我认为堆栈视图在其排列的子视图更改大小时正在做一些事情。
这里的图片显示了展开和折叠表格视图。折叠时可见三行,展开时可见五行。
黄色和蓝色视图代表页面上的其他视图。它们不是该视图的一部分,它们所包含的堆栈视图也不是。它们只是此处包含的页面的其他部分,用于显示此问题。这里的所有视图都包含在堆栈视图中,我认为这是导致动画问题的原因,但我不确定如何解决。
【问题讨论】:
-
一个更好的解决方法是使用 UICollectionView 自定义您的可扩展表格,同时扩展只需调用
reloadSections(_:with:)重新加载特定部分。 -
从您的问题中不清楚您要“展开/折叠”什么。您的动画图像似乎显示表格行 moving ...您只是想“显示”已经填充的表格视图?
-
@DonMag 对 gif 的质量感到抱歉,它太快了。但是发生的事情是我们从 3 行开始,然后当我展开表格时,表格似乎在添加任何行之前设置了它的
frame.height,然后它添加了行。但这会导致该表部分下方的其余部分“跳”下来而不是平滑地动画。我还尝试让表格完全填充,然后在表格上添加高度约束,但这也导致了跳跃,因为它包含在堆栈视图中。有没有比使用堆栈视图更好的布局接口的方法?跨度> -
@pqteru 你的意思是使用 UICollectionView 来处理应用程序的布局吗?我尝试过使用 UITableView 和静态单元格,到目前为止我尝试过使用堆栈视图。当我需要调整其中一个部分的大小时(它可能是 UIViewController 子类或 UIView 子类),它们都会给我带来问题。但我需要能够支持在这些部分中动态扩展和折叠项目,因此这些部分需要能够增加和缩小高度。我也在尝试支持 iOS 12+,但如有必要,我可以放弃它并支持 iOS 13+。你推荐什么样的布局?
-
@DonMag 或者你发现我的代码有什么问题吗?当我将所有行添加到表视图中,然后通过约束设置表的高度然后为其设置动画时,如果我将包含视图设置为非常大的高度(对于本节,我有一个 UIView 和在其中我有一个标签和一个表格,如代码所示),因此它不必扩展。但是如果表格视图必须拉伸包含视图,那就是它停止正常工作的时候。我只是想让该部分自动调整大小,并使其能够根据需要顺利展开和折叠表格。
标签: ios uitableview uikit uistackview uianimation