我有一个使用流式布局的UICollectionView,每个单元格都是正方形。如何确定每行中每个单元格之间的间距?我找不到相应的设置。我在集合视图的nib文件中看到了最小间距属性,但我将其设置为0,单元格甚至不能够粘贴在一起。
还有其他的想法吗?
我有一个使用流式布局的UICollectionView,每个单元格都是正方形。如何确定每行中每个单元格之间的间距?我找不到相应的设置。我在集合视图的nib文件中看到了最小间距属性,但我将其设置为0,单元格甚至不能够粘贴在一起。
还有其他的想法吗?
参照 @matt 的方法,我修改了他的代码以确保项目始终左对齐。我发现,如果一个项目单独出现在一行上,流式布局会将其居中。我进行了以下更改来解决此问题。
只有当您的单元格宽度不同时,才会出现此情况,可能会导致以下布局。由于 UICollectionViewFlowLayout
的行为,最后一行总是左对齐,但问题在于任何行中单独存在的项目。
使用 @matt 的代码时,我看到了以下结果。
在该示例中,我们看到如果单元格独占一行,则会将其置于中心位置。下面的代码可确保您的集合视图看起来像这样。
#import "CWDLeftAlignedCollectionViewFlowLayout.h"
const NSInteger kMaxCellSpacing = 9;
@implementation CWDLeftAlignedCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
if (nil == attributes.representedElementKind) {
NSIndexPath* indexPath = attributes.indexPath;
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
}
}
return attributesToReturn;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* currentItemAttributes =
[super layoutAttributesForItemAtIndexPath:indexPath];
UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset];
CGRect currentFrame = currentItemAttributes.frame;
if (indexPath.item == 0) { // first item of section
currentFrame.origin.x = sectionInset.left; // first item of the section should always be left aligned
currentItemAttributes.frame = currentFrame;
return currentItemAttributes;
}
NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
CGFloat previousFrameRightPoint = CGRectGetMaxX(previousFrame) + kMaxCellSpacing;
CGRect strecthedCurrentFrame = CGRectMake(0,
currentFrame.origin.y,
self.collectionView.frame.size.width,
currentFrame.size.height);
if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
// the approach here is to take the current frame, left align it to the edge of the view
// then stretch it the width of the collection view, if it intersects with the previous frame then that means it
// is on the same line, otherwise it is on it's own new line
currentFrame.origin.x = sectionInset.left; // first item on the line should always be left aligned
currentItemAttributes.frame = currentFrame;
return currentItemAttributes;
}
currentFrame.origin.x = previousFrameRightPoint;
currentItemAttributes.frame = currentFrame;
return currentItemAttributes;
}
@end
UICollectionView
中的UICollectionViewCell
。触摸事件由委托方法处理。然后,它们使用可伸缩背景进行样式设置,除了蓝色背景,它只是单元格内的一张图片。据我所知,没有开源的东西,但是使用UICollectionView
相当简单,难点是让它们以这种方式对齐,而这里已经为您完成了 ;) - Chris WagnerkMaxCellSpacing
更改为3,并实现了- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
,它返回标签按钮的计算大小(是的,我正在制作与您屏幕截图相同的东西)。但是,我仍然在处理行间距方面遇到麻烦。当单元格之间的距离为3时,行之间的距离仍然约为10。您能告诉我如何影响它吗? - kokokolayoutAttributesForElementsInRect:
和layoutAttributesForItemAtIndexPath:
方法。- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* arr = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* atts in arr) {
if (nil == atts.representedElementKind) {
NSIndexPath* ip = atts.indexPath;
atts.frame = [self layoutAttributesForItemAtIndexPath:ip].frame;
}
}
return arr;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* atts =
[super layoutAttributesForItemAtIndexPath:indexPath];
if (indexPath.item == 0) // degenerate case 1, first item of section
return atts;
NSIndexPath* ipPrev =
[NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
CGFloat rightPrev = fPrev.origin.x + fPrev.size.width + 10;
if (atts.frame.origin.x <= rightPrev) // degenerate case 2, first item of line
return atts;
CGRect f = atts.frame;
f.origin.x = rightPrev;
atts.frame = f;
return atts;
}
这个很容易的原因是我们并没有真正进行布局的重活;我们正在利用UICollectionViewFlowLayout已经为我们完成的布局工作。它已经决定了每行放多少项;我们只需读取这些行并将项目推在一起,如果你明白我的意思的话。有几点需要考虑:
尝试在IB中更改最小间距,但将光标留在该字段中。请注意,Xcode不会立即将文档标记为已更改。 但是,当您单击其他字段时,Xcode会注意到文档已更改,并在文件导航器中标记它。因此,在进行更改后,请务必切换到其他字段。
在更改后保存storyboard / xib文件,并确保重新构建应用程序。很容易忽略这一步,然后你会想知道为什么更改似乎没有任何效果。
UICollectionViewFlowLayout
有一个minimumInteritemSpacing
属性,在IB中进行设置。但是,集合的代理也可以有一种方法来确定项目间距。该方法优先于布局的属性,因此如果在代理中实现它,则不会使用布局的属性。
请记住,那里的间距是最小间距。布局将使用该数字(无论它是来自属性还是委托方法)作为最小允许空间,但如果行上有剩余空间,则可能会使用更大的空间。因此,例如,如果将最小间距设置为0,则可能仍会看到项目之间的几个像素。如果您想要更精确地控制项目的间距,您可能应该使用不同的布局(可能是您自己创建的布局)。
minimumInteritemSpacing
和minimumLineSpacing
。 - iDevUICollectionViewDelegateFlowLayout
协议,集合视图的代理可以采用以支持流式布局。请参见方法 – collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
。 - Caleb只需像这样使用取模运算(我将我的minimumInteritemSpacing值同时用作最大值):
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* currentItemAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
NSInteger numberOfItemsPerLine = floor([self collectionViewContentSize].width / [self itemSize].width);
if (indexPath.item % numberOfItemsPerLine != 0)
{
NSInteger cellIndexInLine = (indexPath.item % numberOfItemsPerLine);
CGRect itemFrame = [currentItemAttributes frame];
itemFrame.origin.x = ([self itemSize].width * cellIndexInLine) + ([self minimumInteritemSpacing] * cellIndexInLine);
currentItemAttributes.frame = itemFrame;
}
return currentItemAttributes;
}
调整UICollectionViewFlowLayout子类中的layoutAttributesForElementsInRect:方法是实现左对齐的简单方法:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *allLayoutAttributes = [super layoutAttributesForElementsInRect:rect];
CGRect prevFrame = CGRectMake(-FLT_MAX, -FLT_MAX, 0, 0);
for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes)
{
//fix blur
CGRect theFrame = CGRectIntegral(layoutAttributes.frame);
//left justify
if(prevFrame.origin.x > -FLT_MAX &&
prevFrame.origin.y >= theFrame.origin.y &&
prevFrame.origin.y <= theFrame.origin.y) //workaround for float == warning
{
theFrame.origin.x = prevFrame.origin.x +
prevFrame.size.width +
EXACT_SPACE_BETWEEN_ITEMS;
}
prevFrame = theFrame;
layoutAttributes.frame = theFrame;
}
return allLayoutAttributes;
}
干净利落的Swift解决方案,源自于演变历程:
open class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout {
open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return super.layoutAttributesForElements(in: rect)?.map { $0.representedElementKind == nil ? layoutAttributesForItem(at: $0.indexPath)! : $0 }
}
open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let currentItemAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes,
collectionView != nil else {
// should never happen
return nil
}
// if the current frame, once stretched to the full row intersects the previous frame then they are on the same row
if indexPath.item != 0,
let previousFrame = layoutAttributesForItem(at: IndexPath(item: indexPath.item - 1, section: indexPath.section))?.frame,
currentItemAttributes.frame.intersects(CGRect(x: -.infinity, y: previousFrame.origin.y, width: .infinity, height: previousFrame.size.height)) {
// the next item on a line
currentItemAttributes.frame.origin.x = previousFrame.origin.x + previousFrame.size.width + evaluatedMinimumInteritemSpacingForSection(at: indexPath.section)
} else {
// the first item on a line
currentItemAttributes.frame.origin.x = evaluatedSectionInsetForSection(at: indexPath.section).left
}
return currentItemAttributes
}
func evaluatedMinimumInteritemSpacingForSection(at section: NSInteger) -> CGFloat {
return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingForSectionAt: section) ?? minimumInteritemSpacing
}
func evaluatedSectionInsetForSection(at index: NSInteger) -> UIEdgeInsets {
return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, insetForSectionAt: index) ?? sectionInset
}
}
用法: 项目之间的间距由代理方法 collectionView(_:layout:minimumInteritemSpacingForSectionAt:)
决定。
我把它放在了 Github 上,https://github.com/Coeur/UICollectionViewLeftAlignedLayout,在那里我实际上添加了一个支持水平和垂直两个滚动方向的功能。
class PazLeftAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout {
var maxCellSpacing = 14.0
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
if var attributesToReturn = super.layoutAttributesForElementsInRect(rect) as? Array<UICollectionViewLayoutAttributes> {
for attributes in attributesToReturn {
if attributes.representedElementKind == nil {
let indexPath = attributes.indexPath
attributes.frame = self.layoutAttributesForItemAtIndexPath(indexPath).frame;
}
}
return attributesToReturn;
}
return super.layoutAttributesForElementsInRect(rect)
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let currentItemAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)
if let collectionViewFlowLayout = self.collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
let sectionInset = collectionViewFlowLayout.sectionInset
if (indexPath.item == 0) { // first item of section
var frame = currentItemAttributes.frame;
frame.origin.x = sectionInset.left; // first item of the section should always be left aligned
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
let previousIndexPath = NSIndexPath(forItem:indexPath.item-1, inSection:indexPath.section)
let previousFrame = self.layoutAttributesForItemAtIndexPath(previousIndexPath).frame;
let previousFrameRightPoint = Double(previousFrame.origin.x) + Double(previousFrame.size.width) + self.maxCellSpacing
let currentFrame = currentItemAttributes.frame
var width : CGFloat = 0.0
if let collectionViewWidth = self.collectionView?.frame.size.width {
width = collectionViewWidth
}
let strecthedCurrentFrame = CGRectMake(0,
currentFrame.origin.y,
width,
currentFrame.size.height);
if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
// the approach here is to take the current frame, left align it to the edge of the view
// then stretch it the width of the collection view, if it intersects with the previous frame then that means it
// is on the same line, otherwise it is on it's own new line
var frame = currentItemAttributes.frame;
frame.origin.x = sectionInset.left; // first item on the line should always be left aligned
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
var frame = currentItemAttributes.frame;
frame.origin.x = CGFloat(previousFrameRightPoint)
currentItemAttributes.frame = frame;
}
return currentItemAttributes;
}
}
要使用它,请按照以下步骤进行:
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.collectionViewLayout = self.layout
}
var layout : PazLeftAlignedCollectionViewFlowLayout {
var layout = PazLeftAlignedCollectionViewFlowLayout()
layout.itemSize = CGSizeMake(220.0, 230.0)
layout.minimumLineSpacing = 12.0
return layout
}
UICollectionViewFlowLayout
存在一个问题,即它将单元格应用了一种合理的对齐方式:一行中的第一个单元格左对齐,一行中的最后一个单元格右对齐,而在它们之间的所有其他单元格都平均分布,且间距大于minimumInteritemSpacing
。UICollectionViewFlowLayout
。结果是您可以得到一个将单元格左对齐的布局。另一种有效的解决方案是使用一个常量间距来分配单元格并将其右对齐。
UICollectionViewFlowLayout
子类,遵循与matt和Chris Wagner建议的类似思路,可以将单元格对齐到:
⬅︎ 左侧:
或者 ➡︎ 右侧: 您可以从这里简单地下载它,将布局文件添加到您的项目中,并将AlignedCollectionViewFlowLayout
设置为您的集合视图的布局类: +---------+----------------------------------------------------------------+---------+
| | | |
| | +------------+ | |
| | | | | |
| section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
| inset | |intersection| | | line rect | inset |
| |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| |
| (left) | | | current item | (right) |
| | +------------+ | |
| | previous item | |
+---------+----------------------------------------------------------------+---------+
...你需要做反向操作,即检查具有索引i+1的下一个单元格。
以下是基于Chris Wagner答案的更简洁的Swift版本,适合对此感兴趣的人:
class AlignLeftFlowLayout: UICollectionViewFlowLayout {
var maximumCellSpacing = CGFloat(9.0)
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
let attributesToReturn = super.layoutAttributesForElementsInRect(rect) as? [UICollectionViewLayoutAttributes]
for attributes in attributesToReturn ?? [] {
if attributes.representedElementKind == nil {
attributes.frame = self.layoutAttributesForItemAtIndexPath(attributes.indexPath).frame
}
}
return attributesToReturn
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let curAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)
let sectionInset = (self.collectionView?.collectionViewLayout as UICollectionViewFlowLayout).sectionInset
if indexPath.item == 0 {
let f = curAttributes.frame
curAttributes.frame = CGRectMake(sectionInset.left, f.origin.y, f.size.width, f.size.height)
return curAttributes
}
let prevIndexPath = NSIndexPath(forItem: indexPath.item-1, inSection: indexPath.section)
let prevFrame = self.layoutAttributesForItemAtIndexPath(prevIndexPath).frame
let prevFrameRightPoint = prevFrame.origin.x + prevFrame.size.width + maximumCellSpacing
let curFrame = curAttributes.frame
let stretchedCurFrame = CGRectMake(0, curFrame.origin.y, self.collectionView!.frame.size.width, curFrame.size.height)
if CGRectIntersectsRect(prevFrame, stretchedCurFrame) {
curAttributes.frame = CGRectMake(prevFrameRightPoint, curFrame.origin.y, curFrame.size.width, curFrame.size.height)
} else {
curAttributes.frame = CGRectMake(sectionInset.left, curFrame.origin.y, curFrame.size.width, curFrame.size.height)
}
return curAttributes
}
}
CGRectIntersectsRect
很直观且易于借鉴。@matt的CGFloat rightPrev = fPrev.origin.x + fPrev.size.width + 10;
更好。结果相同,而且做得更少。比较Y位置更加简洁。 - dengST30