美文网首页iOS高级开发iOS猛码计划ios开发学习
UICollectionView中Cell左对齐 居中 右对齐

UICollectionView中Cell左对齐 居中 右对齐

作者: 哈南 | 来源:发表于2016-06-14 16:22 被阅读8566次

最近在开发软件的时候被产品经理要求,要让UICollectionView上面的cell之间的距离要被固定,但是cell得宽度不一定,所以一行有几个cell其实不固定,所以其实cell之间的间距不一定,cell之间最终距离不是你设置的,而是由系统把一行中cell宽度减去,剩下的空白格平均分配的.所以我一开始写出来的效果看起来不是很规则


屏幕快照

所以喽,产品经理不同意这样,说看起来不是很规则,然后只能去网上找,做种找到了别人的一些例子,最终弄出来一个貌似还能看的规则。


屏幕快照

最近需求还没下来,所以把很久之前的这个代码给重新折腾了一下,支持靠左,居中,靠右,等间距对齐。整体逻辑也比之前清晰了一些。

靠左等间距.png 居中等间距.png 靠右等间距.png
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger,AlignType){
    AlignWithLeft,
    AlignWithCenter,
    AlignWithRight
};
@interface JYEqualCellSpaceFlowLayout : UICollectionViewFlowLayout
//两个Cell之间的距离
@property (nonatomic,assign)CGFloat betweenOfCell;
//cell对齐方式
@property (nonatomic,assign)AlignType cellType;

-(instancetype)initWthType : (AlignType)cellType;
//全能初始化方法 其他方式初始化最终都会�走到这里
-(instancetype)initWithType:(AlignType) cellType betweenOfCell:(CGFloat)betweenOfCell;

@end
#import "JYEqualCellSpaceFlowLayout.h"
@interface JYEqualCellSpaceFlowLayout(){
    //在居中对齐的时候需要知道这行所有cell的宽度总和
    CGFloat _sumCellWidth ;
}
@end

