美文网首页iOS开发知识小集
UICollectionView实现婚礼纪欢迎页

UICollectionView实现婚礼纪欢迎页

作者: 简书坤 | 来源:发表于2018-05-25 18:14 被阅读122次

    老习惯先上效果图(和上篇文章一样):

    collectionViewForWecomePage.gif

    整体思路:

    1. 如上篇文章中提到的,cell上布局了一个imageView,通过UICollectionViewLayoutAttributes的子类PageLayoutAttributescontentOffsetX来更新iamgeView的位置
    2. 通过重写UICollectionViewFlowLayoutshouldInvalidateLayoutForBoundsChange:方法来触发滚动collectionView时更新layout中的PageLayoutAttributescontentOffsetX属性;
    3. PageLayoutAttributes通过isEqual:判断是否需要更新PageLayoutAttributes实例对应的cell;
    4. cell通过applyLayoutAttributes:获取布局属性,进行布局

    附上:demo

    本篇文章中,您将看到:

    1. UICollectionViewLayoutAttributes子类化,及相关的注意点;
    2. collectionViewCell中使用自定义的layoutAttributes来布局cell`;
    3. 简单的自定义UICollectionViewLayout

    一. UICollectionViewLayoutAttributes子类化

    官方文档中的注意点如下:

    If you subclass and implement any custom layout attributes, you must also override the inherited isEqual: method to compare the values of your properties. In iOS 7 and later, the collection view does not apply layout attributes if those attributes have not changed. It determines whether the attributes have changed by comparing the old and new attribute objects using the isEqual: method. Because the default implementation of this method checks only the existing properties of this class, you must implement your own version of the method to compare any additional properties. If your custom properties are all equal, call super and return the resulting value at the end of your implementation.

    如果继承了UICollectionViewLayoutAttributes并且添加了任何自定义的layout attributes,也必须实现isEqual:方法来比较自定义属性.在iOS7(包括iOS7)以后,如果UICollectionViewLayoutAttributes的属性值没有改变,collection view不会应用layout attributes,这些layout attributes的是否改变由isEqual:的返回值来决定.在重写isEqual:时,除了需要处理自定义属性外,还需要注意父类方法的调用.

    Because layout attribute objects may be copied by the collection view, it conforms to the NSCopying protocol. It is very important that we also conform to this protocol and implement copyWithZone:. Otherwise, our property will always be zero (as guaranteed by the compiler).

    由于layout attributes对象可能会被collection view复制,因此layout attributes对象应该遵循NSCoping协议,并实现copyWithZone:方法,否则我们获取的自定义属性会一直是空值.

    举例如下:

    /** subclass must conforms to the NSCopying protocol */
    - (id)copyWithZone:(NSZone *)zone {
    
        CLSectionColorLayoutAttributes *layoutAttributes = [super copyWithZone:zone];
        layoutAttributes.sectionColor = self.sectionColor;
        return layoutAttributes;
    }
    
    
    /** In iOS 7 and later, the collection view does not apply layout attributes if
     those attributes have not changed. It determines whether the attributes have changed
     by comparing the old and new attribute objects using the isEqual: method. */
    - (BOOL)isEqual:(id)object {
        if (self == object) {
            return YES;
        }
        if ([object class] == [self class]) {
            return [super isEqual:object] && (self.sectionColor == [object sectionColor]);
        }
        return NO;
    }
    

    这里说下本文demo中的碰到的实际情况:

    首先,我们在自定义的PageLayoutAttributes增添了contentOffsetX属性来控制图片的偏移量:

    @interface PageLayoutAttributes : UICollectionViewLayoutAttributes
    
    /**** 偏移量 ***/
    @property (nonatomic, assign) CGFloat contentOffsetX;
    
    @end
    
    

    然后在.m文件中实现了copyWithZone:isEqual:方法

    
    @implementation PageLayoutAttributes
    
    - (BOOL)isEqual:(id)object {
        /*
         //这里的判断永远是不相等的(仅本例)
        if (self == object) {
            return YES;
        }
         */
        if ([object isKindOfClass:[PageLayoutAttributes class]]) {
            PageLayoutAttributes *newObject = (PageLayoutAttributes *)object;
            if (newObject.contentOffsetX == self.contentOffsetX) {
                  //BUG点
                return YES;
            }
            return [super isEqual:object];
        }
        return [super isEqual:object];
    }
    
    - (instancetype)copyWithZone:(NSZone *)zone {
        PageLayoutAttributes *model = [super copyWithZone:zone];
        model.contentOffsetX = self.contentOffsetX;
        return model;
    }
    
    @end
    

    这里//BUG点,整直接返回了YES,结果布局出来的视图呈现了如下效果:

    BUG版本.gif

    找这个BUG原因的时间花了一下午😳,因此在这里建议自定义UICollectionViewLayoutAttributes时在isEqual,仅有两种返回值:

    1. return NO;
    2. return [super isEqual:object];

    修改后的代码请参阅demo

    二.自定义的UICollectionViewLayout

    本demo中对layout的需求过于简单,重点在layout中使用子类化的layoutAttributes,因此demo中通过继承UICollectionViewFlowLayout来实现;

    #import "WecomePageFlowLayout.h"
    #import "PageLayoutAttributes.h"
    
    @interface WecomePageFlowLayout ()
    
    /**** cell的总数 ***/
    @property (nonatomic, assign) NSInteger cellCount;
    
    @property (nonatomic, copy) NSArray *attributsArray;
    
    @end
    
    @implementation WecomePageFlowLayout
    
    
    - (void)prepareLayout {
        [super prepareLayout];
        _cellCount = [[self collectionView] numberOfItemsInSection:0];
    }
    //告诉 layout 使用 自定义的 attributes 来布局
    + (Class)layoutAttributesClass {
        return [PageLayoutAttributes class];
    }
    
    
    /*!
     *  多次调用 只要滑出范围就会 调用
     *  当CollectionView的显示范围发生改变的时候,是否重新发生布局
     *  一旦重新刷新 布局,就会重新调用
     *  1.layoutAttributesForElementsInRect:方法
     *  2.preparelayout方法
     */
    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
        return YES;
    }
    
    - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
        //计算当前的偏移量
        CGFloat width = CGRectGetWidth(self.collectionView.bounds);
        CGFloat offsetX = self.collectionView.contentOffset.x;
        NSInteger index = offsetX / width;
        PageLayoutAttributes *curretnAttribute = self.attributsArray[index];
        PageLayoutAttributes *nextAttribute = nil;
        if (index < _cellCount -1) {
            nextAttribute = self.attributsArray[index + 1];
        }
        //当前的item对应的attribute设置偏移量为0
        curretnAttribute.contentOffsetX = 0;
        if (nextAttribute) {
            //正在出现的item对应的attribute设置偏移量为跟随collectionView的offset动态计算
            nextAttribute.contentOffsetX = -(width * 0.5 - (offsetX - width * index) * 0.5);
        }
        return self.attributsArray;
    }
    
    
    #pragma mark -- tools
    
    - (NSArray *)attributsArray {
        if (!_attributsArray) {
            NSMutableArray *array = [NSMutableArray array];
            CGFloat width = CGRectGetWidth([UIScreen mainScreen].bounds);
            CGFloat height = CGRectGetHeight([UIScreen mainScreen].bounds);
            for (NSInteger i = 0; i < _cellCount; i++) {
                NSIndexPath* indexPath = [NSIndexPath indexPathForItem:i inSection:0];
                PageLayoutAttributes *attribute = [PageLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
                attribute.contentOffsetX = (i == 0 ? 0 : -(CGRectGetWidth([UIScreen mainScreen].bounds) * 0.5));
                attribute.frame = CGRectMake(i * width, 0, width, height);
                [array addObject:attribute];
            }
            self.attributsArray = [array copy];
        }
        return _attributsArray;
    }
    
    @end
    

    这里对于比较复杂的layout需求给出一个苹果官方例子作为参考:CircleLayout

    3.在collectionViewCell中使用子类化layoutAttributes布局

    使用- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;方法来进行自定义布局

    #import "WecomePageCell.h"
    #import "PageLayoutAttributes.h"
    
    @interface WecomePageCell ()
    
    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *offsetX;
    @property (weak, nonatomic) IBOutlet UIImageView *cImageView;
    
    @end
    
    @implementation WecomePageCell
    
    - (void)awakeFromNib {
        [super awakeFromNib];
    }
    
    //使用LayoutAttributes布局Cell
    - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
        [super applyLayoutAttributes:layoutAttributes];
        if ([layoutAttributes isKindOfClass:[PageLayoutAttributes class]]) {
            self.offsetX.constant = [(PageLayoutAttributes *)layoutAttributes contentOffsetX];
        }
    }
    
    #pragma mark === public
    - (void)updateImage:(UIImage *)image {
        [self.cImageView setImage:image];
    }
    
    @end
    
    

    相关文章

      网友评论

      本文标题:UICollectionView实现婚礼纪欢迎页

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