如何实现UICollectionView顶部固定1个header、底部固定1个header仅中间内容滚动
问题描述
我在UICollectionView中设置了多个section header,已经通过系统自带特性实现了第一个header固定在顶部的效果,现在需要实现第二个header固定在UICollectionView底部,其余header都排布在底部固定header的下方,仅两个固定header之间的内容可以滚动,也欢迎其他实现思路。
现有实现代码
顶部吸顶实现
if let layout = groupCollectionView.collectionViewLayout as? UICollectionViewFlowLayout { layout.sectionHeadersPinToVisibleBounds = true }
业务逻辑代码
extension GrpLVController: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: collectionView.frame.width/3, height: 200) } func numberOfSections(in collectionView: UICollectionView) -> Int { return totalSec.count } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { return CGSize(width: self.view.frame.size.width, height: 36) } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { switch kind { case UICollectionView.elementKindSectionHeader: let headerV: GCell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "GCell", for: indexPath) as! GCell headerV.headerBackGroundView.layer.borderWidth = 0.5 headerV.headerBackGroundView.layer.borderColor = UIColor.lightGray.cgColor let plusBtn = headerV.viewWithTag(101) as? UIButton if self.totalSec[indexPath.section] == selectedIndx { plusBtn?.setImage(UIImage(named: "Close"), for: .normal) } else { plusBtn?.setImage(UIImage(named: "Open"), for: .normal) } headerV.hDelegate = self headerV.headerBtnOut.tag = indexPath.section let sectionn = self.totalSec[indexPath.section] let lbl = headerV.viewWithTag(100) as? UILabel if sectionn == .Grp_Sugestion { lbl?.text = "Group suggestions" } else if sectionn == .MyGrp { lbl?.text = "My groups (\(myGList.count))" } else if sectionn == .InvitesReceivedCorrect { lbl?.text = "Invites Received (\(inviteList.count))" } else { lbl?.text = "Requests Sent (\(requestedList.count))" } return headerV default: return UICollectionReusableView() } } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let sectionn = self.totalSec[section] if sectionn == .Grp_Suggestion && selectedIndx == .Grp_Suggestion { if suggestedList.count % 3 == 0 { return suggestedList.count } else { return suggestedList.count - (suggestedList.count % 3) + 3 } } else if sectionn == .MyGrp && selectedIndx == .MyGrp { if myGList.count % 3 == 0 { return myGList.count } else { return myGList.count - (myGList.count % 3) + 3 } } else if sectionn == .InvitesCorrect && selectedIndx == .InvitesCorrect { if inviteReveivedList.count % 3 == 0 { return inviteList.count } else { return inviteList.count - (inviteList.count % 3) + 3 } } else if sectionn == .InvitesReceived && selectedIndx == .InvitesReceived { if requestedList.count % 3 == 0 { return requestedList.count } else { return requestedList.count - (requestedList.count % 3) + 3 } } else { return 0 } } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GroupGridCell", for: indexPath) as! GroupGridCell cell.headerDelegate = self let sectionn = self.totalSec[indexPath.section] if sectionn == .Grp_Suggestion { if suggestedList.count > indexPath.row { cell.conf_G_InfoCell(group: suggestedList[indexPath.row]) } else { cell.contentSubView.isHidden = true } } else if sectionn == .MyGroup { if myGList.count > indexPath.row { cell.configureGroupCell(group: myGList[indexPath.row]) } else { cell.contentSubView.isHidden = true } } else if sectionn == .InvitesRCorrect { if inviteList.count > indexPath.row { cell.configureInviteCell(invite: inviteList[indexPath.row]) } else { cell.contentSubView.isHidden = true } } else { if requestedList.count > indexPath.row { cell.configureInviteCell(invite: requestedList[indexPath.row]) } else { cell.contentSubView.isHidden = true } } cell.contentSubView.layer.cornerRadius = 7.0 cell.contentSubView.layer.masksToBounds = true cell.contentSubView.layer.borderWidth = 0.5 cell.contentSubView.layer.borderColor = UIColor.lightGray.cgColor cell.RJoinBtn.layer.borderWidth = 1.0 cell.RJoinBtn.layer.borderColor = UIColor.systemGreen.cgColor cell.RJoinBtn.layer.cornerRadius = 12.0 cell.RJoinBtn.layer.masksToBounds = true cell.ivGPic.layer.borderWidth = 1.0 cell.ivGPic.layer.borderColor = UIColor.white.cgColor cell.ivGPic.layer.cornerRadius = 8 cell.ivGPic.layer.masksToBounds = true return cell } }
实现方案
方案1:自定义UICollectionViewFlowLayout(纯布局实现)
- 重写
layoutAttributesForElements(in rect: CGRect)方法,手动计算需要固定在底部的header的frame,将其zIndex设置为高于普通cell,避免被滚动内容遮挡 - 重写
shouldInvalidateLayout(forBoundsChange newBounds: CGRect)返回true,让滚动时布局实时更新 - 核心逻辑:当底部header对应的section内容滚动到超过底部固定位置时,保持header的y值为
collectionView.bounds.height - header高度 - collectionView.contentInset.bottom,如果内容还没到这个位置,就正常按照流布局排布
方案2:分层视图实现(改动最小,兼容性最好)
- 把要固定在底部的header单独作为独立UIView,直接添加到UICollectionView的父视图上,位置固定在底部
- 调整UICollectionView的contentInset,底部增加和固定header高度相等的内边距,避免底部滚动内容被固定header遮挡
- 原有数据源中移除该固定header对应的section,剩余的section全部放在中间滚动区域,顶部吸顶效果保留原有的
sectionHeadersPinToVisibleBounds配置即可 - 底部固定header的点击交互直接给独立view添加事件即可,不需要和collectionView的补充视图逻辑绑定
内容的提问来源于stack exchange,提问作者Baljinder Kamboj
相关产品推荐
相关产品推荐

