web-dev-qa-db-ja.com

ピンチズームをUICollectionViewに追加する

はじめに

達成したい効果について説明します。次に、これを現在どのように実装しようとしているのか、そして現状の動作の何が悪いのかについて詳しく説明します。私が見たが、まったく機能しなかった別のアプローチについても触れます。

最も関連性の高いコードが質問の下部にインラインで表示され、すばやくアクセスできます。 ソースのZipをダウンロードまたはプロジェクトを-として取得できます Mercurial RepositoryBitBucketで。プロジェクトには、以下の回答からの修正が組み込まれています。壊れたバージョンを最初に提供したい場合は、 "initial-buggy-version"のタグが付けられます

このプロジェクトは、効果が実行可能かどうかを評価するための最小限の概念実証/スパイクであるため、かなり軽くてシンプルです!

望ましい効果

アプリは、垂直テーブルを形成する多数の個別の情報行を表示します。テーブルはユーザーが垂直方向にスクロールできます。これはUITableViewの標準的な動作であり、UICollectionViewも使用できます。ただし、アプリはピンチスケーリングもサポートする必要があります。テーブルでズームをピンチすると、allの行がつぶれます。伸ばすと、allの線が引き離されます。

私の概念実証では、個々のセルのサイズは変更されていません。それらのセルは、より近くまたはより離れて再配置されています。これは意図的なものです。アイデアの実現可能性を検証することが重要だとは思いません。

現在のアプリがどのようにズームアウトおよびズームアウトされているかを示す画面グラブは次のとおりです。

Zoomed in imageZoomed out image

現在の実装

私はカスタムUICollectionViewサブクラスでUICollectionViewLayoutを使用しています。レイアウトは、UICollectionViewCellsを画面の真ん中に下がるうねりの良い正弦波に配置します。各UICollectionViewCellは、UILabel行を保持するindexPathのコンテナーにすぎません。

UICollectionViewLayoutサブクラスには、説明する各セル間の垂直方向の間隔をUICollectionViewに設定するパラメーターがあり、これを調整することで、テーブルを必要に応じて垂直方向に押しつぶしたり垂直方向に拡大したりできます。

私のUICollectionViewControllerサブクラスにはUIPinchGestureRecognizerがあります。認識機能がスケールの変更を検出すると、UICollectionViewのレイアウトの垂直セル間隔がそれに応じて変更されます。

これ以上考慮しなければ、スケーリングは、タッチジェスチャの中心ではなく、コンテンツの上部から発生します。 UICollectionViewcontentOffsetプロパティは、この機能を提供するためにピンチ中に調整されます。

ジェスチャーレコグナイザーは、つまむときに発生するドラッグに対応する必要もあります。これは、UICollectionViewcontentOffsetを変更することによっても処理されます。いくつかの追加コードでは、ジェスチャーに指を追加したり、ジェスチャーから指を削除したりするときに、タッチジェスチャーの中心点を変更できます。

UICollectionViewUIScrollViewサブクラスであるため、独自に追加したUIPanGestureRecognizerと相互作用する独自のUIPinchGestureRecogniserがあることに注意してください。これが問題の原因かどうかはわかりません。

ピンチジェスチャー中にUICollectionViewの組み込みのスクロールを無効にするコードを追加しましたが、それほど大きな違いはないようです。 gestureRecognizer:shouldRequireFailureOfGestureRecognizer:を使用してUIPinchGestureRecognizerを組み込みのUIPanGestureRecognizerで失敗させようとしましたが、これによりピンチ認識機能がまったく動作しなくなったようです。これが私が愚かなのか、iOSのバグなのかはわかりません。

前述のように、現在のUICollectionViewCellsはサイズ変更されません。それらは単に再配置されます。これは意図的なものです。この概念を検証することは重要ではないと思います。

機能するもの

動作ビットは非常によく機能します。テーブルを上下にドラッグできます。ドラッグ中に、指を追加してピンチを開始し、指を離してドラッグを続行し、追加してピンチするなどを行うことができます。すべて非常にスムーズです。オリジナルのiPhone 5では、ピンチとパンをスムーズにサポートし、画面上に200以上のビューを表示します。

機能しないもの1

ビューの上部または下部が画面上にあるときにピンチインおよびピンアウトしようとすると、すべてが少し気が狂います。

  • スクロールすると、ビューはドラッグして、表示可能なコンテンツを超えて引き出すことができます(iOSのデータリストの標準的な動作であるため、これが必要です)。
  • ただし、スケールが変更されると、ビューがスナップされてコンテンツが画面に固定されます(これが発生するのは望ましくありません)。

ピンチジェスチャーの間、これら2つは互いに戦うため、コンテンツが上下に激しくちらつきます(これは絶対に必要ありません!)。

機能しないもの2

UICollectionViewのデフォルトのスクロールは、スクロール中に手を離すと減速し、外側にスクロールするとスムーズに跳ね返ります。現在、これらはまったく処理されていません。

  • スクロール中にピンチジェスチャーを放すと、停止します。
  • ピンチジェスチャーでコンテンツを超えてスクロールしてから離しても、コンテンツは元の場所にとどまり、跳ね返ることはありません。その後、もう一度スクロールを開始すると、コンテンツがジャンプして戻ります。

試したが仕事に行けなかったもの

UICollectionViewUIScrollViewであることは、ズームをサポートするように正しく設定されている場合、組み込みのUIPinchGestureRecogniserが必要です。自分のUIPinchGestureRecogniserの代わりにこれを利用できるかどうか疑問に思いました。最小と最大のスケールを設定し、コントローラーのピンチハンドラーを追加して、これを設定しようとしました。ただし、viewForZoomingInScrollView:の実装から何を返す必要があるのか​​本当に理解できないので、[[UIView alloc] initWithFrame: [[self collectionView] bounds]]でダミーのビューを作成しています。それはスクロールビューを1行に「折りたたむ」ようにしますが、それは私が求めているものではありません!

