如何实现UITableView两个头部视图的吸顶效果
实现双头部吸顶(折叠顶部Header + 固定Section头部)
核心修改方案
针对你需要的「顶部Header折叠后吸顶、Section Header固定吸顶」效果,分两部分调整代码:
1. 完善顶部折叠Header的吸顶逻辑
修改HeaderView.swift中的updatePosition方法,确保Header折叠到最小高度后固定在安全区顶部:
func updatePosition() { guard let scrollView = scrollView else { return } let minimumSize = minimumHeight let referenceOffset = scrollView.safeAreaInsets.top let initialHeaderHeight: CGFloat = 250 // 和tableView contentInset的top值保持一致 let scrollOffsetY = scrollView.contentOffset.y let maxCollapseOffset = initialHeaderHeight - minimumSize // 滚动超过最大折叠偏移量时,Header保持最小高度并固定 if scrollOffsetY >= maxCollapseOffset { frame = CGRect(x: 0, y: referenceOffset, width: scrollView.frame.width, height: minimumSize) } else { // 未完全折叠时,动态调整高度和位置 let currentHeight = initialHeaderHeight - scrollOffsetY frame = CGRect(x: 0, y: referenceOffset + scrollOffsetY, width: scrollView.frame.width, height: currentHeight) } }
同时在HomeTableViewController中添加滚动监听,实时更新Header位置:
override func scrollViewDidScroll(_ scrollView: UIScrollView) { headerView.updatePosition() }
2. 实现Section Header固定吸顶
将原tableView的section header改为独立视图,通过滚动偏移控制其位置:
步骤1:添加自定义Section Header属性
在HomeTableViewController中声明:
private lazy var sectionHeaderView: UIView = { let view = UIView() view.backgroundColor = .systemBackground view.translatesAutoresizingMaskIntoConstraints = false // 标题Label let titleLabel = UILabel() titleLabel.text = "Subtitle" titleLabel.textColor = .label titleLabel.font = UIFont.systemFont(ofSize: 20.0, weight: .semibold) titleLabel.translatesAutoresizingMaskIntoConstraints = false view.addSubview(titleLabel) // 按钮 let showAllButton = UIButton(type: .system) showAllButton.setTitle("alle anzeigen →", for: .normal) showAllButton.setTitleColor(.secondaryLabel, for: .normal) showAllButton.titleLabel?.font = UIFont.systemFont(ofSize: 15.0, weight: .semibold) showAllButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside) showAllButton.translatesAutoresizingMaskIntoConstraints = false view.addSubview(showAllButton) // 布局约束 NSLayoutConstraint.activate([ titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: view.frame.width * 0.05), titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), showAllButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -view.frame.width * 0.05), showAllButton.centerYAnchor.constraint(equalTo: view.centerYAnchor), view.heightAnchor.constraint(equalToConstant: 58) ]) return view }()
步骤2:初始化并隐藏原Section Header
在viewDidLoad中添加视图并隐藏tableView默认section header:
override func viewDidLoad() { super.viewDidLoad() // ... 原有代码 ... // 添加自定义section header到视图 view.addSubview(sectionHeaderView) NSLayoutConstraint.activate([ sectionHeaderView.leadingAnchor.constraint(equalTo: view.leadingAnchor), sectionHeaderView.trailingAnchor.constraint(equalTo: view.trailingAnchor), sectionHeaderView.heightAnchor.constraint(equalToConstant: 58) ]) // 隐藏tableView默认section header tableView.sectionHeaderHeight = 0 }
步骤3:滚动时更新Section Header位置
修改scrollViewDidScroll方法,让Section Header在顶部Header完全折叠后固定:
override func scrollViewDidScroll(_ scrollView: UIScrollView) { headerView.updatePosition() let headerMinHeight = headerView.minimumHeight let safeTop = scrollView.safeAreaInsets.top let maxCollapseOffset = 250 - headerMinHeight // 250为顶部Header初始高度 // 顶部Header完全折叠后,Section Header固定在其下方;否则跟随滚动 if scrollView.contentOffset.y >= maxCollapseOffset { sectionHeaderView.frame.origin.y = safeTop + headerMinHeight } else { sectionHeaderView.frame.origin.y = safeTop + 250 + scrollView.contentOffset.y } }
步骤4:适配安全区变化
更新viewSafeAreaInsetsDidChange和viewWillLayoutSubviews,确保位置正确:
override func viewSafeAreaInsetsDidChange() { super.viewSafeAreaInsetsDidChange() tableView.contentInset = UIEdgeInsets(top: 250 + tableView.safeAreaInsets.top, left: 0, bottom: 0, right: 0) headerView.updatePosition() scrollViewDidScroll(tableView) } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() headerView.updatePosition() scrollViewDidScroll(tableView) }
3. 调整TableView内容间距
避免内容和自定义Section Header重叠,设置section间距:
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0 } override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return .leastNormalMagnitude }
最终效果
- 向上滚动:顶部Header逐步折叠,达到最小高度后固定在安全区顶部;Section Header随滚动上移,直到顶部Header完全折叠后,固定在其下方
- 向下滚动:顶部Header逐步展开,Section Header跟随回到初始位置
内容的提问来源于stack exchange,提问作者j___.___j
相关产品推荐
相关产品推荐

