57

UICollectionViewスクロールを開始するまで、問題なく動作するがあります。最初にいくつかの写真を次に示します。 ここに画像の説明を入力

ご覧のとおり素晴らしいです。スクロールを開始すると (ページングが有効になっています)、最初のスクロールが少し画面外に出ます。 ここに画像の説明を入力

これが問題です。元のビューには 3 つのビューがあり、スクロールして 3 つのビューのみを表示したいと考えています。ただし、スクロールすると (ページングが有効)、最初のビューが少し隠れて、次のページの次の最初のビューが少し表示されます。

説明するのがちょっと難しいので、ここにビデオがあります: Video of the problem (Dropbox)

これが私のUICollectionView設定の写真です: ここに画像の説明を入力

誰かが助けてくれたら最高です!

4

16 に答える 16

78

基本的な問題は、Flow Layout がページングをサポートするように設計されていないことです。ページング効果を実現するには、セル間のスペースを犠牲にする必要があります。そして、セルフレームを慎重に計算し、コレクションビューフレームで余りなく分割できるようにします。その理由を説明します。

次のレイアウトを言うと、あなたが望んでいたものです。

ここに画像の説明を入力

一番左の余白 (緑) はセル間隔の一部ではないことに注意してください。これは、フロー レイアウト セクション インセットによって決まります。フロー レイアウトは異種の間隔値をサポートしていないためです。これは簡単な作業ではありません。

したがって、間隔とインセットを設定した後。次のレイアウトが得られます。

ここに画像の説明を入力

次のページにスクロールした後。あなたの細胞は明らかにあなたが期待したように整列していません.

ここに画像の説明を入力

セル間隔を 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 をキャッシュすることをお勧めします。

于 2013-11-22T23:38:19.000 に答える
28

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];
}
于 2014-03-16T20:14:19.350 に答える
20

この答えはかなり遅れていますが、私はちょうどこの問題で遊んでいて、ドリフトの原因が行間であることがわかりました。UICollectionView/FlowLayoutをセル幅の正確な倍数でページングする場合は、次を設定する必要があります。

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

水平スクロールでは行間隔が影響するとは思わないでしょうが、どうやらそうです。

私の場合、セル間にスペースを入れずに、一度に 1 セルずつ、左から右へのページングを試していました。ページをめくるたびに、目的の位置からのわずかなずれが発生し、直線的に蓄積されたように見えました。 毎ターン ~10.0 ポイント. 10.0がフロー レイアウトのminimumLineSpacingのデフォルト値であることに気付きました。0.0に設定すると、ドリフトはありません。境界幅の半分に設定すると、各ページが境界の半分だけドリフトしました。

minimumInteritemSpacingを変更しても効果はありませんでした。

編集 -- UICollectionViewFlowLayoutのドキュメントから:

@property (非アトミック) CGFloat maximumLineSpacing;

討論

...

垂直スクロール グリッドの場合、この値は連続する行間の最小間隔を表します。水平スクロール グリッドの場合、この値は連続する列間の最小間隔を表します。 この間隔は、ヘッダーと最初の行の間、または最終行とフッターの間のスペースには適用されません。

このプロパティのデフォルト値は 10.0 です。

于 2013-09-22T15:04:11.347 に答える
8

この質問が古いことは知っていますが、たまたまこれに出くわした人にとっては.

これを修正するには、MinimumInterItemSpacing を 0 に設定し、コンテンツのフレームを減らすだけです。

于 2013-08-06T00:49:41.017 に答える
7

@devdavid は、flowLayout.minimumLineSpacing をゼロにしました。

レイアウト エディターで行の最小間隔を 0 に設定して実行することもできます。

より簡単な方法

于 2016-08-30T11:51:55.093 に答える
6

私は問題を理解していると思います。こちらもご理解いただけるように努めます。

よく見ると、この問題は最初のページのスワイプだけでなく、徐々に発生することがわかります。

ここに画像の説明を入力

