我想像这样的应用程序调整单元格的大小:
如您所见,右边的单元格比左边的单元格短。为了达到同样的目的,我使用了以下代码:
var testI=1
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var cellHeight=287
if testI % 2 == 0 {
cellHeight=247
}
testI += 1
return CGSizeMake(CGFloat(176), CGFloat(cellHeight))
}
结果:
我不想要这些顶部空间。如何删除它们?我的收藏夹视图属性:
我该如何实现?
UICollectionViewFlowLayout
希望所讨论的单元格具有相同的高度(或宽度,取决于滚动方向),以便它可以正确地水平或垂直对齐它们。
Thus, you can't use that to get the above effect. You need to create your own layout class which will have similar properties as UICollectionViewFlowLayout
such as minimumInteritemSpacing
, sectionInset
etc. Besides, the new subclass (which I will name UserCollectionViewLayout
from now on) will be capable of generating all the layout attributes for each corresponding cell with a frame value having different height so that cells will be positioned just like in the image you've shared.
Before we begin, you should at least have a basic understanding about how to create custom layouts so I strongly recommend you to read this article for a fresh start.
First, we need to create our own UICollectionViewLayoutAttributes
class so we can store the height of the cell there for later use.
class UserCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
var height: CGFloat = 0.0
}
Layout classes sometimes copy UICollectionViewLayoutAttributes
when needed, so we need to override several methods to be able to pass height
to copied object when it happens:
override func copyWithZone(zone: NSZone) -> AnyObject {
let copy = super.copyWithZone(zone) as! UserCollectionViewLayoutAttributes
copy.height = height
return copy
}
override func isEqual(object: AnyObject?) -> Bool {
if let object = object as? UserCollectionViewLayoutAttributes {
if height != object.height {
return false
}
return super.isEqual(object)
}
return false
}
We are going to need a new protocol which will ask for delegate the height of each cell:
(Similar to UICollectionViewDelegateFlowLayout.sizeForItemAtIndexPath
)
protocol UserCollectionViewLayoutDelegate: UICollectionViewDelegate {
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UserCollectionViewLayout, heightForItemAtIndexPath indexPath: NSIndexPath) -> CGFloat
}
OK, everything we need on our way to defining the layout is there. So, let's start:
class UserCollectionViewLayout: UICollectionViewLayout {
private var contentHeight: CGFloat = 0.0
private var allAttributes = [UserCollectionViewLayoutAttributes]()
weak var delegate: UserCollectionViewLayoutDelegate!
var numberOfColumns: Int = 2
private var numberOfItems: Int {
return collectionView?.numberOfItemsInSection(0) ?? 0
}
private var contentWidth: CGFloat {
return CGRectGetWidth(collectionView?.bounds ?? CGRectZero)
}
private var itemWidth: CGFloat {
return round(contentWidth / CGFloat(numberOfColumns))
}
...
OK, let's explain the above properties piece by piece:
contentHeight
: Content height of collection view so it will know how to/where to scroll. Same as defining scrollView.contentSize.height
.allAttributes
: An array holding the attributes of each cell. It will be populated in prepareLayout()
method.numberOfColumns
: A value indicating how many cells will be shown at each row. It will be 2
in your example as you want to show 2 photos in a row.Let's continue with overriding some important methods:
override func collectionViewContentSize() -> CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override class func layoutAttributesClass() -> AnyClass {
return UserCollectionViewLayoutAttributes.self
}
The crucial part is to implement prepareLayout
correctly. In this method, we are going to populate all the layout attributes corresponding to each indexPath, and store them in allAttributes
for later use.
The layout looks like a 2D matrix of cells. However, height of each cell might be different. Thus, you should keep in mind that frame.origin.y
value might not be same for cells positioned in the same row:
override func prepareLayout() {
if allAttributes.isEmpty {
let xOffsets = (0..<numberOfColumns).map { index -> CGFloat in
return CGFloat(index) * itemWidth
}
var yOffsets = Array(count: numberOfColumns, repeatedValue: CGFloat(0))
var column = 0
for item in 0..<numberOfItems {
let indexPath = NSIndexPath(forItem: item, inSection: 0)
let attributes = UserCollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
let cellHeight = delegate.collectionView(collectionView!, layout: self, heightForItemAtIndexPath: indexPath)
let frame = CGRect(x: xOffsets[column], y: yOffsets[column], width: itemWidth, height: cellHeight)
attributes.frame = frame
attributes.height = cellHeight
allAttributes.append(attributes)
yOffsets[column] = yOffsets[column] + cellHeight
column = (column >= numberOfColumns - 1) ? 0 : column + 1
contentHeight = max(contentHeight, CGRectGetMaxY(frame))
}
}
}
override func invalidateLayout() {
allAttributes.removeAll()
contentHeight = 0
super.invalidateLayout()
}
We need to implement 2 more methods to let the layout know which layout attribute it should use upon displaying a cell which are:
Let's give it a shot:
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return cachedAttributes.filter { attribute -> Bool in
return CGRectIntersectsRect(rect, attribute.frame)
}
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
return cachedAttributes.filter { attribute -> Bool in
return attribute.indexPath == indexPath
}.first
}
OK, that's all for the layout part. Right now, we have a layout class capable of displaying cells with different height values in a single row. Now, we need to figure out how to pass height
value to actual cell object.
Luckily for us, it is really straightforward if you are organizing your cell content using AutoLayout
. There is a method named applyLayoutAttributes
in UICollectionViewCell
which enables you to make last minute changes on cell's layout.
class UserCell: UICollectionViewCell {
let userView = UIView(frame: CGRectZero)
private var heightLayoutConstraint: NSLayoutConstraint!
override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) {
super.applyLayoutAttributes(layoutAttributes)
let attributes = layoutAttributes as! UserCollectionViewLayoutAttributes
heightLayoutConstraint.constant = attributes.height
}
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = UIColor.whiteColor()
contentView.addSubview(userView)
userView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[userView]|",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: nil,
views: ["userView": userView])
)
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[userView]|",
options: NSLayoutFormatOptions.AlignAllCenterX,
metrics: nil,
views: ["userView": userView])
)
heightLayoutConstraint = NSLayoutConstraint(
item: self,
attribute: NSLayoutAttribute.Height,
relatedBy: NSLayoutRelation.Equal,
toItem: nil,
attribute: NSLayoutAttribute.NotAnAttribute,
multiplier: 0.0,
constant: 0.0
)
heightLayoutConstraint.priority = 500
contentView.addConstraint(heightLayoutConstraint)
}
}
Getting all of them together, here is the result:
I am sure you can add minimumInteritemSpacing
and sectionInset
properties just like in UICollectionViewFlowLayout
class in order to add some padding between cells.
(Hint: you should tweak cell's frame in prepareLayout
method accordingly.)
You can download the example project from here.
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句