自定义 UITableViewCell 的布局何时完成?
Posted
技术标签:
【中文标题】自定义 UITableViewCell 的布局何时完成?【英文标题】:When layout is complete for custom UITableViewCell? 【发布时间】:2014-11-20 22:32:29 【问题描述】:我在故事板文件中定义了一个自定义表格视图单元格。
此单元格包含带有几页内容的滚动视图和一个页面指示器。滚动视图、内容视图和单元格本身都使用自动布局定义。 Page1/2/3 视图绑定为与滚动视图具有相同的宽度,并且容器绑定为具有滚动视图的 3 倍宽度。这样,我不确定内容的大小是否完全可以显示三页信息,无论屏幕大小如何。
我在自定义单元格类中有一个代码,用于在滚动视图滚动时更新页面指示器 - 这工作正常。
我还在单元类中公开了该方法以从外部指定页面,并且在这个 setter 中我正在做类似的事情:
- (void)setPage:(NSInteger)page animated:(BOOL)animated
CGFloat pageWidth = self.scrollView.frame.size.width;
self.pageControl.currentPage = page;
[self.scrollView setContentOffset:CGPointMake(page*pageWidth, 0) animated:animated];
我面临的问题是,当我调用此设置器时,滚动视图框架和内容大小通常没有正确定义,因此会设置页面指示器,但滚动视图不会更改内容偏移量。
我在哪里可以看到我的单元格自动布局已完成并且我可以保证 setContentOffset 会正常工作?我尝试从cellForIndexPath
调用setPage
- 这显然不起作用,然后我尝试从willDisplayCell
调用它 - 同样的故事 - 单元格本身已经正确布局,但滚动视图仍然有 600px(来自故事板),它失败了。
【问题讨论】:
您是否要显示分页内容? 是的。这正是我想要做的 【参考方案1】:我最终做了以下事情:
在自定义单元子类中添加了layoutSubview
实现
- (void)layoutSubviews
[super layoutSubviews];
[self.contentView layoutSubviews];
// set proper content offset
[self.scrollView setContentOffset:CGPointMake(self.frame.size.width*self.pageControl.currentPage, 0) animated:NO];
如果没有我直接在contentView
上调用layoutSubview
,即使在[super layoutSubview]
完成后,滚动视图也没有正确的框架。
我不必将当前页码存储在内部变量中,因为pageControl
已经保存了它,并且它是在配置原始单元格信息时设置的。
有趣的是——最初我在该方法中添加了[self.scrollView layoutSubview]
,它在 ios8 中运行良好,但在 iOS7 中没有任何好处。我必须上一级到contentView
以确保它在 7 和 8 中都有效。
【讨论】:
这太棒了。【参考方案2】:我发现 UITableViewCell 的 layoutSubviews 的覆盖不能正常工作。在第一次调用 layoutSubviews 时,我得到了 contentView 的不正确边界。 所以我找到了另一种解决方案:
// implementation of the UITableViewDelegate protocol
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
DispatchQueue.main.async
// the layout of the cell is ready
【讨论】:
从该异步 内部调用 cell.layoutSubviews() 是唯一解决我将圆形蒙版应用于图像视图的问题DispatchQueue.main.async
是这里的关键【参考方案3】:
您似乎已准备好定制实施,所以这可能是可行的。
在表格视图设置其子视图之前调用您的 -setPage:animated:
方法,让单元格有机会执行相同操作,或者除非您在单元格中保持某种状态(可能只是页码)并对以下一种或多种机会做出反应:
等到 UITableViewController 的 -viewDidLayoutSubviews
方法触发,然后调用 -cellForRowAtIndexPath:
来获取单元格。然后单元格应该有一个有效的框架。
在您的自定义 UITableViewCell 中覆盖 -didMoveToSuperview
。到这个时候,框架也应该是有效的。
在这些点之后,您可以检查自定义单元格的状态(其中应包含要显示的页码)并滚动到适当的点。重构您的方法以设置状态(单元格上的整数属性)并具有单独的私有方法 -setPage:animated:
和上面的两个覆盖之一可以调用来计算偏移量并进行滚动可能是有意义的。
建议的替代方案:
您可以通过在单元格的contentView
中嵌入 UICollectionView 来实现这一点。您当前正在实现 UITableViewDataSource 的 UIViewController 也可以实现 UICollectionViewDataSource 并为集合视图提供其单元格。
假设您的 UITableView 单元格是屏幕的宽度和高度,您可以将嵌入式集合视图配置为基本上水平分页(因为 UICollectionView 是 UIScrollView 的子类)。
在设置集合视图时,使用 UICollectionViewFlowLayout 作为布局对象并进行如下配置:
// Ensure the collection view scrolls horizontally.
let flowLayout = UICollectionViewFlowLayout();
flowLayout.scrollDirection = .Horizontal;
let collectionView = UICollectionView(frame:tableCellFrame, layout:flowLayout);
// These are actually UIScrollView properties.
collectionView.pagingEnabled = true;
collectionView.bounces = false;
然后,您将每个内容页面放置在自定义 UICollectionViewCell 中,集合视图将为您分页。这样做的好处是免费为您提供分页滚动 API。
【讨论】:
您是否建议将 UIPageController 放在单元格内? 不,完全放弃 UITableView。 这不是一个选项。我需要在牢房内处理这个问题 您能否发布一张您必须完成的屏幕截图,以帮助我了解您要完成的工作? 感谢您非常彻底的回答和为此花费的时间。我会发布我最终做的事情,但我非常感谢你的帮助。【参考方案4】:由于 layoutSubviews() 经常被多次调用,所以很难知道布局是否是最终的。因此,我实现了 UIView 的扩展,并使用它来了解要执行哪个 layoutSubviews() 调用。
斯威夫特 4.2
extension UIView
/** A Boolean value that determines whether the view or any of its subviews need a layout update.
This can be called from -layoutSubviews to determine if the layout of all the subviews are up-to-date.
*/
open var needsLayout: Bool
get
return needsLayout(view: self)
func needsLayout(view: UIView) -> Bool
var result = view.needsUpdateConstraints()
for subview in view.subviews
result = result || needsLayout(view: subview)
return result
那么就可以简单的在 UITableViewCell 子类中使用它了:
class MyCustomCell: UITableViewCell
override func layoutSubviews()
super.layoutSubviews()
if !needsLayout
// The layout is now complete, do what you must!
【讨论】:
以上是关于自定义 UITableViewCell 的布局何时完成?的主要内容,如果未能解决你的问题,请参考以下文章
自定义 UITableViewCell 背景,何时重新加载等