You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌入UIScrollView的UICollectionView垂直滚动时消失问题求助

问题:垂直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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 21:27:02