Зберігання contentOffset у UICollectionView при обертанні орієнтації інтерфейсу


74

Я намагаюся обробляти зміни орієнтації інтерфейсу в UICollectionViewController. Я намагаюся досягти того, щоб я хотів мати однаковий contentOffset після обертання інтерфейсу. Це означає, що його слід змінювати відповідно до співвідношення змін меж.

Початок у портреті із зміщенням вмісту { bounds.size.width * 2, 0}…

UICollectionView у portait

... має призвести до зміщення вмісту в альбомному режимі також із { bounds.size.width * 2, 0} (і навпаки).

UICollectionView в альбомному режимі

Обчислення нового зміщення не є проблемою, але не знаю, де (або коли) його встановити, щоб отримати плавну анімацію. Те, що я роблю за тариф, - це скасування макета willRotateToInterfaceOrientation:duration:та скидання зміщення змісту в didRotateFromInterfaceOrientation::

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                                duration:(NSTimeInterval)duration;
{
    self.scrollPositionBeforeRotation = CGPointMake(self.collectionView.contentOffset.x / self.collectionView.contentSize.width,
                                                    self.collectionView.contentOffset.y / self.collectionView.contentSize.height);
    [self.collectionView.collectionViewLayout invalidateLayout];
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
    CGPoint newContentOffset = CGPointMake(self.scrollPositionBeforeRotation.x * self.collectionView.contentSize.width,
                                           self.scrollPositionBeforeRotation.y * self.collectionView.contentSize.height);
    [self.collectionView newContentOffset animated:YES];
}

Це змінює зміщення вмісту після обертання.

Як я можу встановити його під час обертання? Я намагався встановити зміщення нового вмісту, willAnimateRotationToInterfaceOrientation:duration:але це призводить до дуже дивної поведінки.

Приклад можна знайти в моєму проекті на GitHub .


7
Ви знайшли рішення для цього?
Ghar

Відповіді:


33

Ви можете зробити це в контролері перегляду:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    guard let collectionView = collectionView else { return }
    let offset = collectionView.contentOffset
    let width = collectionView.bounds.size.width

    let index = round(offset.x / width)
    let newOffset = CGPoint(x: index * size.width, y: offset.y)

    coordinator.animate(alongsideTransition: { (context) in
        collectionView.reloadData()
        collectionView.setContentOffset(newOffset, animated: false)
    }, completion: nil)
}

Або в самому макеті: https://stackoverflow.com/a/54868999/308315


Точна і точна відповідь. Дуже дякую!
Кенан Бегіч,

Після численних досліджень це найелегантніше рішення!
iDoc

Це працює. Потрібно лише переконатися, що викликається viewWillTransition. Інший момент полягає в тому, що якщо наступна сторінка не є "цілою" сторінкою, вам просто потрібно взяти раунд змінної індексу.
Nathan Barreto

Чи слід використовувати floorфункцію замість round?
Маріо

@Mario Я використовую лише раунд
Gurjinder Singh

18

Рішення 1, "просто клацніть"

Якщо вам потрібно лише переконатися, що contentOffset закінчується в потрібному положенні, ви можете створити підклас UICollectionViewLayout і реалізувати метод targetContentOffsetForProposedContentOffset:. Наприклад, ви можете зробити щось подібне для обчислення сторінки:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
    NSInteger page = ceil(proposedContentOffset.x / [self.collectionView frame].size.width);
    return CGPointMake(page * [self.collectionView frame].size.width, 0);
}

Але проблема, з якою ви зіткнетесь, полягає в тому, що анімація для цього переходу надзвичайно дивна. Що я роблю у своїй справі (яка майже така ж, як і у вас):

Рішення 2, "плавна анімація"

1) Спочатку я встановив розмір комірки, яким можна керувати за допомогою collectionView: layout: sizeForItemAtIndexPath: метод делегування наступним чином:

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout  *)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return [self.view bounds].size;
}

Зверніть увагу, що [межі самоперегляду] змінюватимуться залежно від обертання пристрою.