私が正しく理解している場合、現在、あなたのアプリでは、すべての UICollectionView アイテムは丸みを帯びたボックスであり、それらすべての間に一定のオフセット/マージンがあります。これが問題の原因です。

代わりに、ビュー全体の幅の 1/3 の UICollectionView アイテムを作成し、その中に丸みを帯びたイメージ ビューを追加する必要があります。画像を参照するには、黒色ではなく緑色を UICollectionViewItem にする必要があります。

于 2012-11-18T10:43:27.667 に答える
3

あなたはあなた自身を転がしますUICollectionViewFlowLayoutか?

その場合、追加-(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocityすると、スクロールビューを停止する場所を計算するのに役立ちます。

これはうまくいくかもしれません(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);
}
于 2012-11-13T13:35:19.210 に答える
2

あなたUICollectionViewの幅は、セルサイズの幅+左右のインセットの正確な乗算でなければなりません。あなたの例では、セルの幅が 96 の場合、UICollectionViewの幅は になります(96 + 5 + 5) * 3 = 318。または、UICollectionView の 320 幅を維持したい場合は、セル サイズの幅を にする必要があります320 / 3 - 5 - 5 = 96.666

これで問題が解決しない場合UICollectionViewは、アプリケーションの実行時に の幅が xib ファイルで設定されているものと異なる可能性があります。これを確認するにNSLogは、実行時にビューのサイズを出力するステートメントを追加します。

NSLog(@"%@", NSStringFromCGRect(uiContentViewController.view.frame));
于 2012-11-18T14:08:22.393 に答える
1

これは私が経験していたのと同じ問題で、解決策を別の投稿に投稿したので、ここにもう一度投稿します。

それに対する解決策を見つけましたが、それには UICollectionViewFlowLayout のサブクラス化が含まれていました。

CollectionViewCell のサイズは 302 X 457 で、最小行間隔を 18 (セルごとに 9pix) に設定しています。

そのクラスから拡張する場合、オーバーライドする必要があるメソッドがいくつかあります。それらの1つは

  • (CGSize)collectionViewContentSize

このメソッドでは、UICollectionView の幅の合計を合計する必要がありました。これには、([データソース数] * widthOfCollectionViewCell) + ([データソース数] * 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);
}

これは私にとってはうまくいったので、他の誰かが役に立つことを願っています!

于 2013-03-14T05:02:04.240 に答える
0

ページングでも同様の問題がありました。セルのインセットがすべて 0 で、セルの幅と高さが とまったく同じサイズであったにもかかわらずUICollectionView、ページングは​​適切ではありませんでした。

私が気づいたのは、このバージョン ( UICollectionView、iOS 6)のバグのように聞こえUICollectionViewます。ただし、幅をたとえば 300px (同じ高さ) に減らすと、完全に機能するようになりました。

今後の参考になれば幸いです!

于 2013-07-11T09:56:27.820 に答える
0

セクションインセットとセルと行の間隔を持つセルの 3 x 3 グリッドで水平ページングを機能させようとすると、同様の問題が発生しました。

私にとっての答えは(UICollectionViewFlowLayoutのサブクラス化やさまざまなUIScrollViewデリゲートソリューションなど、多くの提案を試した後)簡単でした。データセットを 9 項目 (またはそれ以下) のセクションに分割し、numberOfSectionsInCollectionView および numberOfItemsInSection UICollectionView データソース メソッドを利用することで、UICollectionView のセクションを単純に使用しました。

UICollectionView の水平ページングが美しく機能するようになりました。現在、同様のシナリオで髪を引き裂いている人には、このアプローチをお勧めします.

于 2014-07-31T14:34:33.693 に答える
0

私はこの問題の解決策を持っていると思います。しかし、それが最高の場合はしません。

UICollectionViewFlowLayoutというプロパティが含まれていますsectionInset。したがって、セクション インセットを必要に応じて設定し、1 ページを 1 セクションに等しくすることができます。したがって、スクロールは自動的にページ ( = セクション) に適切に収まるはずです。

于 2013-02-06T10:48:11.120 に答える