嵌入UIScrollView的UICollectionView垂直滚动时消失问题求助
问题描述
开发时遇到异常:将基于UICollectionView实现的水平无限轮播组件嵌入垂直UIScrollView后,触发垂直滚动时UICollectionView会消失。移除外层垂直UIScrollView后,轮播功能正常,但业务需要保留垂直滚动功能。
原始简化代码:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { private var scrollView: UIScrollView! private var contentView: UIStackView! private var collectionView: UICollectionView! private var items = [UIColor.red, UIColor.green] private var currentIndex: Int = 0 override func viewDidLoad() { super.viewDidLoad() setupScrollView() setupCollectionView() } private func setupScrollView() { scrollView = UIScrollView() scrollView.showsVerticalScrollIndicator = true scrollView.alwaysBounceVertical = true scrollView.delegate = self view.addSubview(scrollView) scrollView.snp.makeConstraints { make in make.edges.equalToSuperview() } contentView = UIStackView() contentView.axis = .vertical contentView.alignment = .center scrollView.addSubview(contentView) contentView.snp.makeConstraints { make in make.edges.equalToSuperview() make.width.equalToSuperview() make.height.greaterThanOrEqualToSuperview() } } private func setupCollectionView() { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.minimumLineSpacing = 0 collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.register(CarouselCell.self, forCellWithReuseIdentifier: CarouselCell.reuseIdentifier) collectionView.dataSource = self collectionView.delegate = self collectionView.isPagingEnabled = true collectionView.showsHorizontalScrollIndicator = false collectionView.isMultipleTouchEnabled = true contentView.addArrangedSubview(collectionView) collectionView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.height.equalTo(200) } // 原代码未定义该视图,临时注释避免崩溃 // contentView.addArrangedSubview(horizontalStackView) // horizontalStackView.snp.makeConstraints { make in // make.top.equalTo(collectionView.snp.bottom).offset(20) // make.centerX.equalToSuperview() // make.bottom.equalToSuperview().offset(-20) // } // 移除多余空视图,避免破坏StackView布局逻辑 // contentView.addArrangedSubview(UIView()) // Initialize starting position DispatchQueue.main.async { self.collectionView.scrollToItem(at: IndexPath(item: self.items.count * 10, section: 0), at: .centeredHorizontally, animated: false) } } // MARK: - UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return items.count * 20 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CarouselCell.reuseIdentifier, for: indexPath) as! CarouselCell cell.cardView.backgroundColor = items[indexPath.item % items.count] return cell } // MARK: - UICollectionViewDelegateFlowLayout func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: collectionView.frame.width, height: collectionView.frame.height) } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { // 修复语法错误,仅处理轮播CollectionView的滚动事件 guard let collectionScrollView = scrollView as? UICollectionView, collectionScrollView === self.collectionView else { return } let pageWidth = collectionScrollView.frame.width let currentPage = collectionScrollView.contentOffset.x / pageWidth let numberOfItems = CGFloat(items.count * 20) let pageIndex = Int(currentPage) % items.count currentIndex = pageIndex if currentPage < 1 { collectionScrollView.setContentOffset(CGPoint(x: pageWidth * (numberOfItems + currentPage), y: 0), animated: false) } else if currentPage >= numberOfItems { collectionScrollView.setContentOffset(CGPoint(x: pageWidth * (currentPage - numberOfItems), y: 0), animated: false) } } } // Custom UICollectionViewCell class CarouselCell: UICollectionViewCell { static let reuseIdentifier = "CarouselCell" let cardView: UIView = { let view = UIView() view.backgroundColor = .white view.layer.cornerRadius = 12 view.layer.masksToBounds = true view.translatesAutoresizingMaskIntoConstraints = false return view }() override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { contentView.addSubview(cardView) // Add padding and set constraints for the cardView NSLayoutConstraint.activate([ cardView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), cardView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10), cardView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10), cardView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10) ]) } } extension ViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { // Add any additional logic for the vertical scroll view if needed } }
核心问题与修复方案
1. ScrollViewDelegate方法语法与逻辑错误
原代码scrollViewDidEndDecelerating存在语法错误(赋值符号使用错误),且未区分垂直scrollView和水平collectionView的滚动事件,导致错误处理垂直滚动的回调。修复后通过类型转换+实例匹配,仅处理轮播组件的滚动事件。
2. UICollectionView宽度约束缺失
外层UIStackView设置alignment = .center,若collectionView无明确宽度约束,会导致其宽度自适应异常,滚动时布局崩溃。修复时确保collectionView宽度与父视图一致,避免StackView的对齐规则干扰。
3. 无效布局元素干扰
原代码中未定义的horizontalStackView会直接导致崩溃,多余的空UIView会破坏contentView的高度计算逻辑,进而影响垂直scrollView的滚动区域。修复时注释/删除无效元素,保证布局链完整。
4. Cell尺寸计算异常
原代码使用collectionView.bounds.size获取cell尺寸,在视图布局完成前可能拿到无效值(如(0,0)),导致cell无法正常显示。修复后改为基于frame获取尺寸,保证布局完成后cell尺寸正确。
最终效果
修复后,垂直scrollView滚动时水平collectionView不会消失,轮播功能正常,同时保留垂直滚动交互。
内容的提问来源于stack exchange,提问作者Kristijan Delivuk