2) Коли пристрій має обертатися, я додаю imageView поверх подання колекції з усіма масками зміни розміру. Цей вигляд насправді приховає дивацтво collectionView (оскільки воно знаходиться зверху), а оскільки метод willRotatoToInterfaceOrientation: викликається всередині блоку анімації, він буде обертатися відповідно. Я також зберігаю наступний contentOffset відповідно до показаного indexPath, щоб я міг виправити contentOffset, як тільки обертання буде зроблено:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                                duration:(NSTimeInterval)duration
{
    // Gets the first (and only) visible cell.
    NSIndexPath *indexPath = [[self.collectionView indexPathsForVisibleItems] firstObject];
    KSPhotoViewCell *cell = (id)[self.collectionView cellForItemAtIndexPath:indexPath];

    // Creates a temporary imageView that will occupy the full screen and rotate.
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[[cell imageView] image]];
    [imageView setFrame:[self.view bounds]];
    [imageView setTag:kTemporaryImageTag];
    [imageView setBackgroundColor:[UIColor blackColor]];
    [imageView setContentMode:[[cell imageView] contentMode]];
    [imageView setAutoresizingMask:0xff];
    [self.view insertSubview:imageView aboveSubview:self.collectionView];

    // Invalidate layout and calculate (next) contentOffset.
    contentOffsetAfterRotation = CGPointMake(indexPath.item * [self.view bounds].size.height, 0);
    [[self.collectionView collectionViewLayout] invalidateLayout];
}

Зверніть увагу, що мій підклас UICollectionViewCell має загальнодоступну властивість imageView.

3) Нарешті, останнім кроком є ​​"прив'язка" зміщення вмісту до дійсної сторінки та видалення тимчасового перегляду зображення.

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [self.collectionView setContentOffset:contentOffsetAfterRotation];
    [[self.view viewWithTag:kTemporaryImageTag] removeFromSuperview];
}

1
Ваша відповідь блискуча! Але ви пропустили дзвінки до супер у willRotateToInterfaceOrientation та didRotateFromInterfaceOrientation.
Валерій Ван

Це не спрацювало для мене як було, я замість цього змінив його, щоб не обчислювати зміщення після обертання, замість цього я зберігаю NSIndexPath, витягнутий при отриманні інформації про клітинку, і замість встановлення зміщення я прокручую до цього без анімації. Якщо хтось зацікавлений, я можу скласти фактичну відповідь із кодом (швидко).
mwright

15

Відповідь "просто прив'язати" вище для мене не спрацювала, оскільки часто не закінчувалась на елементі, який був у полі зору до обертання. Тому я вивів макет потоку, який використовує елемент фокусу (якщо встановлений) для обчислення зміщення вмісту. Я встановлюю елемент у willAnimateRotationToInterfaceOrientation і очищаю його в didRotateFromInterfaceOrientation. Налаштування вставки, здається, потрібно на IOS7, оскільки подання Колекція може розміщуватися під верхньою панеллю.

@interface HintedFlowLayout : UICollectionViewFlowLayout
@property (strong)NSIndexPath* pathForFocusItem;
@end

@implementation HintedFlowLayout

-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
    if (self.pathForFocusItem) {
        UICollectionViewLayoutAttributes* layoutAttrs = [self layoutAttributesForItemAtIndexPath:self.pathForFocusItem];
        return CGPointMake(layoutAttrs.frame.origin.x - self.collectionView.contentInset.left, layoutAttrs.frame.origin.y-self.collectionView.contentInset.top);
    }else{
        return [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
    }
}
@end

безумовно, найпростіше рішення
просто кодер

10

Для тих, хто використовує iOS 8+, willRotateToInterfaceOrientation та didRotateFromInterfaceOrientation застаріли.

Ви повинні використати наступне:

/* 
This method is called when the view controller's view's size is changed by its parent (i.e. for the root view controller when its window rotates or is resized). 
If you override this method, you should either call super to propagate the change to children or manually forward the change to children.
*/
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator 
{
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // Update scroll position during rotation animation
        self.collectionView.contentOffset = (CGPoint){contentOffsetX, contentOffsetY};
    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // Whatever you want to do when the rotation animation is done
    }];
}

