如何使用autoresizing masks正确为UITableView设置tableHeaderView?
解决方案
你代码里的核心问题有3个:
- viewDidLoad阶段ViewController的视图还没完成最终布局,此时取
view.bounds.width是不准确的,拿到的宽度大概率是xib或storyboard里的默认值,和最终展示宽度不一致,会导致首次渲染就出错。 tableHeaderView的frame更新后不会自动通知UITableView重新计算头部高度,必须手动重新将header赋值给tableView.tableHeaderView才能生效。- 你在header的
layoutSubviews方法中修改自身frame,会触发递归调用layoutSubviews,容易出现布局循环,也不符合系统视图布局的逻辑。
第一步:修改Header视图逻辑
删除EventDetailTableHeaderView的layoutSubviews里修改自身frame的代码,新增计算自身合适高度的方法:
final class EventDetailTableHeaderView: UIView { private let titleContainer: TitleContainerView private let subtitleContainer: SubtitleContainerView init(event: CloudEvent) { // 初始frame先给zero,后面再计算大小 super.init(frame: .zero) titleContainer = TitleContainerView(frame: .zero, text: event.title) subtitleContainer = SubtitleContainerView(frame: .zero, text: event.displayString) backgroundColor = StyleKit.wDOWhite autoresizingMask = [.flexibleWidth] setupSubviews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { addSubview(titleContainer) titleContainer.autoresizingMask = [.flexibleWidth] titleContainer.backgroundColor = StyleKit.wDOWhite addSubview(subtitleContainer) subtitleContainer.autoresizingMask = [.flexibleWidth] subtitleContainer.backgroundColor = StyleKit.wDOBlue } // 新增方法:根据给定宽度计算header合适的大小 func sizeFitting(width: CGFloat) -> CGSize { // 先给两个子view设置正确的宽度,触发它们的布局计算高度 titleContainer.frame = CGRect(x: 0, y: 0, width: width, height: 0) titleContainer.layoutIfNeeded() subtitleContainer.frame = CGRect(x: 0, y: titleContainer.frame.maxY, width: width, height: 0) subtitleContainer.layoutIfNeeded() return CGSize(width: width, height: subtitleContainer.frame.maxY) } override func layoutSubviews() { super.layoutSubviews() // 只布局子view,不要修改自身frame positionSubtitleContainer() } private func positionSubtitleContainer() { subtitleContainer.frame.origin.y = titleContainer.frame.height } }
第二步:修改ViewController的布局逻辑
把header改成ViewController的全局属性,在viewWillLayoutSubviews里更新header的大小:
// 把header改成VC的属性,方便后续更新 private var tableHeaderView: EventDetailTableHeaderView! override func viewDidLoad() { super.viewDidLoad() tableView = EventDetailTableView(frame: .zero, style: .plain) tableView?.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView!) // 初始化header不需要传宽度 tableHeaderView = EventDetailTableHeaderView(event: event) tableView?.tableHeaderView = tableHeaderView NSLayoutConstraint.activate([ view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: tableView!.topAnchor), view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: tableView!.trailingAnchor), view.safeAreaLayoutGuide.leadingAnchor.constraint(equalTo: tableView!.leadingAnchor), view.bottomAnchor.constraint(equalTo: tableView!.bottomAnchor) ]) } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() guard let tableView = tableView else { return } // 拿到当前tableView的真实宽度,计算header的合适大小 let headerWidth = tableView.bounds.width let headerSize = tableHeaderView.sizeFitting(width: headerWidth) // 如果header的高度没变化就不用更新,避免重复布局 if tableHeaderView.bounds.height != headerSize.height { tableHeaderView.frame.size = headerSize // 必须重新赋值才能触发tableView更新头部布局 tableView.tableHeaderView = tableHeaderView } }
额外说明
你的TitleContainerView的layoutSubviews逻辑可以保持现状,如果SubtitleContainerView和TitleContainerView实现逻辑一致也不需要修改。屏幕旋转、视图尺寸变化的时候viewWillLayoutSubviews会自动触发,会自动更新header的高度,不需要额外处理旋转通知。
内容的提问来源于stack exchange,提问作者bobby123uk
相关产品推荐
相关产品推荐

