像这样的 CollectionView 堆叠单元如何存档,用于重叠单元,如钱包

Posted

技术标签:

【中文标题】像这样的 CollectionView 堆叠单元如何存档,用于重叠单元,如钱包【英文标题】:How archive like this CollectionView stacked Cell for Overlapping Cell Like Wallet 【发布时间】:2020-06-23 17:28:43 【问题描述】:

我想要在点击特定单元格时显示展开

let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = -UIScreen.main.bounds.width/2.08

我要展开单元格?

朋友有任何建议和需要对我们有帮助 谢谢

【问题讨论】:

不认识任何人?? 【参考方案1】:

通常我们使用UICollectionViewFlowLayout,一旦给定了item的大小和间距,就确定了item的frame。

待办事项

CollectionView 堆叠单元格,用于像钱包一样重叠单元格

您需要为UICollectionView 中的每个项目提供所需的框架。

通过自定义UICollectionViewLayout 来完成它,并使用您自己的UICollectionViewLayout 子类。

最终效果如下图

基本的东西:

自定义布局很简单:

override public func prepare() ,

计算每个项目的帧数,

并将项目的框架放入其容器中,您的自定义UICollectionViewLayoutAttributes

override public func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? ,

检索指定索引路径中具有相应单元格的项目的布局信息。

为项目布局分配您准备好的自定义UICollectionViewLayoutAttributes

override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? ,

检索指定矩形中所有单元格和视图的布局属性。

在这种情况下没有补充视图和装饰视图,只是为了处理项目。

UICollectionView 布局分配您准备好的自定义UICollectionViewLayoutAttributes

关键点:

你有两种布局状态。

初始状态,和未选择状态相同,当没有选择卡时。​​li>

前面的卡片部分隐藏,最后一张卡片完全显示。

自定义UICollectionViewLayoutAttributes,用isExpand记录是否有单元格被选中。

isExpand 用于添加UIPanGestureRecognizer 的单元格

class CardLayoutAttributes: UICollectionViewLayoutAttributes 
    var isExpand = false

    override func copy(with zone: NSZone? = nil) -> Any 
        let attribute = super.copy(with: zone) as! CardLayoutAttributes
        attribute.isExpand = isExpand
        return attribute
    

未选中状态的框架计算很简单。

设置第一项的框架,

然后是第二个...

y: titleHeight * CGFloat(index),一切正常

 fileprivate func setNoSelect(attribute:CardLayoutAttributes) 
        guard let collection = collectionView else 
            return
        
        let noneIdx = Int(collection.contentOffset.y/titleHeight)
        if noneIdx < 0 
            return
        
        attribute.isExpand = false
        let index = attribute.zIndex
        var currentFrame = CGRect(x: collection.frame.origin.x, y: titleHeight * CGFloat(index), width: cellSize.width, height: cellSize.height)
        if index == noneIdx
            attribute.frame = CGRect(x: currentFrame.origin.x, y: collection.contentOffset.y, width: cellSize.width, height: cellSize.height)
        
        else if index <= noneIdx, currentFrame.maxY > collection.contentOffset.y
            currentFrame.origin.y -= (currentFrame.maxY - collection.contentOffset.y )
            attribute.frame = currentFrame
        
        else 
            attribute.frame = currentFrame
        
    
选中状态,选中一个item,item被展开,其他人会为它腾出空间。

逻辑是把选中的item放在中间,y的位置很重要,collection.contentOffset.y + offsetSelected,

中心项的框架已知,然后计算两侧。

一边是从(selectedIdx-1)到0的item,计算item的frame。