Свіфт 3:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    coordinator.animate(alongsideTransition: { (context:UIViewControllerTransitionCoordinatorContext) in
        // Update scroll position during rotation animation
    }) { (context:UIViewControllerTransitionCoordinatorContext) in
        // Whatever you want to do when the rotation animation is done
    }
}

3
Як щодо повноекранних комірок? Коли я ковзаю до другої чи третьої комірки та обертаю, я бачу попередню комірку останньою та останньою другою :(
iTux

8

Я думаю, що правильним рішенням є перевизначення методу targetContentOffsetForProposedContentOffset: у підкласіUICollectionViewFlowLayout

З документів:

Під час оновлення макета або при переході між макетами подання колекції викликає цей метод, щоб надати вам можливість змінити запропонований зсув вмісту для використання в кінці анімації. Ви можете замінити цей метод, якщо анімація або перехід може спричинити позиціонування елементів таким чином, що не є оптимальним для вашого дизайну.


7

Підклас Swift 4.2:

class RotatableCollectionViewFlowLayout: UICollectionViewFlowLayout {

    private var focusedIndexPath: IndexPath?

    override func prepare(forAnimatedBoundsChange oldBounds: CGRect) {
        super.prepare(forAnimatedBoundsChange: oldBounds)
        focusedIndexPath = collectionView?.indexPathsForVisibleItems.first
    }

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
        guard let indexPath = focusedIndexPath
            , let attributes = layoutAttributesForItem(at: indexPath)
            , let collectionView = collectionView else {
                return super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
        }
        return CGPoint(x: attributes.frame.origin.x - collectionView.contentInset.left,
                       y: attributes.frame.origin.y - collectionView.contentInset.top)
    }

    override func finalizeAnimatedBoundsChange() {
        super.finalizeAnimatedBoundsChange()
        focusedIndexPath = nil
    }
}

1
Це більше те, що я шукав. Я хотів, щоб код обертання містився у моєму користувацькому підкласі UICollectionView, так що код обертання не повинен дублюватись у всій програмі. Однак це спричиняє прокрутку, що означає повторне використання клітин. Це може мати або не мати ненавмисних побічних ефектів.
Деніел Вільямс,

5

Щоб відмовитись від рішення troppoli, ви можете встановити зсув у власному класі, не турбуючись про те, щоб пам’ятати про впровадження коду у вашому контролері перегляду. prepareForAnimatedBoundsChangeповинно бути викликано при обертанні пристрою, а потім finalizeAnimatedBoundsChangeпісля його обертання.

@interface OrientationFlowLayout ()

@property (strong)NSIndexPath* pathForFocusItem;

@end

@implementation OrientationFlowLayout

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset {
    if (self.pathForFocusItem) {
        UICollectionViewLayoutAttributes* layoutAttrs = [self layoutAttributesForItemAtIndexPath:
                                                         self.pathForFocusItem];
        return CGPointMake(layoutAttrs.frame.origin.x - self.collectionView.contentInset.left,
                           layoutAttrs.frame.origin.y - self.collectionView.contentInset.top);
    }
    else {
        return [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
    }
}

- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds {
    [super prepareForAnimatedBoundsChange:oldBounds];
    self.pathForFocusItem = [[self.collectionView indexPathsForVisibleItems] firstObject];
}

- (void)finalizeAnimatedBoundsChange {
    [super finalizeAnimatedBoundsChange];
    self.pathForFocusItem = nil;
}

@end

чи потрібен виклик супер?
Просто кодер

Я реалізував це в Xamarin, чудово працює в iOS 11
Патрік Робертс

3

Ця проблема мене трохи турбувала. Відповідь, що отримала найбільшу кількість голосів, здалася мені занадто хакерською, тому я просто трохи згасив це і просто змінив альфу подання колекції відповідно до та після обертання. Я також не анімую оновлення зміщення вмісту.

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
self.collectionView.alpha = 0;
[self.collectionView.collectionViewLayout invalidateLayout];

self.scrollPositionBeforeRotation = CGPointMake(self.collectionView.contentOffset.x / self.collectionView.contentSize.width,
                                                self.collectionView.contentOffset.y / self.collectionView.contentSize.height);
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
CGPoint newContentOffset = CGPointMake(self.scrollPositionBeforeRotation.x * self.collectionView.contentSize.width,
                                       self.scrollPositionBeforeRotation.y * self.collectionView.contentSize.height);

[self.collectionView setContentOffset:newContentOffset animated:NO];
self.collectionView.alpha = 1;
}

