在提出更好的解决方案之前,我已经实施了以下解决方法...
默认实现从[super prepareLayout] 调用当前和下一个布局,以选择和缓存需要从/到转换的布局属性。因为我们无法访问这个缓存(我的主要抱怨!),我们不能在过渡期间直接使用它们。相反,当默认实现调用内插布局属性时,我会构建自己的这些属性缓存。这只能在layoutAttributesForElementsInRect: 中发生(接近currentLayout.collectionView == nil 的问题),但幸运的是,这个方法似乎首先在与转换开始相同的运行循环中调用,并且在collectionView 属性设置为@之前987654325@。这提供了一个机会来建立我们的 from/to 布局属性并在过渡期间缓存它们。
@interface CustomTransitionLayout ()
@property(nonatomic, strong) NSMutableDictionary *transitionInformation;
@end
@implementation
- (void)prepareLayout
{
[super prepareLayout];
if (!self.transitionInformation) {
self.transitionInformation = [NSMutableDictionary dictionary];
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// Let the super implementation tell us which attributes are required.
NSArray *defaultLayoutAttributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *layoutAttributes = [NSMutableArray arrayWithCapacity:[defaultLayoutAttributes count]];
for (UICollectionViewLayoutAttributes *defaultAttr in defaultLayoutAttributes) {
UICollectionViewLayoutAttributes *attr = defaultAttr;
switch (defaultAttr.representedElementCategory) {
case UICollectionElementCategoryCell:
attr = [self layoutAttributesForItemAtIndexPath:defaultAttr.indexPath];
break;
case UICollectionElementCategorySupplementaryView:
attr = [self layoutAttributesForSupplementaryViewOfKind:defaultAttr.representedElementKind atIndexPath:defaultAttr.indexPath];
break;
case UICollectionElementCategoryDecorationView:
attr = [self layoutAttributesForDecorationViewOfKind:defaultAttr.representedElementKind atIndexPath:defaultAttr.indexPath];
break;
}
[layoutAttributes addObject:attr];
}
return layoutAttributes;
}
layoutAttributesForElementsInRect: 的覆盖只是为super 想要为其返回属性的每个元素索引路径调用layoutAttributesFor...atIndexPath:,它会缓存从/到属性。例如,layoutAttributesForItemAtIndexPath: 方法看起来像这样:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *indexPathKey = [indexPath collectionViewKey];
NSMutableDictionary *info = self.transitionInformation[indexPathKey];
if (!info) {
info = [NSMutableDictionary dictionary];
self.transitionInformation[indexPathKey] = info;
}
// Logic to choose layout attributes to interpolate from.
// (This is not exactly how the default implementation works, but a rough approximation)
MyLayoutAttributes *fromAttributes = info[TransitionInfoFromAttributesKey];
if (!fromAttributes) {
MyLayoutAttributes *standardToAttributes = (MyLayoutAttributes *)[self.nextLayout layoutAttributesForItemAtIndexPath:indexPathKey];
MyLayoutAttributes *initialAttributes = (MyLayoutAttributes *)[self.nextLayout initialLayoutAttributesForAppearingItemAtIndexPath:indexPathkey];
if (initialAttributes && ![initialAttributes isEqual:standardToAttributes]) {
fromAttributes = [initialAttributes copy];
} else {
fromAttributes = [(MyLayoutAttributes *)[self.currentLayout layoutAttributesForItemAtIndexPath:indexPathKey] copy];
}
info[TransitionInfoFromAttributesKey] = fromAttributes;
}
MyLayoutAttributes *toAttributes = info[TransitionInfoToAttributesKey];
if (!toAttributes) {
// ... similar logic as for fromAttributes ...
info[TransitionInfoToAttributesKey] = toAttributes;
}
MyLayoutAttributes *attributes = [self interpolatedLayoutAttributesFromLayoutAttributes:fromAttributes
toLayoutAttributes:toAttributes
progress:self.transitionProgress];
return attributes;
}
这只是留下了一种新方法来进行实际插值,您不仅要插值自定义布局属性属性,而且重新实现默认插值(center/size/@987654334 @/transform/transform3D):
- (MyLayoutAttributes *)interpolatedLayoutAttributesFromLayoutAttributes:(MyLayoutAttributes *)fromAttributes
toLayoutAttributes:(MyLayoutAttributes *)toAttributes
progress:(CGFloat)progress
{
MyLayoutAttributes *attributes = [fromAttributes copy];
CGFloat t = progress;
CGFloat f = 1.0f - t;
// Interpolate all the default layout attributes properties.
attributes.center = CGPointMake(f * fromAttributes.x + t * toAttributes.center.x,
f * fromAttributes.y + t * toAttributes.center.y);
// ...
// Interpolate any custom layout attributes properties.
attributes.customProperty = f * fromAttributes.customProperty + t * toAttributes.customProperty;
// ...
return attributes;
}
总结...
因此,令人沮丧的是,它包含大量代码(为简洁起见,此处未显示很多代码),其中大部分只是复制或尝试复制默认实现的内容反正做。如果UICollectionViewTransitionLayout 公开了一个要覆盖的方法,这会导致性能下降,并浪费开发时间来如此简单得多,例如:
- (UICollectionViewLayoutAttributes *)interpolatedLayoutAttributesFromLayoutAttributes:(UICollectionViewLayoutAttributes *)fromAttributes
toLayoutAttributes:(UICollectionViewLayoutAttributes *)toAttributes
progress:(CGFloat)progress
{
MyLayoutAttributes *attributes = (MyLayoutAttributes *)[super interpolatedLayoutAttributesFromLayoutAttributes:fromAttributes toLayoutAttributes:toAttributes progress:progress];
attributes.customProperty = (1.0f - progress) * fromAttributes.customProperty + progress * toAttributes.customProperty;
return attributes;
}
这种解决方法的好处是您不必重新实现决定哪些布局属性在转换的开始/结束时可见的代码 - 默认实现为我们完成了这项工作。我们也不必在每次布局失效时获取 everything 的属性,然后检查与可见矩形相交的项目。