UICollectionView: анимация изменения размера ячейки при выборе

Что я хочу сделать, так это изменить размер UICollectionViewCell и анимировать это изменение при выборе ячейки. Мне уже удалось сделать это без анимации, пометив ячейку как выбранную в collectionView: didSelectItemAtIndexPath:, а затем вызвав reloadData в моем UICollectionView, отобразив выбранную ячейку с другим размером.

Тем не менее, это происходит одновременно, и я понятия не имею, как анимировать это изменение размера. Есть идеи?

Я уже нашел Анимировать ячейки uicollectionview при выборе, но ответ был неконкретным для меня и Я еще не понял, может ли это помочь мне в моем случае.


person Ignatius Tremor    schedule 08.12.2012    source источник


Ответы (9)


[UIView transitionWithView:collectionView 
                  duration:.5 
                   options:UIViewAnimationOptionTransitionCurlUp 
                animations:^{

    //any animatable attribute here.
    cell.frame = CGRectMake(3, 14, 100, 100);

} completion:^(BOOL finished) {

    //whatever you want to do upon completion

}];

Поэкспериментируйте с чем-то подобным внутри вашего метода didselectItemAtIndexPath.

person Chase Roberts    schedule 08.12.2012
comment
Игра с этим, похоже, решила мою проблему. Я прав, не уточняя свое решение на основе вашего предложения для большей элегантности, чтобы опубликовать его, как только я закончу. Большое спасибо. - person Ignatius Tremor; 08.12.2012
comment
Кажется, это работает, но результаты ужасны визуально. Кажется, что он остановился. Есть идеи? - person Mike M; 11.11.2013

С помощью Чейза Робертса я нашел решение своей проблемы. Мой код в основном выглядит так:

// Prepare for animation

[self.collectionView.collectionViewLayout invalidateLayout];
UICollectionViewCell *__weak cell = [self.collectionView cellForItemAtIndexPath:indexPath]; // Avoid retain cycles
void (^animateChangeWidth)() = ^()
{
    CGRect frame = cell.frame;
    frame.size = cell.intrinsicContentSize;
    cell.frame = frame;
};

// Animate

[UIView transitionWithView:cellToChangeSize duration:0.1f options: UIViewAnimationOptionCurveLinear animations:animateChangeWidth completion:nil];

Для дальнейшего объяснения:

  1. Одним из наиболее важных шагов является вызов invalidateLayout на UICollectionView.collectionViewLayout, который вы используете. Если вы забудете об этом, вполне вероятно, что клетки вырастут за границы вашей коллекции — чего вы, скорее всего, не хотите. Также учтите, что ваш код должен в какой-то момент представить новый размер вашей ячейки в макете. В моем случае (я использую UICollectionViewFlowLayout) я скорректировал метод collectionView:layout:sizeForItemAtIndexPath, чтобы отразить новый размер измененной ячейки. Если забыть об этой части, с размерами ваших ячеек вообще ничего не произойдет, если вы сначала вызовете invalidateLayout, а затем попытаетесь анимировать изменение размера.

  2. Самым странным (по крайней мере для меня), но важным является то, что вы вызываете invalidateLayout не внутри блока анимации. Если вы сделаете это, анимация будет отображаться не плавно, а с глюками и мерцанием.

  3. Вы должны вызвать метод UIViews transitionWithView:duration:options:animations:completion: с ячейкой в ​​качестве аргумента для transitionWithView, а не всего представления коллекции, потому что это ячейка, которую вы хотите анимировать, а не вся коллекция.

  4. Я использовал метод intrinsicContentSize моей ячейки, чтобы вернуть нужный размер — в моем случае я увеличиваю и уменьшаю ширину ячейки, чтобы отобразить кнопку или скрыть ее, в зависимости от состояния выбора ячейки.

Я надеюсь, что это поможет некоторым людям. Мне потребовалось несколько часов, чтобы понять, как заставить эту анимацию работать правильно, поэтому я стараюсь освободить других от этого трудоемкого бремени.