Досить гладка і менш хакі.


3

Я використовую варіант fz. відповідь (iOS 7 і 8):

Перед обертанням:

  1. Зберігає поточний видимий шлях до індексу
  2. Створіть знімок collectionView
  3. Помістіть UIImageView з ним поверх подання колекції

Після обертання:

  1. Прокрутіть до збереженого індексу
  2. Видаліть зображення.

    @property (nonatomic) NSIndexPath *indexPath;
    
    - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                                    duration:(NSTimeInterval)duration {
        self.indexPathAfterRotation = [[self.collectionView indexPathsForVisibleItems] firstObject];
    
        // Creates a temporary imageView that will occupy the full screen and rotate.
        UIGraphicsBeginImageContextWithOptions(self.collectionView.bounds.size, YES, 0);
        [self.collectionView drawViewHierarchyInRect:self.collectionView.bounds afterScreenUpdates:YES];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    
        UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
        [imageView setFrame:[self.collectionView bounds]];
        [imageView setTag:kTemporaryImageTag];
        [imageView setBackgroundColor:[UIColor blackColor]];
        [imageView setContentMode:UIViewContentModeCenter];
        [imageView setAutoresizingMask:0xff];
        [self.view insertSubview:imageView aboveSubview:self.collectionView];
    
        [[self.collectionView collectionViewLayout] invalidateLayout];
    }
    
    - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
        [self.collectionView scrollToItemAtIndexPath:self.indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
    
        [[self.view viewWithTag:kTemporaryImageTag] removeFromSuperview];
    }
    

1
Я в підсумку також використав щось подібне, хоч би побачив першим твій
mwright

2

Після повороту орієнтації інтерфейсу UICollectionViewCell зазвичай переміщується в інше положення, оскільки ми не будемо оновлювати contentSize і contentOffset.

Тож видимий UICollectionViewCell завжди не знаходиться в очікуваному положенні.

Видимий UICollectionView, який ми очікували зображення наступним чином

Орієнтація, яку ми очікували

UICollectionView повинен делегувати функцію [collectionView sizeForItemAtIndexPath] 『UICollectionViewDelegateFlowLayout』.

І вам слід розрахувати розмір елемента в цій функції.