另一边是从(selectedIdx+1)到最终index的item,也计算item的frame。

    fileprivate func calculate(for attributes: [CardLayoutAttributes],  choose selectedIP: IndexPath) -> [CGRect]
        
        guard let collection = collectionView else 
            return []
        
        let noneIdx = Int(collection.contentOffset.y / titleHeight)
        if noneIdx < 0 
            return []
        
        let x = collection.frame.origin.x
        
        var selectedIdx = 0
        for attr in attributes
            if attr.indexPath == selectedIP
                break
            
            selectedIdx += 1
        
        
        var frames = [CGRect](repeating: .zero, count: attributes.count)
        
        // Edit here
        let offsetSelected: CGFloat = 100
        let marginBottomSelected: CGFloat = 10
        frames[selectedIdx] = CGRect(x: x, y: collection.contentOffset.y + offsetSelected, width: cellSize.width, height: cellSize.height)
        if selectedIdx > 0
            for i in 0...(selectedIdx-1)
                frames[selectedIdx - i - 1] = CGRect(x: x, y: frames[selectedIdx].origin.y - titleHeight * CGFloat(i + 1), width: cellSize.width, height: cellSize.height)
            
        
        if selectedIdx < (attributes.count - 1)
            for i in (selectedIdx + 1)...(attributes.count - 1)
                frames[i] = CGRect(x: x, y: frames[selectedIdx].origin.y + marginBottomSelected + titleHeight * CGFloat(i - selectedIdx - 1) + cellSize.height, width: cellSize.width, height: cellSize.height)
            
        
        
        
        return frames
        
    

当有一个项目被选中时,你应该刷新自定义布局。

拨打invalidateLayout()

使当前布局无效并触发布局更新。

    fileprivate var _selectPath: IndexPath? 
        didSet 
            self.collectionView!.isScrollEnabled = (_selectPath == nil)
        
    
    
    
    
    public var selectPath: IndexPath? 
        set 
            _selectPath = (_selectPath == newValue) ? nil : newValue
            self.collectionView?.performBatchUpdates(
                self.invalidateLayout()
            , completion: nil)
         get 
            return _selectPath
        
    

还有一件事,the sample demo in github

【讨论】:

感谢您的精彩演示,它看起来很棒,但我需要这个功能,您能解决这个问题吗? link 您的问题是什么。自定义集合视图布局并不难。具有选中状态的自定义集合视图布局也不难。只需按照我的演示,做一点改变, 我尝试了你的演示,当我点击卡片时它会展开它很好但滚动不起作用,我尝试解决但我不能 class CustomLayout: UICollectionViewLayout override public var collectionViewContentSize: CGSize CGSize(width: collectionViewWidth, height: contentHeight) 滚动内容取决于contentHeight 。只是尝试修改它的值【参考方案2】:

这里是重叠布局,minimumLineSpacing负数

如果点击卡片,它应该被展开并向下和向上滚动卡片 不应该像钱包一样透露任何信息

class OverlappedCustomFlowLayout: UICollectionViewFlowLayout 
    override func prepare() 
        super.prepare()
        // This allows us to make intersection and overlapping
        // A negative number implies overlapping whereas positive implies space between the adjacent edges of two cells.
        minimumLineSpacing = -100
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
        let layoutAttributes = super.layoutAttributesForElements(in: rect)
        for currentLayoutAttributes: UICollectionViewLayoutAttributes in layoutAttributes! 
            // zIndex - Specifies the item’s position on the z-axis.
            // Unlike a layer's zPosition, changing zIndex allows us to change not only layer position,
            // but tapping/UI interaction logic too as it moves the whole item.
            currentLayoutAttributes.zIndex = currentLayoutAttributes.indexPath.row + 1
        
        return layoutAttributes
    

【讨论】:

【参考方案3】:

你可以试试下面的思路

布局部分:

var selectedIdx: Int?



override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
        let layoutAttributes = super.layoutAttributesForElements(in: rect)
        var originY: CGFloat = 0
        let defaultOffsetY: CGFloat = 80
        if let idx = selectedIdx
            // expanded layout
            for i in 0..<cnt
                 // frame the attribute
                 if i == idx + 1
                       // to expand is to make room for the rest items
                       originY += 400
                 
                 else
                       originY += defaultOffsetY
                 
            
        
        else
            // default layout 
            for i in 0..<cnt 
                 // frame the attribute
                 originY += defaultOffsetY
            
        
        return layoutAttributes
    

触发展开动作

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
        // edit the state
        layout.selectedIdx = indexPath.item
        // trigger refreshing the collectionView's layout
        collectionView.reloadData()
 

【讨论】:

感谢分享,如何从数组列表中展开选中的属性? 展开就是刷新集合布局。不是滚动的东西【参考方案4】:

我试过了,谢谢大家