@implementation JYEqualCellSpaceFlowLayout
-(instancetype)init{
    return [self initWithType:AlignWithCenter betweenOfCell:5.0];
}
-(void)setBetweenOfCell:(CGFloat)betweenOfCell{
    _betweenOfCell = betweenOfCell;
    self.minimumInteritemSpacing = betweenOfCell;
}
-(instancetype)initWthType:(AlignType)cellType{
    return [self initWithType:cellType betweenOfCell:5.0];
}
-(instancetype)initWithType:(AlignType)cellType betweenOfCell:(CGFloat)betweenOfCell{
    self = [super init];
    if (self){
        self.scrollDirection = UICollectionViewScrollDirectionVertical;
        self.minimumLineSpacing = 5;
        self.minimumInteritemSpacing = 5;
        self.sectionInset = UIEdgeInsetsMake(5, 5, 5, 5);
        _betweenOfCell = betweenOfCell;
        _cellType = cellType;
    }
    return self;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray * layoutAttributes_t = [super layoutAttributesForElementsInRect:rect];
    NSArray * layoutAttributes = [[NSArray alloc]initWithArray:layoutAttributes_t copyItems:YES];
    //用来临时存放一行的Cell数组
    NSMutableArray * layoutAttributesTemp = [[NSMutableArray alloc]init];
    for (NSUInteger index = 0; index < layoutAttributes.count ; index++) {
        
        UICollectionViewLayoutAttributes *currentAttr = layoutAttributes[index]; // 当前cell的位置信息
        UICollectionViewLayoutAttributes *previousAttr = index == 0 ? nil : layoutAttributes[index-1]; // 上一个cell 的位置信
        UICollectionViewLayoutAttributes *nextAttr = index + 1 == layoutAttributes.count ?
        nil : layoutAttributes[index+1];//下一个cell 位置信息
        
        //加入临时数组
        [layoutAttributesTemp addObject:currentAttr];
        _sumCellWidth += currentAttr.frame.size.width;
        
        CGFloat previousY = previousAttr == nil ? 0 : CGRectGetMaxY(previousAttr.frame);
        CGFloat currentY = CGRectGetMaxY(currentAttr.frame);
        CGFloat nextY = nextAttr == nil ? 0 : CGRectGetMaxY(nextAttr.frame);
        //如果当前cell是单独一行
        if (currentY != previousY && currentY != nextY){
            if ([currentAttr.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
                [layoutAttributesTemp removeAllObjects];
                _sumCellWidth = 0.0;
            }else if ([currentAttr.representedElementKind isEqualToString:UICollectionElementKindSectionFooter]){
                [layoutAttributesTemp removeAllObjects];
                _sumCellWidth = 0.0;
            }else{
                [self setCellFrameWith:layoutAttributesTemp];
            }
        }
        //如果下一个cell在本行,这开始调整Frame位置
        else if( currentY != nextY) {
            [self setCellFrameWith:layoutAttributesTemp];
        }
    }
    return layoutAttributes;
}
//调整属于同一行的cell的位置frame
-(void)setCellFrameWith:(NSMutableArray*)layoutAttributes{
    CGFloat nowWidth = 0.0;
    switch (_cellType) {
        case AlignWithLeft:
            nowWidth = self.sectionInset.left;
            for (UICollectionViewLayoutAttributes * attributes in layoutAttributes) {
                CGRect nowFrame = attributes.frame;
                nowFrame.origin.x = nowWidth;
                attributes.frame = nowFrame;
                nowWidth += nowFrame.size.width + self.betweenOfCell;
            }
            _sumCellWidth = 0.0;
            [layoutAttributes removeAllObjects];
            break;
            
        case AlignWithCenter:
            nowWidth = (self.collectionView.frame.size.width - _sumCellWidth - ((layoutAttributes.count - 1) * _betweenOfCell)) / 2;
            for (UICollectionViewLayoutAttributes * attributes in layoutAttributes) {
                CGRect nowFrame = attributes.frame;
                nowFrame.origin.x = nowWidth;
                attributes.frame = nowFrame;
                nowWidth += nowFrame.size.width + self.betweenOfCell;
            }
            _sumCellWidth = 0.0;
            [layoutAttributes removeAllObjects];
            break;
            
        case AlignWithRight:
            nowWidth = self.collectionView.frame.size.width - self.sectionInset.right;
            for (NSInteger index = layoutAttributes.count - 1 ; index >= 0 ; index-- ) {
                UICollectionViewLayoutAttributes * attributes = layoutAttributes[index];
                CGRect nowFrame = attributes.frame;
                nowFrame.origin.x = nowWidth - nowFrame.size.width;
                attributes.frame = nowFrame;
                nowWidth = nowWidth - nowFrame.size.width - _betweenOfCell;
            }
            _sumCellWidth = 0.0;
            [layoutAttributes removeAllObjects];
            break;
    }
}

@end

OC版本的Demo在这里
------------------------------------------------------分界线-----------------------------------------------------
下面是Swift版本的代码

import UIKit
enum AlignType : NSInteger {
    case left = 0
    case center = 1
    case right = 2
}
class JYEqualCellSpaceFlowLayout: UICollectionViewFlowLayout {
    //两个Cell之间的距离
    var betweenOfCell : CGFloat{
        didSet{
            self.minimumInteritemSpacing = betweenOfCell
        }
    }
    //cell对齐方式
    var cellType : AlignType = AlignType.center
    //在居中对齐的时候需要知道这行所有cell的宽度总和
    var sumCellWidth : CGFloat = 0.0
    
    override init() {
        betweenOfCell = 5.0
        super.init()
        scrollDirection = UICollectionViewScrollDirection.vertical
        minimumLineSpacing = 5
        sectionInset = UIEdgeInsetsMake(5, 5, 5, 5)
    }
    convenience init(_ cellType:AlignType){
        self.init()
        self.cellType = cellType
    }
    convenience init(_ cellType: AlignType, _ betweenOfCell: CGFloat){
        self.init()
        self.cellType = cellType
        self.betweenOfCell = betweenOfCell
    }
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        let layoutAttributes_super : [UICollectionViewLayoutAttributes] = super.layoutAttributesForElements(in: rect) ?? [UICollectionViewLayoutAttributes]()
        let layoutAttributes:[UICollectionViewLayoutAttributes] = NSArray(array: layoutAttributes_super, copyItems:true)as! [UICollectionViewLayoutAttributes]
        var layoutAttributes_t : [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()
        for index in 0..<layoutAttributes.count{
            
            let currentAttr = layoutAttributes[index]
            let previousAttr = index == 0 ? nil : layoutAttributes[index-1]
            let nextAttr = index + 1 == layoutAttributes.count ?
                nil : layoutAttributes[index+1]
            
            layoutAttributes_t.append(currentAttr)
            sumCellWidth += currentAttr.frame.size.width
            
            let previousY :CGFloat = previousAttr == nil ? 0 : previousAttr!.frame.maxY
            let currentY :CGFloat = currentAttr.frame.maxY
            let nextY:CGFloat = nextAttr == nil ? 0 : nextAttr!.frame.maxY
            
            if currentY != previousY && currentY != nextY{
                if currentAttr.representedElementKind == UICollectionElementKindSectionHeader{
                    layoutAttributes_t.removeAll()
                    sumCellWidth = 0.0
                }else if currentAttr.representedElementKind == UICollectionElementKindSectionFooter{
                    layoutAttributes_t.removeAll()
                    sumCellWidth = 0.0
                }else{
                    self.setCellFrame(with: layoutAttributes_t)
                    layoutAttributes_t.removeAll()
                    sumCellWidth = 0.0
                }
            }else if currentY != nextY{
                self.setCellFrame(with: layoutAttributes_t)
                layoutAttributes_t.removeAll()
                sumCellWidth = 0.0
            }
        }
        return layoutAttributes
    }
    
    /// 调整Cell的Frame
    ///
    /// - Parameter layoutAttributes: layoutAttribute 数组
    func setCellFrame(with layoutAttributes : [UICollectionViewLayoutAttributes]){
        var nowWidth : CGFloat = 0.0
        switch cellType {
        case AlignType.left:
            nowWidth = self.sectionInset.left
            for attributes in layoutAttributes{
                var nowFrame = attributes.frame
                nowFrame.origin.x = nowWidth
                attributes.frame = nowFrame
                nowWidth += nowFrame.size.width + self.betweenOfCell
            }
            break;
        case AlignType.center:
            nowWidth = (self.collectionView!.frame.size.width - sumCellWidth - (CGFloat(layoutAttributes.count - 1) * betweenOfCell)) / 2
            for attributes in layoutAttributes{
                var nowFrame = attributes.frame
                nowFrame.origin.x = nowWidth
                attributes.frame = nowFrame
                nowWidth += nowFrame.size.width + self.betweenOfCell
            }
            break;
        case AlignType.right:
            nowWidth = self.collectionView!.frame.size.width - self.sectionInset.right
            for var index in 0 ..< layoutAttributes.count{
                index = layoutAttributes.count - 1 - index
                let attributes = layoutAttributes[index]
                var nowFrame = attributes.frame
                nowFrame.origin.x = nowWidth - nowFrame.size.width
                attributes.frame = nowFrame
                nowWidth = nowWidth - nowFrame.size.width - betweenOfCell
            }
            break;
        }
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Swift版本Demo在这里

相关文章

网友评论

  • KingSuccess:在iOS9.0以下,cell展示会出问题,设置了居左展示,当cell只有一个时,居中展示
  • EdLiya:[super layoutAttributesForElementsInRect:rect] 这个调用父类的方法能获得什么啊?
    哈南:就是获取未调整之前的Cell的位置信息
  • EdLiya:妈蛋,好东西啊笑纳了哈
  • 移动端_小刚哥:大神写的很6,但是我发现了一个问题,当设置左对齐的时候设置insetForSectionAtIndex方法左侧无效, 当设置右对齐的时候UIEdgeInsets的right参数无效
    移动端_小刚哥:@哈南 👍👍👍👍👍👍👍👍
    哈南:@iOS_小刚哥 有些bug,我找时间给修复一下,就是当写项目折腾出来的代码,写的不够完善
  • YimG:太66666666666666666666
    太66666666666666666666
    太66666666666666666666
    太66666666666666666666
    太66666666666666666666
    太66666666666666666666
  • Rdxer:只有一行的情况会出问题。。。。 :mask: cell 的位置算的不对。。。
    Rdxer:@哈南 滚动方向横向,然后cell 的数量超出屏幕,然后问题出现了,swift 版
    哈南:@Rdxer 我试了一下我的Demo,只有三个cell,而且在一行,OC和Swift版本都没问题
    Rdxer:只有一行然后,靠右对齐
  • ZJQ_Joker:这个不支持不同高度的 item 。。
    哈南:@ZJQ_Joker 嗯,这个的确没做,只做了普通的等间距对齐问题
  • xudehuai001:写的不错,学习了。个人认为,_sumCellWidth这个属性只在居中对齐时出现,可以考虑作为局部变量,其次居中对齐时,计算左右距离时应该应该还要减去sectionInset.left和sectionInset.right吧
    哈南:应该需要减去 sectionInset.left和sectionInset.right,考虑不周,_sumCellWidth这个当时是为了省事,有时间更新下代码
  • 我觉得ok_:项目中用到了,感谢分享
  • langkee:666, swift版本全部copy下来直接就能满足需求,你也是真的让我省时省力省心,由衷佩服,感谢你这位素昧平生的大神。:pray:
    langkee:哥们,你那个github上的文件里面没有代码耶,好像有问题,帮忙看看
    langkee:@哈南 好的,感谢温馨提醒:+1:
    哈南:哥们,我建议你去github下载一个Demo,然后copy那个。文章里面的代码有个Bug,github上改掉,这里忘了更新,就是如果你设置的cell间距过大,cell会到屏幕之外,这是当时考虑不周。
  • 7df1327b40df:你好,居中显示 nowWidth = (self.collectionView.frame.size.width - _sumWidth - (layoutAttributes.count * _betweenOfCell)) / 2;
    这句应该是两个cell之间有一个_betweenOfCell吧。应为(layoutAttributes.count-1) * _betweenOfCell 不然间距设置大的情况下会导致左边缘的cell 出屏幕
    哈南:@frankay_6575 好的,发现问题了,还有一个如果间距达到50以上右边也会出屏幕边缘,现在都修复了,github上面更新了,谢谢发现bug。
    7df1327b40df:@哈南 :flushed: 你自己试试你设置一个15左右就会发现问题了。你会发现左边距离左边缘和右边距离右边缘的距离是不等的 然后再设置大一点 左边就会出屏幕。
    哈南:是因为我设置了默认的minimumInteritemSpacing = 5 ,然后我在代码里面又根据_betweenOfCell重新设置了Cell的frame。你在代码里面把 minimumInteritemSpacing = _betweenOfCell的值,这样就没问题了
  • 下雨就好:谢谢博主,感激之情无以言表
  • spectatorNan:刷新就会出现问题 目前感觉问题出在遍历 获取到的layoutAttributes 数量不对等。for index in 0..<layoutAttributes.count{

    cell数量减少会崩溃,增加要刷新两次才会出现。
    spectatorNan:@哈南 stack上的方法解决了我的问题 tanks
    spectatorNan:@哈南 嗯,我刚发现 增加没问题,只是第二次调用才增加,减少数量的时候才就出现我提交给你的那个错误。我先去看看你给我的链接:pray:
    哈南:你是Github上面问我的那个人吧,我的Swift版本的Demo更新了一下,我在里面增加了 点击第一个分区Cell 减少第一个分区Cell的个数 点击第二个分区增加第二个分区cell个数。实验了一下还没出现问题。
  • 35ca33d7920b:很好,非常棒。谢谢分享
  • 有梦想的咸鱼宁:为啥俺的header没了~!求回复。。。。
    哈南:@有梦想的咸鱼宁 你运行在
    //根据cell的Y轴位置来判断cell是否是单独一行
    if curY > preY&&curY < nextY{
    print(curAttr.representedElementKind)//在这个位置加上这句话 看看有没有输出UICollectionElementKindSectionHeader
    }
    有梦想的咸鱼宁:@哈南 区头
    哈南:@有梦想的咸鱼宁 区头还是表头?
  • 一如初见丿:额 是我代码写错了……
  • 一如初见丿:swift3用你这个 for循环改成for in attributes.count会为零 你有什么解决方法吗
  • _ZhangJ:- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
    for (int i=1; i < attributes.count; i++) {
    UICollectionViewLayoutAttributes *curAttr = attributes[i]; // 当前cell的位置信息
    UICollectionViewLayoutAttributes *preAttr = attributes[i-1]; // 下一个cell 的位置信息
    // 下面这块代码是对于一行只有一个cell进行位置调整
    UICollectionViewLayoutAttributes *nextAttr = nil;//上一个cell 位置信息
    if (i+1 < attributes.count) {
    nextAttr = attributes[i+1];
    }
    if (nextAttr != nil){
    CGFloat preY = CGRectGetMaxY(preAttr.frame);
    CGFloat curY = CGRectGetMaxY(curAttr.frame);
    CGFloat nextY = CGRectGetMaxY(nextAttr.frame);
    //根据cell的Y轴位置来判断cell是否是单独一行
    if (curY > preY&&curY < nextY) {
    //这个判断方式也会对区头进行判断 如果是区头则X轴还是从0开始
    if ([curAttr.representedElementKind isEqualToString:@"UICollectionElementKindSectionHeader"]) {
    CGRect frame = curAttr.frame;
    frame.origin.x = 0;
    curAttr.frame = frame;
    } else {
    //单独一行的cell的X轴从5开始
    CGRect frame = curAttr.frame;
    frame.origin.x = 5;
    curAttr.frame = frame;
    }
    }
    }
    //下面是对一行多个cell的间距进行调整
    CGFloat origin = CGRectGetMaxX(preAttr.frame);
    CGFloat targetX = origin + self.minimumInteritemSpacing;
    if (CGRectGetMinX(curAttr.frame) > targetX){
    //如果下一个cell换行了则不进行调整
    if (targetX + CGRectGetWidth(curAttr.frame) < self.collectionViewContentSize.width) {
    CGRect frame = curAttr.frame;
    frame.origin.x = targetX;
    curAttr.frame = frame;
    }
    }


    }

    return attributes;
    }
    _ZhangJ:根据你的swift转换成OC的
  • LynnXYT:楼主,这样写,在快速滑动collectionView时有木有卡顿的现象,我这也有同样的需求,发现UI有些卡顿
    LynnXYT:@哈南 我也只是添加了两个View,没有很复杂:cry:
    哈南:真机不卡顿,如果你item上面东西比较多,可以考虑优化一下
  • 喝口水:求ocdemo
    SkyMing一C:@喝口水 怎么弄得
    喝口水:@哈南 多组的就不行了,我已经弄好了,谢谢
    哈南:@喝口水 https://pan.baidu.com/s/1slamWah
  • xksivM:同样碰到:smile: 用你这个逻辑解决了 你这个没有对就只有一行的情况下进行处理吧。

    if (i == 1)
    {
    if ( preY != currentY )
    {
    CGRect frame = preLayoutAttribute.frame;
    frame.origin.x = 0;
    preLayoutAttribute.frame = frame;
    }
    }

    感觉如果完善点,还得处理没有 header 和 footer 的情况。哈哈
    哈南:@linsyorozuya 的确没对只有一行处理,主要就是我之前项目需要写的,然后分享了一下.有时间我给完善一下.
  • xxttw:妈蛋 一样的需求 有 OC 版?
    哈南:@Unc1eWang 给个邮箱,发个我网上找的demo给你,这个demo对于一行只有一个cell的情况没进行适配,如果你没有这种需求可以直接用
    xxttw:@哈南 嗯 demo
    哈南:@Unc1eWang 有的,你现在还需要吗?

本文标题:UICollectionView中Cell左对齐 居中 右对齐

本文链接:https://www.haomeiwen.com/subject/egsudttx.html