Подтвердить что ты не робот

Переупорядочить ячейки UICollectionView

Рассмотрим a UICollectionView с раскладкой потока и горизонтальным направлением. По умолчанию ячейки упорядочиваются сверху вниз, слева направо. Вот так:

1 4 7 10 13 16
2 5 8 11 14 17
3 6 9 12 15 18

В моем случае просмотр коллекции выгружается, и он был спроектирован таким образом, чтобы определенное количество ячеек соответствовало каждой странице. Таким образом, более естественным упорядочением было бы следующее:

1 2 3   10 11 12
4 5 6 - 13 14 15
7 8 9   16 17 18

Что было бы самым простым в достижении этого, если не реализовать собственный собственный макет? В частности, я не хочу потерять любую функциональность, которая предоставляется бесплатно с помощью UICollectionViewFlowLayout (например, вставки/удаление анимаций).

Или вообще, как вы реализуете функцию переупорядочения f(n) в макете потока? То же самое может быть применимо, например, к упорядочению справа налево.

Мой подход до сих пор

Мой первый подход заключался в подклассе UICollectionViewFlowLayout и переопределении layoutAttributesForItemAtIndexPath::

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSIndexPath *reorderedIndexPath = [self reorderedIndexPathOfIndexPath:indexPath];
    UICollectionViewLayoutAttributes *layout = [super layoutAttributesForItemAtIndexPath:reorderedIndexPath];
    layout.indexPath = indexPath;
    return layout;
}

Где reorderedIndexPathOfIndexPath: - f(n). Вызывая super, мне не нужно вычислять компоновку каждого элемента вручную.

Кроме того, мне пришлось переопределить layoutAttributesForElementsInRect:, который является методом, который используется макетом для выбора отображаемых элементов.

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *result = [NSMutableArray array];
    NSInteger sectionCount = 1;
    if ([self.collectionView.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)])
    {
        sectionCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
    }
    for (int s = 0; s < sectionCount; s++)
    {
        NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:s];
        for (int i = 0; i < itemCount; i++)
        {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s];
            UICollectionViewLayoutAttributes *layout = [self layoutAttributesForItemAtIndexPath:indexPath];
            if (CGRectIntersectsRect(rect, layout.frame))
            {
                [result addObject:layout];
            }
        }
    }
    return result;
}

Здесь я просто пробую каждый элемент, и если он находится в заданном rect, я возвращаю его.

Если этот подход является способом, у меня есть следующие более конкретные вопросы:

  • Можно ли упростить переопределение layoutAttributesForElementsInRect: или сделать его более эффективным?
  • Я что-то упустил? По крайней мере, замена ячеек разных страниц приводит к нечетным результатам. Я подозреваю, что это связано с initialLayoutAttributesForAppearingItemAtIndexPath: и finalLayoutAttributesForDisappearingItemAtIndexPath:, но я не могу точно определить, в чем проблема.
  • В моем случае f(n) зависит от количества столбцов и строк каждой страницы. Есть ли способ извлечь эту информацию из UICollectionViewFlowLayout, за исключением жесткого кодирования? Я думал о запросе layoutAttributesForElementsInRect: с границами представления коллекции и выводил строки и столбцы оттуда, но это также неэффективно.
4b9b3361

Ответ 1

Я много думал о вашем вопросе и пришел к следующим соображениям:

Подклассификация FlowLayout представляется самым правильным и наиболее эффективным способом переупорядочивания ячеек и использования анимаций макета потока. И ваш подход работает, за исключением двух важных вещей:

  • Скажем, у вас есть просмотр коллекции только с двумя ячейками, и вы создали свою страницу так, чтобы она содержала 9 ячеек. Первая ячейка будет располагаться в верхнем левом углу представления, как в исходном макете потока. Однако ваша вторая ячейка должна располагаться в верхней части представления и имеет указательный путь [0, 1]. Переупорядоченный путь индекса будет [0, 3] (путь указателя к исходной ячейке макета потока, который будет на его месте). И в вашем переопределении layoutAttributesForItemAtIndexPath вы отправите сообщение, например [super layoutAttributesForItemAtIndexPath:[0, 3]], вы получите объект nil, только потому, что есть только 2 ячейки: [0,0] и [0,1]. И это будет проблемой для вашей последней страницы.
  • Несмотря на то, что вы можете реализовать поведение подкачки, переопределив targetContentOffsetForProposedContentOffset:withScrollingVelocity: и вручную задав такие свойства, как itemSize, minimumLineSpacing и minimumInteritemSpacing, очень важно сделать ваши объекты симметричными, определить границы поискового вызова и так далее на.