person Ignatius Tremor    schedule 08.12.2012
comment
Хорошая работа, опубликовать продолжение. - person Chase Roberts; 13.12.2012
comment
как определяется actorCell? Это должно быть то, что вы хотите отобразить в увеличенном виде? - person Austin; 10.02.2013
comment
Я переименовал ячейку в cellToChangeSize. Имя, которое можно найти в моем посте до редактирования, было просто остатками копипаста из моего проекта, извините за это. - person Ignatius Tremor; 10.02.2013
comment
Я скорректировал метод collectionView:layout:sizeForItemAtIndexPath, чтобы отразить новый размер измененной ячейки. Как ты это сделал? - person Timuçin; 07.06.2013
comment
Часть о том, как избежать цикла сохранения, не должна ли она быть объявлена ​​вне блока? А так как вы используете uicollectionview (это iOS 6+), предпочтительным квалификатором теперь является __weak. Если cell появляется в вашем блоке, создается сильная ссылка, поэтому вы не избегаете цикла сохранения. Тем не менее, большое спасибо за продолжение, очень полезно! - person matehat; 02.07.2013
comment
Привет, @matehat, я изменил код, чтобы отразить твою аннотацию. Рад слышать, что вам все равно понравился ответ :) - person Ignatius Tremor; 31.07.2013
comment
Я просто хочу добавить сюда, что вам не нужно использовать __weak. Цикл сохранения обычно происходит, когда ваш блок сохраняет некоторый объект, который сам сохраняет блок; то есть переход self.block = ^{self.someInteger++;}. Поскольку ваша ячейка не сохраняет блок, нет циклов сохранения :) - person Daniel Galasko; 01.10.2013
comment
Небольшой совет для тех, кто читает это: использование AutoLayout в ваших ячейках может сломать это. Вместо этого мне пришлось изменить ограничение высоты ячейки, а затем использовать [cell layoutIfNeeded] внутри блока анимации. - person Nailer; 04.10.2013
comment
Изменение ячейки анимировано, но представление коллекции перейдет к новому макету. - person openfrog; 17.10.2013
comment
Что касается комментария @openfrog, если вы пытаетесь анимировать изменение высоты, вам нужно использовать transitionWithView:duration:options:animations:completion: с представлением всей коллекции в качестве аргумента, верно? - person bcattle; 07.02.2014
comment
Это не сработало для меня. Мне удалось изменить размер рамки ячейки, но внутри ячейки ничего не изменилось. Я даже пытался изменить рамки отдельных подэлементов, но они всегда остаются неизменными. - person Erika Electra; 22.05.2014

Анимация размера ячейки работает так же для представлений коллекций, как и для представлений таблиц. Если вы хотите анимировать размер ячейки, создайте модель данных, отражающую состояние ячейки (в моем случае я просто использую флаг (CollectionViewCellExpanded, CollectionViewCellCollapsed) и соответственно возвращаю CGSize.

При вызове и пустом вызове PerformBathUpdates представление автоматически анимируется.

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    [collectionView performBatchUpdates:^{
        // append your data model to return a larger size for
        // cell at this index path
    } completion:^(BOOL finished) {

    }];
}
person Andy Poes    schedule 11.02.2013
comment
Отличный ответ. В моем случае мне просто нужно было изменить модель, затем вызвать PerformBatchUpdates и внутри блока вызвать invalidateLayout. - person Dorian Roy; 20.02.2013
comment
У меня есть настраиваемый макет, который содержит переходное свойство относительно того, какой путь индекса является выдающимся путем индекса, и когда я устанавливаю этот путь индекса, представление перерисовывается и перемещает элемент по пути индекса к вершине коллекции, прочь от остальных предметов (вроде Passbook). Установка этого свойства и работа с предложенным @DorianRoy была огромной помощью. - person Ben Kreeger; 10.07.2013
comment
Это гораздо лучшее решение, особенно если учесть, что изменения сохраняются после повторного использования ячейки. - person dezinezync; 27.04.2014
comment
Работал для меня, используя Swift 4.2 - person jangelsb; 18.03.2020

Мне удалось анимировать изменения, используя -setCollectionViewLayout:animated: для создания нового экземпляра того же макета представления коллекции.

Например, если вы используете UICollectionViewFlowLayout, то:

// Make sure that your datasource/delegate are up-to-date first

