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

嵌套Stack View的Collection View:点击按钮带动画隐藏内层Stack View

实现可折叠Collection View Cell的带动画效果

嘿,我来帮你搞定这个可折叠动画的问题!你的布局结构没问题,现在核心是要把直接设置isHidden的逻辑改成带平滑动画的方式,同时还要让CollectionView感知到Cell高度的变化,实现整体流畅的折叠/展开效果。下面分步骤给你拆解:

第一步:修改Collection View Cell的代码

首先要给按钮添加点击事件,用动画切换内层StackView的显示状态,同时通过闭包通知Controller更新布局:

class YourCollectionViewCell: UICollectionViewCell {
    // 假设你已经连接了这些IBOutlet
    @IBOutlet weak var filterTitleButton: UIButton!
    @IBOutlet weak var expandableStackView: UIStackView!
    
    var filter: Filter!
    // 用来通知Controller状态变化的闭包
    var didToggleExpansion: (() -> Void)?

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupButtonTapAction()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupButtonTapAction()
    }

    private func setupButtonTapAction() {
        filterTitleButton.addTarget(self, action: #selector(toggleExpansion), for: .touchUpInside)
    }

    @objc private func toggleExpansion() {
        // 用0.3秒的动画切换隐藏状态,强制更新布局让动画生效
        UIView.animate(withDuration: 0.3) {
            self.expandableStackView.isHidden = !self.expandableStackView.isHidden
            self.contentView.layoutIfNeeded()
        }
        // 通知Controller需要更新Cell高度
        didToggleExpansion?()
    }

    func setupUI(isExpanded: Bool, filter: Filter) {
        self.filter = filter
        filterTitleButton.setTitle(filter.filterTitle, for: .normal)
        // 初始化状态时直接设置,不需要动画
        expandableStackView.isHidden = !isExpanded
    }
}

第二步:在Controller中管理折叠状态并更新CollectionView

你需要维护一个数组记录每个Cell的展开/折叠状态,当按钮被点击时,切换状态并让CollectionView带动画更新Cell高度:

class YourViewController: UICollectionViewController {
    var filters: [Filter] = []
    // 用数组记录每个Cell的展开状态,默认全部折叠(false)
    private var expandedStates: [Bool] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        // 初始化状态数组(假设filters已经提前赋值)
        expandedStates = Array(repeating: false, count: filters.count)
        
        // 配置CollectionView的FlowLayout,开启自动高度计算
        if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
            flowLayout.minimumLineSpacing = 8 // 可选:设置Cell之间的间距
        }
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return filters.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCellIdentifier", for: indexPath) as! YourCollectionViewCell
        let isExpanded = expandedStates[indexPath.item]
        
        cell.setupUI(isExpanded: isExpanded, filter: filters[indexPath.item])
        // 设置闭包,处理按钮点击后的状态更新
        cell.didToggleExpansion = { [weak self] in
            guard let self = self else { return }
            // 切换当前Cell的展开状态
            self.expandedStates[indexPath.item] = !self.expandedStates[indexPath.item]
            // 带动画更新CollectionView布局,让Cell高度平滑变化
            self.collectionView.performBatchUpdates(nil, completion: nil)
        }
        
        return cell
    }
}

第三步:确保Auto Layout约束正确

这是关键!你的Cell必须有完整的约束链,让CollectionView能自动计算高度:

  • 外层StackView的top、leading、trailing、bottom都要和ContentView的对应边缘对齐,没有歧义。
  • 内层StackView的约束也要完整,确保它显示/隐藏时,外层StackView的高度能自动调整。

注意事项

  • 不要在setupUI里用UIView.performWithoutAnimation包裹状态设置,除非是初始化场景;动画要放在按钮点击的方法里。
  • 使用UICollectionViewFlowLayout.automaticSize是让Cell高度自动适配的核心,不需要手动计算高度。
  • performBatchUpdates会让CollectionView的布局更新带有动画,和Cell内部的StackView动画同步,整体效果更流畅。

内容的提问来源于stack exchange,提问作者Mohanraj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 09:22:28