I thnik, подклассификация макета потока готовит для вас большую реализацию, потому что то, что вы хотите, больше не является компоновкой потока. Но подумайте об этом вместе. Что касается ваших вопросов:

  • переопределение layoutAttributesForElementsInRect: - это именно то, как выполняется исходная реализация apple, поэтому нет возможности упростить его. Однако для вашего случая вы можете рассмотреть следующее: если у вас есть 3 строки элементов на странице, а кадр элемента в первой строке пересекает прямой фрейм, тогда (если все элементы имеют одинаковый размер) кадры элементов второй и третьей строки пересечь этот прямоугольник.
  • Извините, я не понял ваш второй вопрос.
  • в моем случае функция переупорядочения выглядит следующим образом: (a - целое число строк/столбцов на каждой странице, rows = columns)

f (n) = (n% a²) + (a - 1) (col - row) + a² (n/a²); col = (n% a²)% a; row = (n% a²)/a;

Отвечая на вопрос, макет потока не знает, сколько строк в каждом столбце, потому что это число может варьироваться от столбца к столбцу в зависимости от размера каждого элемента. Он также не может сказать ничего о количестве столбцов на каждой странице, потому что это зависит от положения прокрутки и также может варьироваться. Таким образом, нет лучшего способа, чем запрос layoutAttributesForElementsInRect, но это будет также включать ячейки, которые только частично видны. Поскольку ваши ячейки равны по размеру, вы можете теоретически узнать, сколько строк имеет ваш просмотр коллекции с горизонтальным режимом прокрутки: путем запуска итерации каждой ячейки, подсчитывающей их, и разбиения, если изменяется их frame.origin.x.

Итак, я думаю, у вас есть два варианта для достижения вашей цели:

  • Подкласс UICollectionViewLayout. Кажется, что много работы реализуют все эти методы, но это единственный эффективный способ. У вас могут быть такие свойства, как itemSize, itemsInOneRow. Тогда вы можете легко найти формулу для расчета кадра каждого элемента на основе его номера (лучший способ сделать это в prepareLayout и сохранить все кадры в массиве, чтобы вы могли получить доступ к кадру, который вам нужен в layoutAttributesForItemAtIndexPath). Реализация layoutAttributesForItemAtIndexPath, layoutAttributesForItemsInRect и collectionViewContentSize будет очень простой. В initialLayoutAttributesForAppearingItemAtIndexPath и finalLayoutAttributesForDisappearingItemAtIndexPath вы можете просто установить альфа-атрибут равным 0.0. Это то, как работают анимации макета стандартного потока. Переопределяя targetContentOffsetForProposedContentOffset:withScrollingVelocity:, вы можете реализовать "поведение пейджинга".

  • Рассмотрите возможность создания вида коллекции с раскладкой потока, pagingEnabled = YES, горизонтального направления прокрутки и размера элемента, равного размеру экрана. Один элемент на размер экрана. В каждую ячейку вы можете установить новое представление коллекции как subview с вертикальной раскладкой потока и тем же источником данных, что и другие виды коллекций, но со смещением. Это очень эффективно, потому что тогда вы повторно используете целые представления коллекции, содержащие блоки из 9 (или других) ячеек, вместо повторного использования каждой ячейки со стандартным подходом. Все анимации должны работать должным образом.

