老习惯先上效果图(和上篇文章一样):
collectionViewForWecomePage.gif整体思路:
- 如上篇文章中提到的,
cell
上布局了一个imageView
,通过UICollectionViewLayoutAttributes
的子类PageLayoutAttributes
的contentOffsetX
来更新iamgeView
的位置 - 通过重写
UICollectionViewFlowLayout
的shouldInvalidateLayoutForBoundsChange:
方法来触发滚动collectionView
时更新layout
中的PageLayoutAttributes
的contentOffsetX
属性; -
PageLayoutAttributes
通过isEqual:
判断是否需要更新PageLayoutAttributes
实例对应的cell
; -
cell
通过applyLayoutAttributes:
获取布局属性,进行布局
附上:demo
本篇文章中,您将看到:
-
UICollectionViewLayoutAttributes
子类化,及相关的注意点; - 在
collectionViewCell
中使用自定义的layoutAttributes来布局
cell`; - 简单的自定义
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 theisEqual:
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 implementcopyWithZone:
. 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
原因的时间花了一下午😳,因此在这里建议自定义UICollectionViewLayoutAttributes
时在isEqual
,仅有两种返回值:
- return NO;
- 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
网友评论