基于UICollectionView实现同步横向滚动的会议室分时预约组件方案咨询
这种会议室预约的时间轴需求我之前做过类似的,UITableView嵌套UICollectionView确实会踩滚动不同步的坑,给你一个更简洁靠谱的方案,用「外层横向滚动视图 + 单个垂直布局UICollectionView」的组合就能完美解决所有需求:
一、核心布局思路(解决滚动同步问题)
放弃嵌套方案,改用以下结构:
- 外层容器:一个
UIScrollView,只允许横向滚动,负责带动整个时间轴区域同步滚动。 - 左侧静态标签区:一个垂直排列的
UIStackView,固定在scrollView左侧,放置「Room 1」「Room 2」这些房间标签,不随滚动移动。 - 右侧时间轴内容区:一个
UICollectionView,设置为垂直滚动(禁用横向滚动),每个section对应一行:- 第0个section:顶部的时间标签行,显示「00:00」「00:15」等时段文本。
- 第1~N个section:每个section对应一个房间,每行的单元格是15分钟的可预约时段块。
这样外层scrollView横向滚动时,整个CollectionView会同步移动,所有房间行和顶部时间行的滚动完全对齐,彻底解决嵌套导致的独立滚动问题。
二、UICollectionView详细配置
1. 数据源处理
首先计算总时段数:24小时共96个15分钟块(24*60/15=96),所以每个section固定有96个item。
numberOfSections(in:)返回「房间数 + 1」(加1是顶部时间行)。collectionView(_:numberOfItemsInSection:)直接返回96。collectionView(_:cellForItemAt:)根据section区分:- 第0个section:返回时间标签单元格,显示对应索引的时间文本(比如index 0对应00:00,index 1对应00:15,以此类推)。
- 其他section:返回可选中的时段单元格,默认背景透明,选中时切换为预约色。
2. 布局实现(推荐iOS 13+用Compositional Layout)
Compositional Layout比FlowLayout更灵活,能轻松实现垂直section+横向item的布局:
func createTimeAxisLayout() -> UICollectionViewLayout { // 单个时段单元格的大小:宽度固定40pt,高度占满行高 let itemSize = NSCollectionLayoutSize( widthDimension: .absolute(40), heightDimension: .fractionalHeight(1) ) let item = NSCollectionLayoutItem(layoutSize: itemSize) // 每行的group:水平排列,行高固定60pt,宽度覆盖96个时段的总宽度 let groupSize = NSCollectionLayoutSize( widthDimension: .absolute(CGFloat(96)*40), heightDimension: .absolute(60) ) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) // 每个section就是一行,间距设为0 let section = NSCollectionLayoutSection(group: group) section.interGroupSpacing = 0 return UICollectionViewCompositionalLayout(section: section) }
如果要兼容iOS 12,用UICollectionViewFlowLayout也可以:设置scrollDirection = .vertical,itemSize = CGSize(width:40, height:60),然后手动计算CollectionView的contentWidth为96*40即可。
三、拖拽选择与预约冲突处理
1. 拖拽选中实现
给CollectionView添加长按拖拽手势,跟踪用户的触摸路径:
- 记录起始的
indexPath,拖拽过程中实时计算当前触摸点对应的indexPath,更新选中的时段范围。 - 可以在
UICollectionViewCell里重写isSelected属性,自动切换背景色,简化状态管理。
2. 同一时段唯一预约逻辑
维护一个字典来记录已选中的时段:比如var bookedSlots: [Int: Int] = [:],key是时段索引(095),value是对应的房间section索引(1N)。
- 当用户选中某个时段时,先检查
bookedSlots中是否已有该时段的记录:- 如果有,且不是当前房间,就禁用选中(或者提示该时段已被占用)。
- 如果没有,就把当前房间的section索引存入字典,同时刷新该时段所有房间的单元格,更新选中状态。
- 在
collectionView(_:cellForItemAt:)中,根据bookedSlots判断:如果当前时段被其他房间占用,就把单元格设为不可交互的灰色;如果被当前房间占用,就设为预约色。
四、优化小技巧
- 滚动对齐:实现
UIScrollViewDelegate的scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)方法,让滚动结束时自动对齐到时段单元格的边界,提升用户体验。 - 性能优化:确保单元格复用逻辑正确,避免因96*N个单元格导致的卡顿;时间标签单元格可以复用,不需要为每个时段创建新的单元格。
- 拖拽反馈:拖拽过程中实时更新单元格的背景色,给用户直观的选中反馈,避免操作困惑。
内容的提问来源于stack exchange,提问作者Aleph72
相关产品推荐
相关产品推荐

