使用Storyboards与Swift实现可展开Collection view cell弹窗方案咨询
问题1:是否可以在Storyboards中直接配置同一个cell的折叠、展开两种视图?
可以实现,操作方式如下:
- 打开Storyboard中对应CollectionView的自定义cell原型,同时添加折叠态、展开态所需的所有UI组件
- 给两种状态的布局约束设置不同的优先级:默认折叠状态下,折叠相关的宽高、内部组件约束优先级设为
999,展开相关约束优先级设为250;也可以将两类约束分别关联为自定义cell的IBOutlet属性,切换状态时主动激活/停用对应约束,避免冲突 - 展开态默认不需要显示的组件,直接在Storyboard属性面板勾选
Hidden,状态切换时修改isHidden属性即可 - 如果展开弹窗会超出原cell的显示范围,直接将弹窗层作为cell的子视图默认隐藏即可,不需要额外新建cell原型
问题2:代码层面实现方案
你可以参考以下步骤实现完整逻辑:
1. 预定义属性
首先在ViewController中添加两个全局属性做状态管理:
// 记录当前展开的cell下标,默认无展开 var currentExpandedIndexPath: IndexPath? // 存储模糊背景视图 weak var blurBackgroundView: UIVisualEffectView?
同时在自定义cell中实现两个状态切换的方法:
// 切换到展开态 func switchToExpandedState() { // 停用折叠约束,激活展开约束 foldedConstraints.forEach { $0.isActive = false } expandedConstraints.forEach { $0.isActive = true } // 切换UI显隐 foldedViews.forEach { $0.isHidden = true } expandedViews.forEach { $0.isHidden = false } layoutIfNeeded() } // 切换到折叠态 func switchToFoldedState() { expandedConstraints.forEach { $0.isActive = false } foldedConstraints.forEach { $0.isActive = true } expandedViews.forEach { $0.isHidden = true } foldedViews.forEach { $0.isHidden = false } layoutIfNeeded() }
2. 点击cell触发状态切换
实现CollectionView的点击代理:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { // 已经有展开的cell,且不是当前点击的,先收起已展开的 if let expandedIndex = currentExpandedIndexPath, expandedIndex != indexPath { collapseExpandedCell() } // 切换当前cell状态 if currentExpandedIndexPath == indexPath { collapseExpandedCell() } else { expandCell(at: indexPath) } }
3. 展开逻辑实现
private func expandCell(at indexPath: IndexPath) { // 禁用CollectionView滚动 collectionView.isScrollEnabled = false // 添加模糊背景 let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialDark) let blurView = UIVisualEffectView(effect: blurEffect) blurView.frame = view.bounds blurView.alpha = 0 // 点击模糊背景收回 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(collapseExpandedCell)) blurView.addGestureRecognizer(tapGesture) view.addSubview(blurView) self.blurBackgroundView = blurView // 拿到点击的cell,转换坐标到VC根视图 guard let cell = collectionView.cellForItem(at: indexPath) as? YourCustomCell else { return } let originalCellFrame = cell.convert(cell.bounds, to: view) // 将cell从CollectionView移到VC根视图层级,放在模糊层上方 view.addSubview(cell) cell.frame = originalCellFrame currentExpandedIndexPath = indexPath // 动画展开到全屏3/5大小 let expandedHeight = view.bounds.height * 3 / 5 let targetFrame = CGRect( x: 0, y: (view.bounds.height - expandedHeight) / 2, width: view.bounds.width, height: expandedHeight ) UIView.animate(withDuration: 0.3) { blurView.alpha = 1 cell.frame = targetFrame cell.switchToExpandedState() } }
4. 收回逻辑实现
@objc private func collapseExpandedCell() { guard let expandedIndex = currentExpandedIndexPath, let cell = view.subviews.first(where: { $0 is YourCustomCell }) as? YourCustomCell, let originalLayout = collectionView.layoutAttributesForItem(at: expandedIndex) else { return } // 转换原cell在CollectionView中的坐标到根视图 let originalFrameInVC = collectionView.convert(originalLayout.frame, to: view) UIView.animate(withDuration: 0.3, animations: { self.blurBackgroundView?.alpha = 0 cell.frame = originalFrameInVC cell.switchToFoldedState() }) { [weak self] _ in guard let self = self else { return } // 把cell移回CollectionView self.collectionView.addSubview(cell) cell.frame = originalLayout.frame // 清理资源 self.blurBackgroundView?.removeFromSuperview() self.blurBackgroundView = nil // 恢复滚动 self.collectionView.isScrollEnabled = true self.currentExpandedIndexPath = nil } }
内容的提问来源于stack exchange,提问作者Peter Hlavatík
相关产品推荐
相关产品推荐