Спеціальний UICollectionViewFlowLayout повинен замінити функції наступним чином.

  1. -(void)prepareLayout

    .Встановіть itemSize, scrollDirection та інші.

  2. -(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity

    .Обчисліть номер сторінки або обчисліть зміщення видимого вмісту.

  3. -(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset

    .Повернути зміщення візуального вмісту.

  4. -(CGSize)collectionViewContentSize

    .Повернути загальний розмір вмісту collectionView.

Ваш viewController повинен замінити 『willRotateToInterfaceOrientation』, і в цій функції ви повинні викликати функцію [XXXCollectionVew.collectionViewLayout invalidateLayout];

Але 『willRotateToInterfaceOrientation』 застаріло в iOS 9, або ви можете різницею викликати функцію [XXXCollectionVew.collectionViewLayout invalidateLayout].

Ось такий приклад: https://github.com/bcbod2002/CollectionViewRotationTest


1
Будь ласка, опишіть, що ви зробили, або розкажіть щось про свій проект!
Alex Cio

2

у Свіфті 3.

вам слід відстежити, який елемент комірки (Сторінка) представляється, перш ніж повертати його за indexPath.item, координатою x або чимось іншим. Потім у вашому UICollectionView:

override func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {

    let page:CGFloat = pageNumber // your tracked page number eg. 1.0
    return CGPoint(x: page * collectionView.frame.size.width, y: -(topInset))
    //the 'y' value would be '0' if you don't have any top EdgeInset
}

У моєму випадку я анулюю макет у viewDidLayoutSubviews (), тому collectionView.frame.size.width - це ширина подання collectionVC, яке було повернуто.


1

Це працює як шарм:

-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return self.view.bounds.size;
}

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {

    int currentPage = collectionMedia.contentOffset.x / collectionMedia.bounds.size.width;
    float width = collectionMedia.bounds.size.height;

    [UIView animateWithDuration:duration animations:^{
        [self.collectionMedia setContentOffset:CGPointMake(width * currentPage, 0.0) animated:NO];
        [[self.collectionMedia collectionViewLayout] invalidateLayout];
}];
}

1

Якщо виявлено, що використання targetContentOffsetForProposedContentOffset працює не у всіх сценаріях, і проблема використання didRotateFromInterfaceOrientation полягає в тому, що він дає візуальні артефакти. Мій ідеально працює код такий:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    _indexPathOfFirstCell = [self indexPathsForVisibleItems].firstObject;
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
    if (_indexPathOfFirstCell) {
        [UIView performWithoutAnimation:^{
            [self scrollToItemAtIndexPath:self->_indexPathOfFirstCell atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
        }];
        _indexPathOfFirstCell = nil;
    }
}

Ключ полягає у використанні методу willRotateToInterfaceOrientation для визначення тієї частини у поданні, до якої ви хочете прокрутити, і willAnimationRotationToInterfaceOrientation для її перерахунку, коли подання змінило свій розмір (межі вже змінилися, коли цей метод викликається фреймворком) та фактично прокрутіть до нової позиції без анімації. У своєму коді я використав шлях індексу для першої візуальної комірки, але відсоток contentOffset.y / contentSize.height також зробив би роботу дещо по-іншому.


не забувайте, що API обертання застарів із iOS 8]
Даніель Галаско,

1

Що для мене означає робота:

  1. Встановіть розмір ваших моїх комірок за вашим моїм UICollectionViewDelegateFlowLayoutметодом

    func collectionView(collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout!, sizeForItemAtIndexPath indexPath: NSIndexPath!) -> CGSize
    {
        return collectionView.bounds.size
    }
    
  2. Після цього я реалізую willRotateToInterfaceOrientationToInterfaceOrientation:duration:так

    override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) 
    {
        let currentPage = Int(collectionView.contentOffset.x / collectionView.bounds.size.width)
    
        var width = collectionView.bounds.size.height
        UIView.animateWithDuration(duration) {
            self.collectionView.setContentOffset(CGPointMake(width * CGFloat(currentPage), 0.0), animated: false)
            self.collectionView.collectionViewLayout.invalidateLayout()
        }
    }
    

Вищезазначений код на Swift, але ви зрозуміли суть і легко "перекласти"


0

Можливо, вам захочеться приховати collectionView під час його (неправильної) анімації та натомість показати вид заповнювача комірки, який правильно обертається.

Для простої фотогалереї я знайшов спосіб зробити це, який виглядає цілком непогано. Дивіться мою відповідь тут: Як повернути UICollectionView, подібну до програми для фотографій, і зберегти поточний вигляд у центрі?


0

Мій спосіб - використовувати об’єкт UICollectionViewFlowlayout.

Встановіть міжрядковий інтервал ojbect, якщо він прокручується горизонтально.

[flowLayout setMinimumLineSpacing:26.0f];

Встановіть інтервал між проміжками, якщо він прокручується вертикально.

[flowLayout setMinimumInteritemSpacing:0.0f];

Зверніть увагу, що при повороті екрана він поводиться інакше. У моєму випадку у мене він прокручується горизонтально, тому мінімальний інтервал рядків становить 26.0f. Тоді це здається жахливим, коли воно повертається в ландшафтний напрямок. Мені потрібно перевірити обертання та встановити мінімальний інтервал ліній для цього напрямку 0.0f, щоб зробити це правильно.

Це воно! Простий.


0