最後に(コードの前)

これは長い質問ですので、読んでいただきありがとうございます。回答にご協力いただける場合は、さらによろしくお願いいたします。言ったことや追加したことの多くが無関係であるとすいません。

ビューコントローラーのコード

//  STViewController.m
#import "STViewController.h"
#import "STDataColumnsCollectionViewLayout.h"
#import "STCollectionViewLabelCell.h"

@interface STViewController () <UIGestureRecognizerDelegate>
@property (nonatomic, assign) CGFloat pinchStartVerticalPeriod;
@property (nonatomic, assign) CGFloat pinchNormalisedVerticalPosition;
@property (nonatomic, assign) NSInteger pinchTouchCount;
-(void) handlePinch: (UIPinchGestureRecognizer *) pinchRecogniser;
@end

@implementation STViewController

-(void) viewDidLoad
{
  [[self collectionView] registerClass: [STCollectionViewLabelCell class] forCellWithReuseIdentifier: [STCollectionViewLabelCell className]];

  UICollectionView *const collectionView = [self collectionView];
  [collectionView setAllowsSelection: NO];

  [_pinchRecogniser addTarget: self action: @selector(handlePinch:)];
  [_pinchRecogniser setDelegate: self];
  [_pinchRecogniser setCancelsTouchesInView:YES];
  [[self view] addGestureRecognizer: _pinchRecogniser];
}

#pragma mark -

-(NSInteger) collectionView: (UICollectionView *)collectionView numberOfItemsInSection: (NSInteger)section
{
  return 800;
}

-(UICollectionViewCell*) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
  STCollectionViewLabelCell *const cell = [[self collectionView] dequeueReusableCellWithReuseIdentifier: [STCollectionViewLabelCell className] forIndexPath: indexPath];
  [[cell label] setText: [NSString stringWithFormat: @"%d", [indexPath row]]];
  return cell;
}

#pragma mark -

-(BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
  return YES;
}

#pragma mark -

-(void) handlePinch: (UIPinchGestureRecognizer *) pinchRecogniser
{
  UICollectionView *const collectionView = [self collectionView];
  STDataColumnsCollectionViewLayout *const layout = (STDataColumnsCollectionViewLayout *)[self collectionViewLayout];

  if(([pinchRecogniser state] == UIGestureRecognizerStateBegan) || ([pinchRecogniser numberOfTouches] != _pinchTouchCount))
  {
    const CGFloat normalisedY = [pinchRecogniser locationInView: collectionView].y / [layout collectionViewContentSize].height;
    _pinchNormalisedVerticalPosition = normalisedY;
    _pinchTouchCount = [pinchRecogniser numberOfTouches];
  }

  switch ([pinchRecogniser state])
  {
    case UIGestureRecognizerStateBegan:
    {
      NSLog(@"Began");
      _pinchStartVerticalPeriod = [layout verticalPeriod];
      [collectionView setScrollEnabled: NO];
      break;
    }

    case UIGestureRecognizerStateChanged:
    {
      NSLog(@"Changed");
      STDataColumnsCollectionViewLayout *const layout = (STDataColumnsCollectionViewLayout *)[self collectionViewLayout];
      const CGFloat newVerticalPeriod = _pinchStartVerticalPeriod * [pinchRecogniser scale];
      [layout setVerticalPeriod: newVerticalPeriod];
      [[self collectionViewLayout] invalidateLayout];

      const CGPoint dragCenter = [pinchRecogniser locationInView: [collectionView superview]];
      const CGFloat currentY = _pinchNormalisedVerticalPosition * [layout collectionViewContentSize].height;
      [collectionView setContentOffset: CGPointMake(0, currentY - dragCenter.y) animated: NO];
    }

    case UIGestureRecognizerStateEnded:
    case UIGestureRecognizerStateCancelled:
    {
      [collectionView setScrollEnabled: YES];
    }

    default:
      break;
  }
}

@end
31
Benjohn

良い点–それを機能させる方法

上記のコードに対するいくつかの非常にマイナーな調整により、機能しないもの1機能しないものが解決されました2 質問の中で。

viewDidLoadUICollectionViewControllerメソッドに次の行を追加しました。

[collectionView setMinimumZoomScale: 0.25];
[collectionView setMaximumZoomScale: 4];

また、サンプルプロジェクトを更新して、テキストラベルの代わりにビューが小さな円で構成されるようにしました。ズームインおよびズームアウトすると、これらのサイズが変更されます。以下は、現在の状態です(ズームアウトとズームイン)。

Image zoomed outImage zoomed in

ズーム中、円のビューは再描画されず、ズーム前のサイズから補間されます。再描画は、ズームが終了するまで延期されます。これは、数回のズームイン後の様子のキャプチャです。

During zoom

ズーム中に再描画をバックグラウンドスレッドで実行して、アーティファクトが目立たなくなるようにするのは素晴らしいことですが、それはこの質問の範囲外であり、まだ取り組んでいません。

Bit Bucketでプロジェクト全体を修正して見つけることができるので、 ファイルを取得 できます。

悪い部分-なぜ機能するのかわかりません

この質問に答えることで、UIScrollViewズームについて多くの新しい確信が持てることを望んでいました。私はしません。

私がUIScrollViewについて読んだことから、この「修正」は何の違いももたらさないはずであり、いずれにしても最初からすでに機能しているはずです。

UIScrollViewは、viewForZoomingInScrollView:を実装するデリゲートを指定するまで、スクロールを有効にすることはできません。

16
Benjohn