UITableViewをスクロールしたときに、Cellの画像をずらすサンプルを書いてみた


最近、スクロールすると表示されてる写真がちょっとずれていくUIをよく見かけるようになりました。これ、どうやってるのかなんとなく想像できたので、サンプルコードを書いてみました。

こんな感じに動きます。
![動作画面](https://raw.github.com/taktamur/MoveImageCellSample/master/anime.gif)

(このアニメーションGIFの作成は、[LICEcap](http://www.cockos.com/licecap/)を使いました。)

## サンプルコード
https://github.com/taktamur/MoveImageCellSample


## ポイント
- セル部分は、UITableViewCell > UIView(A) > UIImageView(B) という階層構造にします。
 - UIView(A)は、いわば「覗き窓」として機能させるために、clipsToBoundsをYESとします。
 - UIImageView(B)のclipsToBoundsをNO、contentModeはUIViewContentModeScaleAspectFill として、はみださせます。

![MoveImageCell.xcodeproj_—_PAMCell.xib-15.png](https://qiita-image-store.s3.amazonaws.com/0/7946/513db0c1-efb9-837d-b08c-b0ae8e13efef.png "MoveImageCell.xcodeproj_—_PAMCell.xib-15.png")

- UITableViewのスクロールは、tableViewDelegateのscrollViewDidScroll: で検出します。
 - tableView.contentOffsetをKVO監視する方法でもやっていましたが、Delegateの方がシンプルに実装できました。[指摘ありがとうございます。](https://twitter.com/hoshi_gaki/status/420567707193331712)
- スクロールを検出したら、Cellの中央とtableViewの中央の差を出して、それに応じて写真部分のView(photoView)を移動させます。

## ソース
```objective-c:PAMCell.m
//
//  PAMCell.m
//  MoveImageCell
//

#import "PAMCell.h"

@interface PAMCell()
@property (weak, nonatomic) IBOutlet UIView *clipView;  // (A)
@property (weak, nonatomic) IBOutlet UIImageView *photoView; // (B)
@end


@implementation PAMCell

-(id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if(self){
        self.clipView.clipsToBounds=YES;
        self.photoView.clipsToBounds=NO;
        self.photoView.contentMode=UIViewContentModeScaleAspectFill;
        // ↑nibファイルでもAspectFill を指定してないとだめっぽかった。
    }
    return self;
}

// ここは、Cell生成時と、tableViewのスクロール時によびだされる。
-(void)movePhotoWithTableView:(UITableView *)tableView
{
    // このcellが、tableViewの中央からどれぐらいずれているか計算
    CGFloat gapRatioY = [self gapRatioFromCenterY:tableView];
    
    // clipViewを基準に、photoViewのcenterを移動させる
    CGPoint center = CGPointMake(self.clipView.bounds.size.width/2,
                                 self.clipView.bounds.size.height/2);
    center.y -= gapRatioY * 50;  // ここで割合から、実際にずれるピクセル数に変換。
    self.photoView.center = center;
}

// このCellの中心が、tableViewの中央からどのくらい離れているのかを計算する
// -1.0は、cellの中央がちょうどtableViewの上限に位置する
// 0.0 は、cellの中央がちょうどtableViewの中央に位置する
// 1.0 は、cellの中央がちょうどtableViewの下限に位置する
// はみ出している場合もあるので、-1.0以下や1.0以上になる場合もある。
-(CGFloat)gapRatioFromCenterY:(UITableView *)tableView
{
    // このCellのframeは、tableViewの中での位置になっているので、
    // tableViewのcontentsOffsetから算出する。
    // ここを参考にした http://songofcloud.gluegent.com/2010/04/uitableviewcell.html
    float displayedCenterY = self.center.y - tableView.contentOffset.y;
    float centerDiffY = displayedCenterY - tableView.bounds.size.height/2;
    float gapRatioFromCenterY = centerDiffY*2/tableView.bounds.size.height;
    // centerAspectY は、cellの中央が画面に入っている場合 -1.0 〜1.0の値をとる。
    NSLog( @"displayedCenterY=%f centerDiffY=%f gapRatioFromCenterY=%f",
          displayedCenterY,centerDiffY,gapRatioFromCenterY);
    return gapRatioFromCenterY;
}


@end

```

```objective-c:PAMTableViewController.m
//
//  PAMTableViewController.m
//  MoveImageCell
//

#import "PAMTableViewController.h"
#import "PAMCell.h"

@interface PAMTableViewController ()

@end

@implementation PAMTableViewController

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Cellにはnibを登録
    UINib *cellNib =[UINib nibWithNibName:NSStringFromClass([PAMCell class]) bundle:nil];
    [self.tableView registerNib: cellNib forCellReuseIdentifier:@"Cell"];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return 20;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    PAMCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    
    // cellの写真位置を調整しておく。
    [cell movePhotoWithTableView:tableView];
    
    return cell;
}

#pragma mark - UITableViewDelegate
// tableViewがスクロールした際に呼び出される
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    // 表示されているCellに、写真の位置をずらす指示を出す
    NSArray *cells = [self.tableView visibleCells];
    for (PAMCell *cell in cells) {
        [cell movePhotoWithTableView:self.tableView];
    }

}
@end

```