У мене виникла проблема з моїм проектом, я використовував два різні макети для UICollectionView.

mCustomCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"LandScapeCell" forIndexPath:indexPath];

theCustomCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"PortraitCell" forIndexPath:indexPath];

Потім перевірте його для кожної орієнтації та використовуйте свою конфігурацію для кожної орієнтації.


0
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    CGSize pnt = CGSizeMake(70, 70);
    return pnt; }

-(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {

//    UIEdgeInsetsMake(<#CGFloat top#>, <#CGFloat left#>, <#CGFloat bottom#>, <#CGFloat right#>)
    return UIEdgeInsetsMake(3, 0, 3, 0); }

Таким чином ви можете регулювати зміщення вмісту та розмір комірки.


0

Використовуйте <CollectionViewDelegateFlowLayout>і в методіdidRotateFromInterfaceOrientation: перезавантажте дані CollectionView.

Реалізуйте collectionView:layout:sizeForItemAtIndexPath:метод <CollectionViewDelegateFlowLayout>і в методі перевірте орієнтацію інтерфейсу та застосуйте власний розмір кожної комірки.

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{

    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];

    if (UIInterfaceOrientationIsPortrait(orientation)) {

        return CGSizeMake(CGFloat width, CGFloat height);

    } else {

        return CGSizeMake(CGFloat width, CGFloat height);

    }

}

0

У мене є подібний випадок, коли я використовую це

- (void)setFrame:(CGRect)frame
{
    CGFloat currentWidth = [self frame].size.width;
    CGFloat offsetModifier = [[self collectionView] contentOffset].x / currentWidth;

    [super setFrame:frame];

    CGFloat newWidth = [self frame].size.width;

    [[self collectionView] setContentOffset:CGPointMake(offsetModifier * newWidth, 0.0f) animated:NO];
}

Це подання, яке містить collectionView. У суперпрегляді я теж це роблю

- (void)setFrame:(CGRect)frame
{    
    UICollectionViewFlowLayout *collectionViewFlowLayout = (UICollectionViewFlowLayout *)[_collectionView collectionViewLayout];

    [collectionViewFlowLayout setItemSize:frame.size];

    [super setFrame:frame];
}

Це налаштовує розміри комірок на повноекранний режим (точніше на повний перегляд;)) Якщо ви цього не зробите тут, може з’явитися багато повідомлень про помилки про те, що розмір комірки більший, ніж колекція, і що поведінка цього не визначена, і бла бла бла .....

Ці методи, звичайно, можуть бути об'єднані в один підклас колекційного перегляду або у подання, що містить колекційний перегляд, але для мого поточного проекту це був логічний шлях.


0

Відповідь "просто закріпити" - це правильний підхід і не вимагає додаткового згладжування за допомогою накладених знімків IMO. Однак є проблема, яка пояснює, чому деякі люди бачать, що в деяких випадках правильна сторінка не прокручується. При обчисленні сторінки потрібно використовувати висоту, а не ширину. Чому? Оскільки геометрія подання вже повернута на час, коли викликається targetContentOffsetForProposedContentOffset, і тому, якою була ширина, це зараз висота. Також заокруглення є більш розумним, ніж стеля. Так:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
    NSInteger page = round(proposedContentOffset.x / self.collectionView.bounds.size.height);
    return CGPointMake(page * self.collectionView.bounds.size.width, 0);
}

0

Я вирішив цю проблему за допомогою наступних кроків:

  1. Обчислити поточний прокручуваний NSIndexPath
  2. Вимкніть прокрутку та розміщення сторінок у UICollectionView
  3. Застосувати новий макет потоку до UICollectionView
  4. Увімкніть прокрутку та розміщення сторінок у UICollectionView
  5. Прокрутіть UICollectionView до поточного NSIndexPath

