像这样的 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 单元格(Swift 4)
collectionview 单元格中的阴影与 swift 中的其他单元格的行为方式不同