如何使用UICollectionViewTransitionLayout插入自定义UICollectionViewLayoutAttributes属性

Stu*_*art 11 transitions ios uicollectionviewlayout

我有两个UICollectionViewLayout使用自定义UICollectionViewLayoutAttributes子类的自定义对象.这些自定义属性添加一个属性tintAlpha,该属性控制附加到每个集合视图单元格的色调覆盖视图的不透明度.

我现在想要使用UICollectionViewTransitionLayout子类在这两个布局之间进行转换.如何配置转换布局子类以tintAlpha在我的自定义布局属性上插入自定义属性?

我可以这样做:

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
     CustomLayoutAttributes *attr = [super layoutAttributesForItemAtIndexPath:indexPath];

     CustomLayoutAttributes *fromAttr = (CustomLayoutAttributes *)[self.currentLayout layoutAttributesForItemAtIndexPath:indexPath];
     CustomLayoutAttributes *toAttr = (CustomLayoutAttributes *)[self.nextLayout layoutAttributesForItemAtIndexPath:indexPath];

     CGFloat t = self.transitionProgress;
     attr.tintAlpha = (1.0f - t) * fromAttr.tintAlpha + t * toAttr.tintAlpha;

     return attr;
}
Run Code Online (Sandbox Code Playgroud)

但是,这将忽略应用于当前或下一个布局中的initialLayoutAttributesForAppearingItemAtIndexPath:&属性的任何更改finalLayoutAttributesForDisappearingItemAtIndexPath:,因此实际上并不正确.据我所知,默认实现UICollectionViewTransitionLayout确定适当的from/to属性并缓存它们,或者在prepareLayout或中layoutAttributesForItemAtIndexPath:.拥有一些公共API UICollectionViewTransitionLayout以允许我们从属性对象访问这些对象是非常有用的,就好像我尝试实现我自己的逻辑关于是否使用初始/最终属性与标准属性一样必须是默认实现中的一些差异.

在布局转换期间是否有更好的方法来插入这些自定义属性?


更新:

我刚刚遇到这个场景的另一个问题.在上面的代码中,取得当fromAttrtoAttr从当前/下一个布局直接地说,collectionViewnil用于当前布局(超过过渡至少第一次运行循环).如果布局完全取决于集合视图的边界 - 例如考虑一个简单的封面流布局 - 那么fromAttr将是不正确的.

我真的很渴望能够被子类覆盖的interpolatedLayoutAttributesFromLayoutAttributes:toLayoutAttributes:progress:on UICollectionViewTransitionLayout.

Stu*_*art 5

在提出更好的解决方案之前,我已实施以下解决方法......

默认实现调用当前和下一个布局,[super prepareLayout]以选择和缓存需要从/转换到的布局属性.因为我们无法访问此缓存(我的主要抱怨!),所以我们无法在转换过程中直接使用它们.相反,当默认实现调用插值布局属性时,我构造自己的这些属性的缓存.这只能发生在layoutAttributesForElementsInRect:(接近问题currentLayout.collectionView == nil),但幸运的是,似乎这个方法首先在与转换开始相同的运行循环中调用,并且在collectionView属性设置之前nil.这样就有机会建立我们的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;
}
Run Code Online (Sandbox Code Playgroud)


覆盖layoutAttributesForElementsInRect:简单地调用layoutAttributesFor...atIndexPath:for super想要返回属性的每个元素索引路径,该路径在进行时缓存from/to属性.例如,该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;
}
Run Code Online (Sandbox Code Playgroud)


这只留下了一个实现插值的新方法,您不仅要插入自定义布局属性属性,还要重新实现默认插值(center/ size/ alpha/ 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;
}
Run Code Online (Sandbox Code Playgroud)


综上所述...

所以令人沮丧的是,这是一个庞大的代码(为简洁起见,这里没有显示),而且大多数代码只是复制或试图复制默认实现正在做的事情.这会导致性能下降,并且如果暴露单个方法来覆盖,那么浪费开发时间就会变得非常简单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;
}
Run Code Online (Sandbox Code Playgroud)


这个解决方法的好处是你不必重新实现决定哪些布局属性在转换的开始/结束时可见的代码 - 默认实现为我们做了.每次布局无效时,我们也不必获取所有内容的属性,然后检查与可见矩形相交的项目.