Ось шаблон коду, що демонструє вищевказані кроки:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                            duration:(NSTimeInterval)duration;
{
     //Calculating Current IndexPath
     CGRect visibleRect = (CGRect){.origin = self.yourCollectionView.contentOffset, .size = self.yourCollectionView.bounds.size};
     CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
     self.currentIndexPath = [self.yourCollectionView indexPathForItemAtPoint:visiblePoint];

     //Disable Scrolling and Pagination
     [self disableScrolling];

     //Applying New Flow Layout
     [self setupNewFlowLayout];

     //Enable Scrolling and Pagination
     [self enableScrolling];
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
     //You can also call this at the End of `willRotate..` method.
     //Scrolling UICollectionView to current Index Path
     [self.yourCollectionView scrollToItemAtIndexPath:self.currentIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
}

- (void) disableScrolling
{
    self.yourCollectionView.scrollEnabled   = false;
    self.yourCollectionView.pagingEnabled   = false;
}

- (void) enableScrolling
{
    self.yourCollectionView.scrollEnabled   = true;
    self.yourCollectionView.pagingEnabled   = true;
}

- (void) setupNewFlowLayout
{
    UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc] init];
    flowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
    flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    flowLayout.minimumInteritemSpacing = 0;
    flowLayout.minimumLineSpacing = 0;
    [flowLayout setItemSize:CGSizeMake(EXPECTED_WIDTH, EXPECTED_HEIGHT)];

    [self.yourCollectionView setCollectionViewLayout:flowLayout animated:YES];
    [self.yourCollectionView.collectionViewLayout invalidateLayout];
}

Сподіваюся, це допоможе.


0

У мене виникли проблеми з animateAlongsideTransitionблокуванням animateAlongsideTransition(див. Код нижче).

Зверніть увагу, що вона викликається під час (але не раніше) анімації. Моїм завданням було оновити положення прокрутки подання таблиці, використовуючи прокрутку до верхнього видимого рядка (я зіткнувся з проблемою на iPad, коли комірки подання таблиці змістилися вгору, коли пристрій обертання, тому я знаходив рішення цієї проблеми). Але, можливо, це було б корисно і для contentOffset.

Я спробував вирішити проблему таким чином:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    __weak TVChannelsListTableViewController *weakSelf = self;

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        weakSelf.topVisibleRowIndexPath = [[weakSelf.tableView indexPathsForVisibleRows] firstObject];
    } completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        [weakSelf.tableView scrollToRowAtIndexPath:weakSelf.topVisibleRowIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
    }];
}

Але це не спрацювало. Наприклад, шлях індексу верхньої ячейки був (0, 20). Але коли було викликано блок обертання пристрою animateAlongsideTransition і [[слабший.табливийВієв indexPathsForVisibleRows] firstObject] повернув шлях до індексу (0, 27).

Я думав, що проблема полягає у отриманні шляхів до індексу до weakSelf. Тому для вирішення проблеми, яку я переніс self.topVisibleRowIndexPathдо [coordinator animateAlongsideTransition: completion]виклику методу:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    __weak TVChannelsListTableViewController *weakSelf = self;
    self.topVisibleRowIndexPath = [[weakSelf.tableView indexPathsForVisibleRows] firstObject];

    [coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        [weakSelf.tableView scrollToRowAtIndexPath:weakSelf.topVisibleRowIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
    }];
}

І ще одна цікава річ, яку я виявив, - це те, що застарілі методи willRotateToInterfaceOrientationі willRotateToInterfaceOrientationдо цих пір успішно викликаються в iOS пізніше 8.0, коли методviewWillTransitionToSize не перевизначений.

Тож іншим способом вирішити проблему в моєму випадку було використання застарілого методу замість нового. Думаю, це буде неправильним рішенням, але можна спробувати, якщо інші способи не працюють :)


-2

Ви можете спробувати цей неперевірений код:

- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation
                                 duration: (NSTimeInterval)         duration
{
    [UIView animateWithDuration: duration
                      animation: ^(void)
     {
       CGPoint newContentOffset = CGPointMake(self.scrollPositionBeforeRotation.x *
                                              self.collectionView.contentSize.height,
                                              self.scrollPositionBeforeRotation.y *
                                              self.collectionView.contentSize.width);
       [self.collectionView setContentOffset: newContentOffset
                                    animated: YES];
     }];
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.