UICollectionView выравнивание логика отсутствует в горизонтальном подкачки scrollview


у меня есть UICollectionView, который работает нормально, пока я не начала прокрутки. Вот некоторые фотографии сначала:

Как вы можете видеть, это здорово. Как только я начинаю прокрутку (подкачка включена) первый идет немного за кадром:

вот в чем проблема. Первоначально мой вид имеет 3 вида, и я хочу прокрутить и показать только 3 вида. Но при прокрутке (с включенной подкачкой) он скрывает немного первого представления и показывает немного следующего первого представления со следующей страницы.

а вот это видео, потому что это трудно объяснить: видео проблемы (Dropbox)

вот фотография моей UICollectionView настройки:

это будет здорово, если кто-то может помочь!

16 55

16 ответов:

основная проблема заключается в том, что макет потока не предназначен для поддержки подкачки. Для достижения эффекта подкачки вам придется пожертвовать пространством между ячейками. И тщательно рассчитать ячейки кадра и сделать его можно разделить на коллекцию вид кадра без остатков. Я объясню причину.

говоря, что следующий макет - это то, что вы хотели.

enter image description here

обратите внимание, что самый левый край (зеленый) не является частью интервала между ячейками. Это определяется вставкой сечения компоновки потока. Поскольку макет потока не поддерживает гетерогенное значение интервала. Это не тривиальная задача.

поэтому после установки интервала и вставки. Следующий макет-это то, что вы получите.

enter image description here

после прокрутки до следующей страницы. Ваши клетки, очевидно, не выровнены, как вы ожидали.

enter image description here

оформление ячейками 0 может решить эту проблему. Однако, это ограничивает ваш дизайн, если вам нужно дополнительное поле на странице, особенно если оно отличается от интервала между ячейками. Это также требует, чтобы рамка вида была делима на рамку ячейки. Иногда это боль, если ваша рамка обзора не фиксирована (учитывая случай вращения).

реальное решение заключается в UICollectionViewFlowLayout подкласс и переопределить следующие методы

- (CGSize)collectionViewContentSize
{
    // Only support single section for now.
    // Only support Horizontal scroll 
    NSUInteger count = [self.collectionView.dataSource collectionView:self.collectionView
                                               numberOfItemsInSection:0];

    CGSize canvasSize = self.collectionView.frame.size;
    CGSize contentSize = canvasSize;
    if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
    {
        NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
        NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;
        NSUInteger page = ceilf((CGFloat)count / (CGFloat)(rowCount * columnCount));
        contentSize.width = page * canvasSize.width;
    }

    return contentSize;
}


- (CGRect)frameForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGSize canvasSize = self.collectionView.frame.size;

    NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
    NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;

    CGFloat pageMarginX = (canvasSize.width - columnCount * self.itemSize.width - (columnCount > 1 ? (columnCount - 1) * self.minimumLineSpacing : 0)) / 2.0f;
    CGFloat pageMarginY = (canvasSize.height - rowCount * self.itemSize.height - (rowCount > 1 ? (rowCount - 1) * self.minimumInteritemSpacing : 0)) / 2.0f;

    NSUInteger page = indexPath.row / (rowCount * columnCount);
    NSUInteger remainder = indexPath.row - page * (rowCount * columnCount);
    NSUInteger row = remainder / columnCount;
    NSUInteger column = remainder - row * columnCount;

    CGRect cellFrame = CGRectZero;
    cellFrame.origin.x = pageMarginX + column * (self.itemSize.width + self.minimumLineSpacing);
    cellFrame.origin.y = pageMarginY + row * (self.itemSize.height + self.minimumInteritemSpacing);
    cellFrame.size.width = self.itemSize.width;
    cellFrame.size.height = self.itemSize.height;

    if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
    {
        cellFrame.origin.x += page * canvasSize.width;
    }

    return cellFrame;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes * attr = [super layoutAttributesForItemAtIndexPath:indexPath];
    attr.frame = [self frameForItemAtIndexPath:indexPath];
    return attr;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray * attrs = [NSMutableArray array];

    [originAttrs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attr, NSUInteger idx, BOOL *stop) {
        NSIndexPath * idxPath = attr.indexPath;
        CGRect itemFrame = [self frameForItemAtIndexPath:idxPath];
        if (CGRectIntersectsRect(itemFrame, rect))
        {
            attr = [self layoutAttributesForItemAtIndexPath:idxPath];
            [attrs addObject:attr];
        }
    }];

    return attrs;
}