// Then animate the layout changes
[collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init] animated:YES];
person codeperson    schedule 16.06.2013
comment
Это дает самую плавную анимацию на сегодняшний день. Отличное решение. - person phatmann; 18.06.2014
comment
Ни один из других ответов не сработал для меня - другие выполняли анимацию в ячейке, но акт аннулирования макета (явно или за кулисами) также вызывал проблемы с размещением других ячеек до или после анимации отдельной ячейки . Это единственный ответ, который сработал для меня! - person Evan R; 18.08.2015
comment
Это работает, за исключением случая (проверено на iPhone4S, iOS8): во время анимации, если вы прокрутите CollectionView, ячейка будет скрыта. - person chipbk10; 10.08.2017
comment
Недостатком является то, что contentOffset меняется. - person Iulian Onofrei; 24.01.2019

Это самое простое решение, которое я нашел - работает как шарм. Плавная анимация и не портит макет.

Быстрый

    collectionView.performBatchUpdates({}){}

Объект-C

    [collectionView performBatchUpdates:^{ } completion:^(BOOL finished) { }];

Блоки (замыкания в Swift) должны быть намеренно оставлены пустыми!

person Pavel Gurov    schedule 14.03.2016
comment
Я попробовал это решение с reloadData, moveItemAtIndexPath: toIndexPath:, reloadItemsAtIndexPaths: внутри блока обновлений, а затем понял, что этот блок следует оставить пустым. Теперь действительно работает как шарм. - person zalogatomek; 23.06.2016

Хороший способ анимировать размер ячейки — переопределить isHighlighted в самом collectionViewCell.

class CollectionViewCell: UICollectionViewCell {

    override var isHighlighted: Bool {
        didSet {
            if isHighlighted {
                UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
                    self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
                }, completion: nil)
            } else {
                UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
                    self.transform = CGAffineTransform(scaleX: 1, y: 1)
                }, completion: nil)
            }
        }
    }

}
person Cristian Pena    schedule 05.09.2017
comment
Вы сказали, что хороший способ анимировать размер ячейки - переопределить isSelected в самой коллекцииViewCell. , но вы переопределяете isHighlighted. Это 2 разных состояния. - person ShadeToD; 23.07.2019

Использование PerformBatchUpdates не анимирует макет содержимого ячейки. Вместо этого вы можете использовать следующее:

    collectionView.collectionViewLayout.invalidateLayout()

    UIView.animate(
        withDuration: 0.4,
        delay: 0.0,
        usingSpringWithDamping: 1.0,
        initialSpringVelocity: 0.0,
        options: UIViewAnimationOptions(),
        animations: {
            self.collectionView.layoutIfNeeded()
        },
        completion: nil
    )

Это дает очень плавную анимацию.

Это похоже на ответ Игнатия Тремора, но анимация перехода у меня не работала должным образом.

Полное решение см. на странице https://github.com/cnoon/CollectionViewAnimations.

person phatmann    schedule 14.12.2017
comment
Проголосовал за этот ответ, но он все еще не идеален. Анимация растягивает ячейку, прежде чем перерисовывать ее снова. Но на самом деле анимация стала приятной, потому что анимируется и высота ячейки. - person Ariel Bogdziewicz; 13.09.2019

Это сработало для меня.

    collectionView.performBatchUpdates({ () -> Void in
        let ctx = UICollectionViewFlowLayoutInvalidationContext()
        ctx.invalidateFlowLayoutDelegateMetrics = true
        self.collectionView!.collectionViewLayout.invalidateLayoutWithContext(ctx)
    }) { (_: Bool) -> Void in
    }
person eonil    schedule 02.12.2015
comment
внутренности PerformBatchUpdates не нужны, см. мой ответ - person Pavel Gurov; 14.03.2016

class CollectionViewCell: UICollectionViewCell {
   override var isHighlighted: Bool {
       if isHighlighted {
            UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {
                self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
            }) { (bool) in
                UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {
                    self.transform = CGAffineTransform(scaleX: 1, y: 1)
                }, completion: nil)
            }
        }
   }
}

Нужно просто проверить, была ли изменена переменная isHighlighted на true, затем использовать анимацию UIView и анимировать обратно в обработчике завершения.

person Alexander    schedule 30.12.2019