Ответ 1
Пока не будет предложено лучшее решение, я применил следующее обходное решение...
Реализация по умолчанию вызывает текущий и следующий макеты из [super prepareLayout]
для выбора и кэширования атрибутов макета, которые необходимо переходить с/на. Поскольку мы не получаем доступ к этому кешу (моя главная проблема!), Мы не можем использовать их непосредственно во время перехода. Вместо этого я создаю свой собственный кеш этих атрибутов, когда реализация по умолчанию вызывает интерполированные атрибуты макета. Это может произойти только в layoutAttributesForElementsInRect:
(наступая близко к проблеме currentLayout.collectionView == nil
), но, к счастью, кажется, что этот метод сначала вызывается в том же цикле запуска, что и начало перехода, и до того, как свойство collectionView
установлено на nil
. Это дает возможность установить наши атрибуты layout/layout и кешировать их на время перехода.
@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:
просто вызывает в layoutAttributesFor...atIndexPath:
для каждого пути индекса элемента, который 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;
}
Который просто оставляет новый метод, который выполняет фактическую интерполяцию, где вам нужно не только интерполировать свойства атрибута пользовательского макета, но и переопределять интерполяцию по умолчанию (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;
}
В резюме...
Так что разочарование в этом заключается в том, что он представляет собой огромное количество кода (многое не показано здесь для краткости), и большая его часть просто реплицирует или пытается реплицировать то, что делает реализация по умолчанию в любом случае. Это приводит к ухудшению производительности и сокращению времени разработки для чего-то, что действительно может быть , поэтому намного проще, если 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;
}
Хорошая вещь об этом обходном пути заключается в том, что вам не нужно переопределять код, который определяет, какие атрибуты макета видны в начале и конце перехода - реализация по умолчанию делает это для нас. Мы также не должны получать атрибуты для всех каждый раз, когда макет недействителен, а затем проверяем элементы, которые пересекают видимый прямоугольник.