Здесь вы можете загрузить образец проекта, используя подход подкласса макета. (# 2)

Ответ 2

Не было бы простым решением иметь представления 2 коллекции со стандартным UICollectionViewFlowLayout?

Или даже лучше: иметь контроллер просмотра страницы с горизонтальной прокруткой, и каждая страница будет представлять собой коллекцию с обычной компоновкой потока.

Идея следующая: в вашем UICollectionViewController -init method вы создаете второй вид коллекции с смещением рамки справа по вашей первоначальной ширине просмотра коллекции. Затем вы добавляете его в виде subview в исходный вид коллекции. Чтобы переключаться между видами коллекции, просто добавьте распознаватель прокрутки. Чтобы вычислить значения смещения, вы можете сохранить исходный кадр вида коллекции в ivar cVFrame. Чтобы идентифицировать представления коллекции, вы можете использовать теги.

Пример метода init:

CGRect cVFrame = self.collectionView.frame;
UICollectionView *secondView = [[UICollectionView alloc] 
              initWithFrame:CGRectMake(cVFrame.origin.x + cVFrame.size.width, 0, 
                             cVFrame.size.width, cVFrame.size.height) 
       collectionViewLayout:[UICollectionViewFlowLayout new]];
    [secondView setBackgroundColor:[UIColor greenColor]];
    [secondView setTag:1];
    [secondView setDelegate:self];
    [secondView setDataSource:self];
    [self.collectionView addSubview:secondView];

UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
                      initWithTarget:self action:@selector(swipedRight)];
    [swipeRight setDirection:UISwipeGestureRecognizerDirectionRight];

UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] 
                       initWithTarget:self action:@selector(swipedLeft)];
    [swipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];

    [self.collectionView addGestureRecognizer:swipeRight];
    [self.collectionView addGestureRecognizer:swipeLeft];

Пример методов swipeRight и swipeLeft:

-(void)swipedRight {
    // Switch to left collection view
    [self.collectionView setContentOffset:CGPointMake(0, 0) animated:YES];
}

-(void)swipedLeft {
    // Switch to right collection view
    [self.collectionView setContentOffset:CGPointMake(cVFrame.size.width, 0) 
                                 animated:YES];
}

И тогда это не большая проблема для внедрения методов DataSource (в вашем случае вы хотите иметь 9 элементов на каждой странице):

-(NSInteger)collectionView:(UICollectionView *)collectionView 
    numberOfItemsInSection:(NSInteger)section {
    if (collectionView.tag == 1) {
         // Second collection view
         return self.dataArray.count % 9;
    } else {
         // Original collection view
         return 9; // Or whatever
}

В методе -collectionView:cellForRowAtIndexPath вам нужно будет получить данные из вашей модели со смещением, если это второй вид коллекции.

Также не забудьте зарегистрировать класс для многоразовой ячейки для второго просмотра коллекции. Вы также можете создать только один распознаватель жестов и распознать стрелки влево и вправо. Это зависит от вас.

Я думаю, теперь он должен работать, попробуйте: -)

Ответ 3

У вас есть объект, который реализует протокол UICollectionViewDataSource. Внутри collectionView:cellForItemAtIndexPath: просто верните правильный элемент, который вы хотите вернуть. Я не понимаю, где будет проблема.

Изменить: хорошо, я вижу проблему. Вот решение: http://www.skeuo.com/uicollectionview-custom-layout-tutorial, в частности шаги с 17 по 25. Это не огромная работа и может быть легко использована повторно.

Ответ 4

Это мое решение, я не тестировал анимацию, но думаю, что это будет хорошо работать с небольшими изменениями, надеюсь, что это будет полезно

CELLS_PER_ROW = 4;
CELLS_PER_COLUMN = 5;
CELLS_PER_PAGE = CELLS_PER_ROW * CELLS_PER_COLUMN;

UICollectionViewFlowLayout* flowLayout = (UICollectionViewFlowLayout*)collectionView.collectionViewLayout;
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flowLayout.itemSize = new CGSize (size.width / CELLS_PER_ROW - delta, size.height / CELLS_PER_COLUMN - delta);
flowLayout.minimumInteritemSpacing = ..;
flowLayout.minimumLineSpacing = ..;
..

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger index = indexPath.row;
    NSInteger page = index / CELLS_PER_PAGE;
    NSInteger indexInPage = index - page * CELLS_PER_PAGE;
    NSInteger row = indexInPage % CELLS_PER_COLUMN;
    NSInteger column = indexInPage / CELLS_PER_COLUMN;

    NSInteger dataIndex = row * CELLS_PER_ROW + column + page * CELLS_PER_PAGE;

    id cellData = _data[dataIndex];
    ...
}