74

UICollectionViewControllerでインターフェイスの向きの変更を処理しようとしています。私が達成しようとしているのは、インターフェイスの回転後に同じcontentOffsetを設定したいということです。つまり、境界の変化の比率に応じて変更する必要があります。

{ bounds.size.width * 2、0}のコンテンツオフセットで縦向きから開始…</ p>

portaitのUICollectionView

…また、{ 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の私のプロジェクトにあります。

4

24 に答える 24

38

ビューコントローラーでこれを行うことができます:

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

于 2017-04-10T12:05:17.093 に答える
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;
}

[self.view bounds] は、デバイスの回転に応じて変化することに注意してください。

2) デバイスが回転しようとしているときに、すべてのサイズ変更マスクを使用して、コレクション ビューの上に imageView を追加しています。このビューは実際には collectionView の奇妙さを隠します (その上にあるため)。また、アニメーション ブロック内で willRotatoToInterfaceOrientation: メソッドが呼び出されるため、それに応じて回転します。また、表示された indexPath に従って次の contentOffset を保持しているので、回転が完了したら 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 のサブクラスには public imageView プロパティがあることに注意してください。

3) 最後に、最後のステップは、コンテンツ オフセットを有効なページに「スナップ」し、一時的なイメージビューを削除することです。

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [self.collectionView setContentOffset:contentOffsetAfterRotation];
    [[self.view viewWithTag:kTemporaryImageTag] removeFromSuperview];
}
于 2014-03-11T20:25:04.273 に答える
10

iOS 8 以降を使用している場合、willRotateToInterfaceOrientationdidRotateFromInterfaceOrientationは非推奨です。

今すぐ次を使用する必要があります。

/* 
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
    }
}
于 2015-03-16T14:57:03.757 に答える
8

正しい解決策は、サブクラス化されたtargetContentOffsetForProposedContentOffset:メソッドをオーバーライドすることだと思いますUICollectionViewFlowLayout

ドキュメントから:

レイアウトの更新時、またはレイアウト間の遷移時に、コレクション ビューはこのメソッドを呼び出して、提案されたコンテンツ オフセットを変更してアニメーションの最後に使用できるようにします。アニメーションまたはトランジションによってアイテムがデザインに最適ではない方法で配置される可能性がある場合は、このメソッドをオーバーライドできます。

于 2014-03-08T04:57:53.400 に答える
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
于 2016-10-03T23:40:04.943 に答える
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;
}

かなり滑らかでハックが少ない。

于 2014-11-13T14:45:12.560 に答える
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 のビューの幅になります。

于 2016-12-28T11:40:37.647 に答える
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];
}];
}
于 2014-11-12T13:03:43.700 に答える
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 のパーセンテージもわずかに異なる方法でジョブを実行します。

于 2014-11-25T20:38:58.790 に答える
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); }

このようにして、コンテンツのオフセットとセルのサイズを調整できます。

于 2013-12-05T17:12:02.313 に答える
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);

    }

}
于 2014-02-03T06:06:09.023 に答える
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];
}

これが役立つことを願っています。

于 2016-04-14T12:16:45.407 に答える
0

私のやり方は、UICollectionViewFlowlayout オブジェクトを使用することです。

水平にスクロールする場合のオブジェクトの行間を設定します。

[flowLayout setMinimumLineSpacing:26.0f];

垂直方向にスクロールする場合のアイテム間の間隔を設定します。

[flowLayout setMinimumInteritemSpacing:0.0f];

画面を回転させると動作が異なることに注意してください。私の場合、水平方向にスクロールするので、最小行間隔は 26.0f です。それから横向きに回転すると恐ろしいように見えます。回転を確認し、その方向の最小行間隔を 0.0f に設定して正しくする必要があります。

それでおしまい!単純。

于 2013-11-28T13:06:05.587 に答える
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);
}
于 2015-11-04T18:13:17.590 に答える
0

(正しくない) アニメーション中に collectionView を非表示にし、代わりに正しく回転するセルのプレースホルダー ビューを表示することができます。

シンプルなフォト ギャラリーの場合、非常に見栄えの良い方法を見つけました。ここで私の答えを参照してください: 写真アプリに似た UICollectionView を回転させ、現在のビューを中央に保持する方法は?

于 2013-09-24T21:00:50.577 に答える
-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];
     }];
}
于 2012-12-08T17:20:56.093 に答える