class CardLayout: UICollectionViewLayout 
    var contentHeight: CGFloat = 0.0
    var cachedAttributes = [UICollectionViewLayoutAttributes]()
    var nextIndexPath: Int?
    override init() 
        super.init()
    
    
    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    
    
    override var collectionViewContentSize: CGSize 
        var size = super.collectionViewContentSize
        let collection = collectionView!
        size.width = collection.bounds.size.width
        if let _ = FlowLayoutAttributes.shared.cellIndex
            size.height = contentHeight+UIScreen.main.bounds.width/2+38
        else
            size.height = contentHeight
        
        print("Contend",contentHeight)
        return size
    
    
    
    func reloadData()
        self.cachedAttributes = [UICollectionViewLayoutAttributes]()
    
    
    override func prepare() 
        cachedAttributes.removeAll()
        guard let numberOfItems = collectionView?.numberOfItems(inSection: 0) else 
            return
        
        for index in 0..<numberOfItems 
            let layout = UICollectionViewLayoutAttributes(forCellWith: IndexPath(row: index, section: 0))
            layout.frame = frameFor(index: index)
            
            if let indexExpand = FlowLayoutAttributes.shared.cellIndex, indexExpand ==  index 
                self.nextIndexPath = index+1
                contentHeight = CGFloat(CGFloat(numberOfItems)*getCardSize())+UIScreen.main.bounds.width/2+38*2
            else
                contentHeight = CGFloat(CGFloat(numberOfItems)*getCardSize())+UIScreen.main.bounds.width/2+38
            
            layout.zIndex = index
            layout.isHidden = false
            
            cachedAttributes.append(layout)
        
    
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
        
        var layoutAttributes = [UICollectionViewLayoutAttributes]()
        
        for attributes in cachedAttributes 
            if attributes.frame.intersects(rect) 
                layoutAttributes.append(cachedAttributes[attributes.indexPath.item])
            
            
        
        return layoutAttributes
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool 
        return true
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? 
        return cachedAttributes[indexPath.item]
    
    func frameFor(index: Int) -> CGRect 
        var frame = CGRect(origin: CGPoint(x: CGFloat(8), y:0), size: CGSize(width: UIScreen.main.bounds.width - CGFloat(8 + 8), height: CGFloat(UIScreen.main.bounds.width/2+38)))
        var frameOrigin = frame.origin
        if let indexExpand = FlowLayoutAttributes.shared.cellIndex
            if index > 0 
                if indexExpand < index 
                    let spacesHeight = CGFloat((getCardSize() * CGFloat(index)))+UIScreen.main.bounds.width/2+38-getCardSize()/2
                    frameOrigin.y = spacesHeight
                else
                    frameOrigin.y = CGFloat((getCardSize() * CGFloat(index)))
                
            
        else
            if index > 0 
                frameOrigin.y = CGFloat((getCardSize() * CGFloat(index)))
            
        
        frame.origin = frameOrigin
        return frame
    
    func getCardSize()-> CGFloat
        if UIDevice().userInterfaceIdiom == .phone 
            switch UIScreen.main.nativeBounds.height 
            case 1136:
                print("iPhone 5 or 5S or 5C")
                return  45.25
            case 1334:
                print("iPhone 6/6S/7/8")
                return  45.25
            case 1920, 2208:
                print("iPhone 6+/6S+/7+/8+")
                return  46
            case 2436:
                print("iPhone X/XS/11 Pro")
                return  45.25
            case 2688:
                print("iPhone XS Max/11 Pro Max")
                return  46
            case 1792:
                print("iPhone XR/ 11 ")
                return  46
            case 2532:
                print("iPhone 12/ iPhone 12 Pro")
                return  45.50
            case 2778:
                print("iPhone 12 Pro Max")
                return  46.2
            default:
                return  46.2
            
        else
            return CGFloat(46.2)
        
    
    

【讨论】:

以上是关于像这样的 CollectionView 堆叠单元如何存档,用于重叠单元,如钱包的主要内容,如果未能解决你的问题,请参考以下文章

CollectionView 中的自定义单元格重新排序行为

更改 CollectionView 单元格的垂直间距

用动画隐藏上方的 CollectionView 单元格(Swift 4)

collectionview 单元格中的阴影与 swift 中的其他单元格的行为方式不同

多个tableview单元格内的Collectionview,如何处理选择?

当它从一个 collectionView 移动到另一个时,如何实现单元格动画?