mixi engineer blog

*** 引っ越しました。最新の情報はこちら → https://medium.com/mixi-developers *** ミクシィ・グループで、実際に開発に携わっているエンジニア達が執筆している公式ブログです。様々なサービスの開発や運用を行っていく際に得た技術情報から採用情報まで、有益な情報を幅広く取り扱っています。

mixi for iPad Ver 2.0 リリース記念 iPad 風フォトビューアの作り方

初めまして。11 新卒の田村と申します。
好きな食べ物は卵でございます。
現在は iOS 公式クライアントアプリ開発を行っており、主に mixi for iPad の開発を行っております。

そんなおり、12/14 に mixi for iPad Ver. 2.0 がリリースされましたー!88888
今回のアップデートの目玉機能は

  • コメント、メッセージのプッシュ通知機能

  • mixi フォト用フォトビューア機能

  • でございます。私はこのフォトビューアの開発を行っておりましたので簡単に宣伝させていただきます。

    今回の mixi フォト用フォトビューアは、mixi にアップロードされている写真を iPad の大きな画面で閲覧することができ、更にそこからその写真に対してイイネ、コメントを簡単に行うことができます。iPad ならではのシームレスな操作感を実現しておりますので iPad をお持ちの方は是非お試しください。ダウンロードはこちら

    さて、このフォトビューアの操作の中で個人的に非常に気に入っているものがあります。そうです、ビューア下部のサムネイルリストをタップして写真を流す操作です。これは iPad の写真アプリのビューアにも搭載されている機能です。

    写真をすらすらと流して閲覧できる操作感がなんとも心地いい。。。

    ということで今回は簡易版 iPad 風フォトビューアの実装について簡単に紹介させていただきます。今回は簡易版ということで Landscape モードのみ対応の実装方法となります。

    必要なコンポーネント

    PhotoViewerSample に必要なコンポーネントは、以下の二つになります。

    MainViewController では写真の描画やフリックでのスライド機能をもたせます。
    ThumbnailListViewController ではビューア下部のサムネイルリストの描画とタッチイベントを取得します。

    ではまず MainViewController の実装から説明します。

    MainViewController.h

    #import <UIKit/UIKit.h>
    #import "ThumbnailListViewController.h"
    
    @interface MainViewController : UIViewController<ThumbnailListViewControllerDelegate>
    {
        UIScrollView * scrollView_;
        NSArray * imageNameArray_;
        
        ThumbnailListViewController * thumbnailListViewController_;
    }
    @property (nonatomic, retain) UIScrollView * scrollView_;
    @property (nonatomic, retain) NSArray * imageNameArray_;
    
    @property (nonatomic, retain) ThumbnailListViewController * thumbnailListViewController_;
    @end
    

    このクラスには、写真をスライドさせるためのUIScrollView、画像のファイル名を持たせるためのNSArray、そして、ThumbnailListViewController のオブジェクトをプロパティとして持たせます。

    MainViewController.m

    #import "MainViewController.h"
    
    @implementation MainViewController
    
    @synthesize scrollView_;
    @synthesize imageNameArray_;
    
    @synthesize thumbnailListViewController_;
    
    #pragma mark - ThumbnailListViewController delegate
    -(void)thumbnailListViewControllerDidTouche:(ThumbnailListViewController *)thumbnailListViewController Index:(int)index
    {
        //5) 指定の位置までスクロールさせる
        CGRect rect = CGRectMake(scrollView_.frame.size.width * index, 
                                 0, 
                                 scrollView_.frame.size.width, 
                                 scrollView_.frame.size.height);
        
        [scrollView_ scrollRectToVisible:rect animated:NO];
    }
    
    #pragma mark - View lifecycle
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        //1) 画像配列作成
        self.imageNameArray_ = [NSArray arrayWithObjects:
                           @"image1.png", 
                           @"image2.png", 
                           @"image3.png", 
                           @"image4.png", 
                           nil];
        
        //2) スクロールビューの設定
        self.scrollView_ = [[[UIScrollView alloc] init]autorelease];
        [self.scrollView_ setFrame:CGRectMake(0, 
                                         0, 
                                         self.view.frame.size.width, 
                                         self.view.frame.size.height)
         ];
        [self.scrollView_ setContentSize:CGSizeMake(self.view.frame.size.width * [self.imageNameArray_ count], 
                                               self.view.frame.size.height)
         ];
        self.scrollView_.pagingEnabled = YES;
        [self.view addSubview:self.scrollView_];
        
        //3) スクロールビューに画像を描画する
        for(int i = 0; i < [self.imageNameArray_ count]; i ++){
            UIImage * image = [UIImage imageNamed:[self.imageNameArray_ objectAtIndex:i]];
            UIImageView * imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
            [self.scrollView_ addSubview:imageView];
            [imageView setFrame:CGRectMake(self.view.frame.size.width * i, 
                                           0, 
                                           self.view.frame.size.width, 
                                           self.view.frame.size.height)
             ];
        }
        
        //4) サムネイルリスト描画
        self.thumbnailListViewController_ = [[[ThumbnailListViewController alloc]initWithImageNameArray:self.imageNameArray_]autorelease];
        self.thumbnailListViewController_.delegate_ = self;
        [self.view addSubview:self.thumbnailListViewController_.view];
    }
    
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    {
        return YES;
    }
    
    @end
    
    MainViewController の実装部分です。まず ViewDidLoad でレイアウトの実装を行います。 1) では、あらかじめ読み込む画像のファイル名をNSArrayに格納しておきます。

    2) でスクロールビューの設定を行います。スクロールビューのサイズは画面のサイズを設定、contentSizeには(写真の枚数 × 画面サイズ)を指定します。また pagingEnabled プロパティを YES に設定することで、フリックするたびにスクロールを止めることが可能になります。

    3) では 2) で設定したスクロールビューに画像をのせていきます。2), 3) で以下のような描画を行っております。

    4) の部分で、ThumbnailListViewController のインスタンスを生成してMainViewController の上にのせています。ThumbnailListViewController でも画像を使用するので imageNameArray_ を用いて初期化しています。

    次に thumbnailListViewControllerDidTouche:Index: メソッドの説明です。
    5) の部分では、ThumbnailListViewController のデリゲートメソッドを実装します。ここでは、どのサムネイルをタッチしたのかという情報を受け取って、スクロールビューを対応する写真の位置までスクロールするように実装します。

    ThumbnailListViewController.h

    #import <UIKit/UIKit.h>
    
    #define kThumbnailListViewControllerViewPosy    704
    #define kThumbnailListViewControllerViewWidth   1024
    #define kThumbnailListViewControllerViewHeight  44
    
    #define kThumbnailImageViewWidth    40
    #define kThumbnailImageViewHeight   30
    
    @interface ThumbnailListViewController : UIViewController
    {
        NSArray * imageNameArray_;
        UIView * thumbnailListView_;
        
        id delegate_;
    }
    @property (nonatomic, retain) NSArray * imageNameArray_;
    @property (nonatomic, retain) UIView * thumbnailListView_;
    
    @property (nonatomic, assign) id delegate_;
    
    - (id)initWithImageNameArray:(NSArray*)imageNameArray;
    
    @end
    
    @protocol ThumbnailListViewControllerDelegate <NSObject>
    
    -(void)thumbnailListViewControllerDidTouche:(ThumbnailListViewController*)thumbnailListViewController Index:(int)index;
    
    @end
    

    このクラスではサムネイルを表示させるためのUIView, イメージ名の NSArray そして、どのサムネイルがタッチされたかを MainViewController に通知するための delegate をプロパティとして用意します。そして、ThumbnailListViewControllerDelegate protocol を用意します。

    ThumbnailListViewController.m

    #import "ThumbnailListViewController.h"
    
    @implementation ThumbnailListViewController
    
    @synthesize imageNameArray_;
    @synthesize thumbnailListView_;
    @synthesize delegate_;
    
    - (id)initWithImageNameArray:(NSArray *)imageNameArray
    {
        self = [super init];
        if (self) {
            self.imageNameArray_ = [NSArray arrayWithArray:imageNameArray];
        }
        return self;
    }
    
    -(void)dealloc
    {
        self.imageNameArray_ = nil;
        self.thumbnailListView_ = nil;
        
        [super dealloc];
    }
    
    #pragma mark - touch event
    -(void)getToucheAreaByCGPoint:(CGPoint)point
    {
        //12) どのサムネイルがタッチされているかを計算
        float touchePosX = point.x - self.thumbnailListView_.frame.origin.x;
        
        int index;
        if(touchePosX < 0){
            index = 0;
        }else if(touchePosX > self.thumbnailListView_.frame.size.width){
            index = [self.imageNameArray_ count] - 1;
        }else{
            index = floor(touchePosX / kThumbnailImageViewWidth);
        }
        if([self.delegate_ respondsToSelector:@selector(thumbnailListViewControllerDidTouche:Index:)]){
            [self.delegate_ thumbnailListViewControllerDidTouche:self Index:index];
        }
    }
    
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {    
        //9) タッチイベント開始を取得
        CGPoint point = [[touches anyObject] locationInView:self.view];
        [self getToucheAreaByCGPoint:point];
    }
    
    -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
        //10) ドラッグイベントを取得
        CGPoint point = [[touches anyObject] locationInView:self.view];
        [self getToucheAreaByCGPoint:point];
    }
    
    -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
        //11) タッチ終了イベントを取得
        CGPoint point = [[touches anyObject] locationInView:self.view];
        [self getToucheAreaByCGPoint:point];
    }
    
    #pragma mark - View lifecycle
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        //6) サイズの設定
        [self.view setFrame:CGRectMake(0, 
                                       kThumbnailListViewControllerViewPosy,
                                       kThumbnailListViewControllerViewWidth, 
                                       kThumbnailListViewControllerViewHeight)];
        self.view.backgroundColor = [UIColor blackColor];
        
        //7) サムネイル画像を描画するサムネイルリストビューを生成、レイアウト
        self.thumbnailListView_ = [[[UIView alloc] init] autorelease];
        self.thumbnailListView_.backgroundColor = [UIColor greenColor];
        [self.thumbnailListView_ setFrame:CGRectMake(0, 
                                                     0, 
                                                     kThumbnailImageViewWidth * [self.imageNameArray_ count], 
                                                     kThumbnailImageViewHeight)];
        
        [self.view addSubview:self.thumbnailListView_];
        [self.thumbnailListView_ setCenter:CGPointMake(self.view.frame.size.width/2, 
                                                       self.view.frame.size.height/2)];
        
        //8) サムネイルリストビューの上に書く画像を描画
        for(int i = 0; i < [self.imageNameArray_ count]; i ++){
            UIImage * image = [UIImage imageNamed:[self.imageNameArray_ objectAtIndex:i]];
            UIImageView * thumbnailImageView = [[[UIImageView alloc] initWithImage:image] autorelease];
            [self.thumbnailListView_ addSubview:thumbnailImageView];
            [thumbnailImageView setFrame:CGRectMake(kThumbnailImageViewWidth * i, 
                                                    0, 
                                                    kThumbnailImageViewWidth, 
                                                    kThumbnailImageViewHeight)];
        }
    }
    
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    {
    	return YES;
    }
    @end
    
    ThumbnailListViewController の実装部分です。ViewDidLoad でレイアウトの実装を行います。 6) では、自分自身のサイズを設定しています。

    7) でサムネイル画像を描画するための thumbnailListView を生成し、自身の view の中央に設置します。

    8) では、7) で生成したビューに画像をのせていきます。

    9), 10), 11) では ThumbnailListViewController 上で起きた各種タッチイベントを取得し、その座標を getToucheAreaByCGPoint: メソッドに渡しています。

    12) ではタッチイベントで受け取った x座標から、どのサムネイルにタッチしているかを計算して、デリゲートメソッドにその写真のインデックスを渡しています。計算方法は下の図のように、thumbnailListView より左ならインデックスは 0、右なら最後のインデックス、 thumbnailListView の中ならサムネイル画像のサイズを用いて何枚目か割り出します。

    実装方法は以上となります。

    まとめ

    今回は簡易版フォトビューアの実装方法について紹介させていただきました。このように、意外とシンプルな実装でフォトビューアを実現できることがお分かりいただけたと思います。 mixi for iPad Ver. 2.0 ではこのビューアを通じて、たくさんの友人とコミュニケーションをとっていただけるようになっておりますので、是非お試しください。

    今後の mixi for iPad のアップデートにもご期待ください!