обратите внимание, выше фрагмент кода поддерживает только один раздел и горизонтальное направление прокрутки. Но это не трудно расширить.

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

вы можете отключить подкачку на UICollectionView и реализовать пользовательский механизм горизонтальной прокрутки / подкачки с пользовательской шириной страницы / смещением следующим образом:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    float pageWidth = 210;

    float currentOffset = scrollView.contentOffset.x;
    float targetOffset = targetContentOffset->x;
    float newTargetOffset = 0;

    if (targetOffset > currentOffset)
        newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth;
    else
        newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth;

    if (newTargetOffset < 0)
        newTargetOffset = 0;
    else if (newTargetOffset > scrollView.contentSize.width)
        newTargetOffset = scrollView.contentSize.width;

    targetContentOffset->x = currentOffset;
    [scrollView setContentOffset:CGPointMake(newTargetOffset, 0) animated:YES];
}

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

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
flowLayout.minimumLineSpacing = 0.0;

вы бы не подумали, что межстрочный интервал вступает в игру при горизонтальной прокрутке, но, по-видимому, это так.

в моем случае я экспериментировал с подкачкой слева направо, одна клетка в времени, без пространства между клетками. Каждый поворот страницы вносил крошечный дрейф от желаемого положения, и он, казалось, накапливался линейно. ~10.0 pts за ход. Я понял 10.0 значение по умолчанию minimumLineSpacing в макете потока. Когда я установил его в 0.0, без дрейфа, когда я установил его на половину ширины границ, каждая страница дрейфовала на дополнительную половину границ.

изменение minimumInteritemSpacing С нет эффекта.

редактировать -- из документации UICollectionViewFlowLayout:

@property (nonatomic) CGFloat minimumLineSpacing;

Обсуждение

...

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

значение по умолчанию этого свойства является 10.0.

enter image description here

решение из следующей статьи является элегантным и простым. Основная идея заключается в создании scrollView поверх вашего collectionView с передачей всех значений contentOffset.

http://b2cloud.com.au/tutorial/uiscrollview-paging-size/

это должно быть сказано путем реализации этого метода:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;

Я не достиг гладкой анимации, как это происходит с pagingEnabled = YES.

Я знаю, что этот вопрос старый, но для тех, кто случайно наткнулся на это.

все, что вам нужно сделать, чтобы исправить это установить MinimumInterItemSpacing в 0 и уменьшить кадр содержимого.

Я думаю, что понимаю проблему. Я постараюсь, чтобы Вы тоже это поняли.

Если вы посмотрите внимательно, то вы увидите, что эта проблема происходит только постепенно, а не только на первой странице салфетки.

enter image description here

Если я правильно понимаю, в вашем приложении в настоящее время каждый элемент UICollectionView-это те округленные поля, которые мы видим, и у вас есть некоторое смещение/поле между всеми из них, которое является постоянным. Это то, что вызывает вопрос.

вместо этого вы должны сделать элемент UICollectionView, который составляет 1/3 ширины всего вида, а затем добавить этот округленный вид изображения внутри него. Чтобы ссылаться на изображение, зеленый цвет должен быть вашим UICollectionViewItem, а не черным.

@devdavid был на месте на flowLayout.minimumLineSpacing до нуля.

Это также можно сделать в Редакторе макета, установив минимальный интервал для строк в 0:

Easier Way

вы катите свой собственный UICollectionViewFlowLayout?

Если да, то добавление -(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity поможет вам рассчитать, где scrollview должен остановиться.

Это может сработать (NB: НЕПРОВЕРЕНО!):

-(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
                             withScrollingVelocity:(CGPoint)velocity
{
  CGFloat offsetAdjustment = MAXFLOAT;
  CGFloat targetX = proposedContentOffset.x + self.minimumInteritemSpacing + self.sectionInset.left;

  CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);

  NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
  for(UICollectionViewLayoutAttributes *layoutAttributes in array) {

    if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
      CGFloat itemX = layoutAttributes.frame.origin.x;

      if (ABS(itemX - targetX) < ABS(offsetAdjustment)) {
        offsetAdjustment = itemX - targetX;
      }
    }
  }

  return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}

мой ответ основан на ответе https://stackoverflow.com/a/27242179/440168 но проще.

enter image description here

вы должны UIScrollView выше UICollectionView и дать им равные размеры:

@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;

настроить contentInset посмотреть коллекции, например:

CGFloat inset = self.view.bounds.size.width*2/9;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);

и contentSize из вида прокрутки:

self.scrollView.contentSize = CGSizeMake(self.placesCollectionView.bounds.size.width*[self.collectionView numberOfItemsInSection:0],0);

не забудьте установить делегат прокрутки:

self.scrollView.delegate = self;

и реализовать основные магия:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView == self.scrollView) {
        CGFloat inset = self.view.bounds.size.width*2/9;
        CGFloat scale = (self.placesCollectionView.bounds.size.width-2*inset)/scrollView.bounds.size.width;
        self.collectionView.contentOffset = CGPointMake(scrollView.contentOffset.x*scale - inset, 0);
    }
}

код UICollectionViewширина должна быть точным умножением ширины размера ячейки + левая и правая вставки. В вашем примере, если ширина ячейки 96, то UICollectionView's ширина должна быть (96 + 5 + 5) * 3 = 318. Или, если вы хотите сохранить ширину 320 UICollectionView, ширина размера ячейки должна быть 320 / 3 - 5 - 5 = 96.666.

если это не поможет, ваш UICollectionView's ширина может отличаться от того, что находится в xib файл, когда приложение запускается. Чтобы проверить это-добавьте NSLog заявление для печати размер представления во время выполнения:

NSLog(@"%@", NSStringFromCGRect(uiContentViewController.view.frame));

Это та же проблема, с которой я столкнулся, и я опубликовал свое решение на другом посту, поэтому я снова опубликую его здесь.

Я нашел решение для него, и он включал в себя подклассы UICollectionViewFlowLayout.

мой размер CollectionViewCell составляет 302 X 457, и я установил свой минимальный межстрочный интервал равным 18 (9pix для каждой ячейки)

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

  • (CGSize)collectionViewContentSize

в этом методе мне нужно было сложить общую ширину того, что было в UICollectionView. Это включает в себя ([datasource count] * widthOfCollectionViewCell) + ([datasource count] * 18)

вот мои пользовательские методы UICollectionViewFlowLayout....

-(id)init
{
    if((self = [super init])){

       self.itemSize = CGSizeMake(302, 457);
       self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
       self.minimumInteritemSpacing = 0.0f;
       self.minimumLineSpacing = 18.0f;
       [self setScrollDirection:UICollectionViewScrollDirectionHorizontal];
   }
    return self;
}



-(CGSize)collectionViewContentSize{
   return CGSizeMake((numCellsCount * 302)+(numCellsCount * 18), 457);
}

это сработало для меня, поэтому я надеюсь, что кто-то еще найдет это полезным!

вы также можете установить ширину представления в "320+интервал", а затем установить страницу включить да. он будет прокручивать '320+интервал для каждого время. я думаю, что это потому, что page enable будет прокручивать ширину представления, но не ширину экрана.

Я думаю, что у меня есть решение этой проблемы. Но я этого не делаю, если это самое лучшее.

UICollectionViewFlowLayout не содержит свойство с именем sectionInset. Таким образом, Вы можете установить вставку раздела на все, что вам нужно, и сделать 1 страницу равной одному разделу. Поэтому ваша прокрутка должна автоматически правильно вписываться в страницы (=разделы)

у меня была аналогичная проблема с пейджингом. Несмотря на то, что все вставки ячеек были 0, а ячейка была точно такого же размера по ширине и высоте, как UICollectionView, пейджинг не был правильным.

то, что я заметил, звучит как ошибка в этой версии (UICollectionView, iOS 6): я мог бы видеть, что если бы я работал с UICollectionView с шириной = 310px или выше, и высотой = 538px, я был в беде. Однако, если я уменьшил ширину, скажем, до 300px (той же высоты), я получил работу отлично!

для некоторых будущих ссылок, я надеюсь, что это поможет!

Я столкнулся с аналогичной проблемой при попытке получить горизонтальную подкачку, работающую на сетке ячеек 3 x 3 с разделительными вставками и интервалом между ячейками и строками.

ответ для меня (после попытки многих предложений - включая подклассы UICollectionViewFlowLayout и различные решения делегата UIScrollView) был простым. Я просто использовал разделы в UICollectionView, разбив свой набор данных на разделы из 9 элементов (или меньше) и используя numberOfSectionsInCollectionView и numberofitemsinsection UICollectionView методы источника данных.

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

Если вы используете flow-layout по умолчанию для вашего UICollectionView и не хотите никакого пространства между каждой ячейкой, вы можете установить его свойство miniumumLineSpacing равным 0 через:

((UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout).minimumLineSpacing = 0;