如何使用UICollectionViewTransitionLayout插入自定义UICollectionViewLayoutAttributes属性 [英] How to interpolate custom UICollectionViewLayoutAttributes properties with UICollectionViewTransitionLayout

查看:540
本文介绍了如何使用UICollectionViewTransitionLayout插入自定义UICollectionViewLayoutAttributes属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个使用自定义 UICollectionViewLayoutAttributes 子类的自定义 UICollectionViewLayout 对象。这些自定义属性添加了一个属性 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;

返回attr;
}

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



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






更新:



我刚刚遇到这个场景的另一个问题。在上面的代码中,当从&获得时直接来自当前/下一个布局的 toAttr collectionView nil 对于当前布局(至少超过转换的第一个运行循环)。如果布局完全依赖于集合视图的边界 - 例如考虑一个简单的封面流布局 - 那么 fromAttr 将是不正确的。



我真的思念一个 interpolatedLayoutAttributesFromLayoutAttributes:toLayoutAttributes:进度: UICollectionViewTransitionLayout 可重写通过子类。

解决方案

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



默认实现调用当前&来自 [super prepareLayout] 的下一个布局来选择&缓存需要从/转换到的布局属性。因为我们无法访问此缓存(我的主要抱怨!),所以我们无法在转换过程中直接使用它们。相反,当默认实现调用插值布局属性时,我构造自己的这些属性的缓存。这只能发生在 layoutAttributesForElementsInRect:(踩到接近 currentLayout.collectionView == nil 的问题),但幸运的是看起来这个方法首先在与转换开始相同的运行循环中调用,并且在 collectionView 属性设置为 nil 。这样就有机会建立我们的from / to布局属性并在转换期间缓存它们。

  @interface CustomTransitionLayout( )
@property(非原子,强)NSMutableDictionary * transitionInformation;
@end

@implementation

- (void)prepareLayout
{
[super prepareLayout];

if(!self.transitionInformation){
self.transitionInformation = [NSMutableDictionary dictionary];
}
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
//让超级实现告诉我们哪些属性是必需的。
NSArray * defaultLayoutAttributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray * layoutAttributes = [NSMutableArray arrayWithCapacity:[defaultLayoutAttributes count]];
for(UICollectionViewLayoutAttributes * defaultLayoutAttributes中的defaultAttr){
UICollectionViewLayoutAttributes * attr = defaultAttr;
switch(defaultAttr.representedElementCategory){
case UICollectionElementCategoryCell:
attr = [self layoutAttributesForItemAtIndexPath:defaultAttr.indexPath];
休息;
case UICollectionElementCategorySupplementaryView:
attr = [self layoutAttributesForSupplementaryViewOfKind:defaultAttr.representedElementKind atIndexPath:defaultAttr.indexPath];
休息;
case UICollectionElementCategoryDe​​corationView:
attr = [self layoutAttributesForDecorationViewOfKind:defaultAttr.representedElementKind atIndexPath:defaultAttr.indexPath];
休息;
}
[layoutAttributes addObject:attr];
}
返回layoutAttributes;
}



重写 layoutAttributesForElementsInRect:只为 super layoutAttributesFor ... atIndexPath: c>想要返回属性,这些属性在进行时缓存from / to属性。例如, layoutAttributesForItemAtIndexPath:方法如下所示:





'pre> - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath * indexPathKey = [indexPath collectionViewKey];

NSMutableDictionary * info = self.transitionInformation [indexPathKey];
if(!info){
info = [NSMutableDictionary dictionary];
self.transitionInformation [indexPathKey] = info;
}

//选择要插入的布局属性的逻辑。
//(这不是默认实现的工作原理,但粗略近似)
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){
// ...类似于fromAttributes的逻辑...
info [TransitionInfoToAttributesKey] = toAttributes;
}

MyLayoutAttributes *属性= [自interpolatedLayoutAttributesFromLayoutAttributes:fromAttributes
toLayoutAttributes:toAttributes
进展:self.transitionProgress];
返回属性;
}



只留下一个新方法实际插值,您不仅要插入自定义布局属性属性,还要重新实现默认插值( center / 尺寸 / 阿尔法 / 变换 / 的Transform3D ):



   - (MyLayoutAttributes *) interpolatedLayoutAttributesFromLayoutAttributes:(MyLayoutAttributes *)fromAttributes 
toLayoutAttributes:(MyLayoutAttributes *)toAttributes
进展:(CGFloat的)进展
{
MyLayoutAttributes *属性= [fromAttributes复制];

CGFloat t =进展;
CGFloat f = 1.0f - t;

//插值所有默认布局属性属性。
attributes.center = CGPointMake(f * fromAttributes.x + t * toAttributes.center.x,
f * fromAttributes.y + t * toAttributes.center.y);
// ...

//插值任何自定义布局属性属性。
attributes.customProperty = f * fromAttributes.customProperty + t * toAttributes.customProperty;
// ...

返回属性;
}




< h2>总结...

所以令人沮丧的是,这是一个庞大的代码(为简洁起见,这里没有显示),大部分内容都是如此只是复制或尝试来复制默认实现正在做的事情。如果 UICollectionViewTransitionLayout 暴露了一个要覆盖的方法,例如,这会导致性能下降,并且浪费开发时间,这可能会非常简单如此更简单:

   - (UICollectionViewLayoutAttributes *)interpolatedLayoutAttributesFromLayoutAttributes:(UICollectionViewLayoutAttributes *)fromAttributes 
toLayoutAttributes:(UICollectionViewLayoutAttributes *)toAttributes
进展:(CGFloat的)进展
{
MyLayoutAttributes *属性=(MyLayoutAttributes *)[超级interpolatedLayoutAttributesFromLayoutAttributes:fromAttributes toLayoutAttributes:toAttributes进展:进展];
attributes.customProperty =(1.0f - progress)* fromAttributes.customProperty + progress * toAttributes.customProperty;
返回属性;
}



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


I have two custom UICollectionViewLayout objects that use a custom UICollectionViewLayoutAttributes subclass. These custom attributes add a single property tintAlpha that controls the opacity of a tint overlay view attached to each collection view cell.

I now want to transition between these two layouts, using a UICollectionViewTransitionLayout subclass. How can I configure the transition layout subclass to interpolate the custom tintAlpha property on my custom layout attributes?

I could do something like this:

- (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;
}

However this will ignore any changes applied to the attributes in initialLayoutAttributesForAppearingItemAtIndexPath: & finalLayoutAttributesForDisappearingItemAtIndexPath: in the current or next layout, and so is not actually correct. As far as I can tell, the default implementation of UICollectionViewTransitionLayout determines the appropriate from/to attributes and caches them, either in prepareLayout or layoutAttributesForItemAtIndexPath:. It would be so useful to have some public API on UICollectionViewTransitionLayout to allow us to access these from/to attributes objects, as if I try and implement my own logic on whether to use the initial/final attributes vs the standard attributes there are bound to be some discrepencies from the default implementation.

Is there a better way to interpolate these custom attributes during a layout transition?


Update:

I have just encountered an additional problem with this scenario. In the code above, when getting fromAttr & toAttr directly from the current/next layouts, the collectionView is nil for the current layout (beyond the first run loop of the transition at least). If layout depends at all on the collection view's bounds - consider a simple cover flow layout for example - then the fromAttr will be incorrect.

I'm really pining for a interpolatedLayoutAttributesFromLayoutAttributes:toLayoutAttributes:progress: on UICollectionViewTransitionLayout that can be overridden by subclasses.

解决方案

Until a better solution is proposed, I have implemented the following workaround...

The default implementation calls into the current & next layouts from [super prepareLayout] to choose & cache the layout attributes that need to be transitioned from/to. Because we don't get access to this cache (my main gripe!), we can't use them directly during the transition. Instead, I construct my own cache of these attributes when the default implementation calls through for the interpolated layout attributes. This can only happen in layoutAttributesForElementsInRect: (treading close to the problem of currentLayout.collectionView == nil), but fortunately it seems this method is first called in the same run loop as the transition starting, and before the collectionView property is set to nil. This gives an opportunity to establish our from/to layout attributes and cache them for the duration of the transition.

@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;
}


The override of layoutAttributesForElementsInRect: simply calls into the layoutAttributesFor...atIndexPath: for each element index path that super wants to return attributes for, which caches the from/to attributes as it goes. For example, the layoutAttributesForItemAtIndexPath: method looks something like this:

- (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;
}


Which just leaves a new method that does the actual interpolation, which is where you have to not only interpolate the custom layout attribute properties, but reimplement the default interpolation (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;
}


In Summary...

So what's frustrating about this is that it's a massive amount of code (much isn't shown here for brevity), and most of it is just replicating or trying to replicate what the default implementation is doing anyway. This results in worse performance, and wastes development time for something that could really be so much simpler if UICollectionViewTransitionLayout exposed a single method to override, such as:

- (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;
}


The good thing about this workaround is that you don't have to reimplement the code that decides which layout attributes are visible at the start/end of the transition - the default implementation does that for us. Nor do we have to get the attributes for everything each time the layout is invalidated, and then check for items that intersect the visible rect.

这篇关于如何使用UICollectionViewTransitionLayout插入自定义UICollectionViewLayoutAttributes属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