2010年9月12日日曜日

簡易スライドビューア [4] 画面回転のサポート

[前回] Cocoaの日々: 簡易スライドビューア [3] ダブルタップで拡大

今回は画面回転をサポートしてみる。


回転処理後の位置と大きさ


お約束として(?)まずは shouldAutorotateInterfaceOrientation: をオーバライドして YESを返してみる。

-(BOOL)shouldAutorotateInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}
するとこうなる。
これは画像が載っている UIScrollViewの位置と大きさが縦向き(Landscape)のままだから。

使っている UIScrollView を描いてみるとわかりやすい。
まずこれが最初の状態。オレンジの枠がベースとなる UIScrollView。青い点線の領域が実際に表示される部分になる。
その状態で横へ傾けるとこうなる。
オートローテーションでやってくれるのはベースとなる UIScrollViewの表示領域(青点線=frame)のみ。その中にある画像表示用の UIScrollViewや、contentSize(オレンジ枠線)は変更されない。これらオートローテーションの対象外となる部分は自前で調整してやる必要がある。こんな感じ。


リファクタリング


回転処理を入れるにあたって前回までのコードを少し変えた。具体的には3つの画像用 UIScrollViewを個別のメンバ変数で保持していたのをやめて配列で管理するようにした。
UIScrollView* previouScrollView_;
UIScrollView* currentScrollView_;
UIScrollView* nextScrollView_;

  ↓

NSMutableArray* imageScrollViews_;
理由は全ての UIScrollView に処理を施す場合、配列の方が扱いやすいから。また回転処理を入れる時にビューの左から並び順を考慮する必要がある為、配列には左のビューから順番に入っている状態とした。


画面回転処理


画面回転に対応するには UIViewController のいくつかのメソッドをオーバライドさせる。

shouldAutorotateToInterfaceOrientation:

どの向きに対応するかどうかを決めるメソッド。
(例)標準の縦と左横のみ対応する場合
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
 if (interfaceOrientation == UIInterfaceOrientationPortrait ||
            interfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
            return YES;
        }
        return NO;
}
今回はすべての向きに対応させてみた。すなわち無条件に YES を返す。
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
 return YES;
}

次に最初に説明したように各ビューの位置と大きさを調整擦る必要がある。先程の図に位置と寸法を入れてみたのがこれ。
縦(Portrait)

横(Landscape)。

回転時に再計算するものは次の通り。
(1) 画像表示用 UIScrollView の位置と大きさ(図中、黄色い箇所)
配列 self.imageScrollViews に格納されている UIScrollView の frame を変更する。またcontentSizeも変える。

(2) ベースとなる UIScrollView の contentSize および contentOffset
内包する imageScrollView の大きさが変わるのでそれに合わせて contentSize を変更する。また表示位置 contentOffset も一緒に変えておく。

これらを行うメソッドを1つ用意した。
- (void)layoutScrollViews
{
 CGSize newSize = self.view.bounds.size;
 
 CGFloat x = (self.contentOffsetIndex-1) * newSize.width;
 for (UIScrollView* scrollView in self.imageScrollViews) {
  scrollView.frame = CGRectMake(x, 0, newSize.width, newSize.height);
  scrollView.contentSize = newSize;
  x += newSize.width;
 }
 
 self.scrollView.contentSize = CGSizeMake(
            [self.imageFiles count]*newSize.width, newSize.height);
 self.scrollView.contentOffset = CGPointMake(self.contentOffsetIndex*newSize.width, 0);
}
このメソッドは縦・横どちらでもうまく動作する。

回転中(*1)は回転対称の UIView の frame と bounds は連携しない。前者は回転前の位置と大きさを表し、後者は回転後の大きさを表す。layoutScrollViews は回転中の処理で呼び出すので、計算に利用する回転後の大きさは bounds から取得している。
(*1) willRotateToInterfaceOrientation:duration: 利用時

なお x が負の値(-1)から始まるのは、初期状態では左端の imageScrollViewはベースとなる UIScrollView の左にはみ出した状態になっているため。こんな感じ。


willRotateToInterfaceOrientation:duration

さて、layoutScrollView をいつ呼び出すべきか?

UIViewController を使った画面回転には2種類存在する。"one-step rotation" と "two-step rotation"。

詳しくはリファレンスを参照(英語だが図があってわかりやすい)。
View Controller Programming Guide for iOS: Custom View Controllers

今回は扱いが簡単な "one-step rotation" を使う。この場合、willRotateToInterfaceOrientation:duration: をオーバライドする。ここで layoutScrollViews を呼び出す。
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 
 duration:(NSTimeInterval)duration
{
 [self layoutScrollViews];
}
これで終わり。

最後に imageScrollView に載せている UIImageView の autoresizingMask を設定しておく。
imageView.autoresizingMask =
   UIViewAutoresizingFlexibleLeftMargin  |
   UIViewAutoresizingFlexibleWidth       |
   UIViewAutoresizingFlexibleRightMargin |
   UIViewAutoresizingFlexibleTopMargin   |
   UIViewAutoresizingFlexibleHeight      |
   UIViewAutoresizingFlexibleBottomMargin;
こうすると親ビューの imageScrollViewの大きさが変わった場合でもレイアウトが崩れない。


サンプル実行


さて動かしてみよう。初期状態。

横に倒す。問題なし。
全方向回転させてもちゃんと表示される。何回・どの方向へ回してもちゃんと追随する。


ソースコード

GitHubからどうぞ。
EasyGallery at 2010-09-12 from xcatsan's iOS-Sample-Code - GitHub


- - - -
おくばせながら回転をやってみた。面白いなこれ。

16 件のコメント:

  1. ありがとうございます!大変役に立ちました!:)
    一つだけ聞かせていただきたいことがありますが、
    画面を横向きにした場合イメージが2枚見えるようにしたいですが、
    どこをさわればいいでしょうか。

    返信削除
  2. 이명근さん
    こんにちは橋口です。

    画面を横向きにした場合イメージが2枚見えるようにする方法ですが、横向きになったことを検出し、その上でイメージが2枚並ぶような設定を行う流れになると思います。
    サンプルの場合 willRotateToInterfaceOrientation:duration のイベントを検出して位置と大きさの調整を行っているので、ここで横向きの場合のみ2枚並ぶような調整を行います。横向きの場合に画像幅を表示領域いっぱいに合わせるのではなく表示幅の1/2とすると画像が2枚表示されると思います。なおこの場合位置調整の計算内容は縦向きの1枚表示と異なるので、向きによって計算を分ける必要があります。またそのままだとスクロールは画像2枚単位になるかもしれません。

    返信削除
  3. xcatsanさんお世話になります。

    コメントありがとうございます!
    もう一歩勉強になりました。
    しかし、あくまでも初心者なので、そういう計算というのが難しいのです。

    for (int i=0; i < 22; i++) {
    [array addObject:[NSString stringWithFormat:@"%@/py%02d.jpg", path, i+1]];
    NSLog(@"%@", [NSString stringWithFormat:@"%@/py%02d.jpg", path, i+1]);
    }

    イメージを読み込んでいるここを縦向きの場合/横向きの場合にそれぞれ別のイメージを読み込む方法はどうでしょうか。
    ここをifなどを使って修正したらできるかなと思うんですが、

    質問ばっかりで申し訳ございませんが、別々イメージを読み込む方法があればやり方を教えていただきたいと思います。
    よろしくお願いします。

    返信削除
  4. 이명근 さん、こんにちは。
    返事が遅くなりました。

    > イメージを読み込んでいるここを縦向きの場合/横向きの場合にそれぞれ別
    > のイメージを読み込む方法はどうでしょうか。
    > ここをifなどを使って修正したらできるかなと思うんですが、

    なるほど。ただ表示の仕組み(UIScrollViewまわり)が1枚の画像だけしか
    表示できないようになっているので、その方法を取るとしたら横向き用に
    2枚の画像を並べて1枚に合成する必要があります。この方法は合成が必要
    なので手間がかかると思います。またスクロールの単位が画像2枚単位になります(これはこれで許容するという考えもあります)。

    では。

    返信削除
  5. お世話になります。takataka と申します。

    質問させて下さい!。
    初心者の質問で申し訳ないのですが、
    image01.jpgはどこで管理、表示させているんでしょうか?
    ソースを拝見させてもらいましたが、
    よくわかりませんでした。すみません。

    今、table view を使用しています。
    画面移行後、イメージを出したいのですが。

    宜しくお願い致します。

    返信削除
  6. takataka さん、こんばんは。

    image01.jpg は EasyGalleryAppDelegate.m 内で配列に入れています。

    https://github.com/xcatsan/iOS-Sample-Code/blob/2010-09-12/EasyGallery/Classes/EasyGalleryAppDelegate.m

    上記コードの24〜30行目がその処理です。

    アプリ起動時に8枚の画像を配列へ入れて EasyGalleryViewController の imageFiles プロパティに渡しています。
    EasyGalleryViewController はこの配列情報を元に画像を表示しています。

    では。

    返信削除
  7. xcatsanさん 

    ご返答ありがとうございます。

    もう一つ質問させて下さい。


    このEasyGalleryAppDelegate.mの中にある24〜30行目を
    EasyGalleryViewController.m の中に書き、動かすことはできませんか?

    試してみたのですが、画面が真っ暗で、image が呼び出されていない
    ようでした。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions //以下省略

    ここにはすでに、違うソースが存在するのと、
    画面移行を繰り返した後にxcatsanさんのを参考にさせて頂きたいと
    考えております。

    初心者すぎて申し訳ないのですが、
    宜しくお願い致します。

    返信削除
  8. takataka さん、こんにちは。
    なるほど。EasyGalleryViewController.m へ移すとしたら viewDidLoad: 内の先頭が良いと思います。こんな感じ。

    - (void)viewDidLoad {
    [super viewDidLoad];

    // 以下追加 -->
    NSString* path = [[NSBundle mainBundle] resourcePath];
    NSMutableArray* array = [NSMutableArray array];
    for (int i=0; i < 8; i++) {
    [array addObject:[NSString stringWithFormat:@"%@/image%02d.jpg", path, i+1]];
    NSLog(@"%@", [NSString stringWithFormat:@"%@/image%02d.jpg", path, i+1]);
    }
    self.imageFiles = array;
    // <--

    // setup internal scroll views
    CGRect imageScrollViewFrame = CGRectZero;
    :
    :

    ※動作確認はしてません。

    では。

    返信削除
  9. xcatsanさん

    ご返答ありがとうございます。
    ありがとうございます、動きました。
    ホント感動しました。

    これからも勉強させて頂きます。

    ご丁寧にありがとうございました。

    返信削除
  10. xcatsanさん

    お世話になります。先日教えて頂いたtakatakaです、
    先日はありがとうございました。

    前回教えて頂いた、- (void)viewDidLoad {
    [super viewDidLoad];以下省略
    ですが、動作はOKなのですが、横に傾けてもviewが変化しません。

    何かソースをviewDidLoad内に入れなければいけないのでしょうか?
    原因がわかりません。
    教えて頂けないでしょうか?

    EasyGalleryViewController.m内に(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return YES;
    } は入れてあります。



    よろしくお願いします。

    返信削除
  11. takataka さん、こんにちは。
    返事が遅くなりました。

    回転しない件、なんでしょうね。原因がわかりませんが基本的には
    shouldAutorotateToInterfaceOrientation: で YES を返せば自動的に回転してくれるようになっています。

    過去の経験でいうと UITabController を使っていて、その中にある UIViewContoller が一つでも上記設定がない場合には回転しなかったことがありました。この場合、タブ内の全ての UIViewController を回転設定したところ回転する様になりました。参考まで。

    では。

    返信削除
  12. はじめまして。
    このブログは大変勉強になります!
    ところで、EasyGalleryのソースコードを実行すると私の環境(Xcoden 3.2.6)では少々おかしな動作をする箇所がありました。

    すなわち、Portraitモードで写真をimage01.jpgから順番に右スクロールして行き、image07.jpgを表示した状態でLandscapeにすると、image08.jpgの写真が表示されます。
    同様に、Landscapeモードでimage08.jpgを表示した状態でPortraitモードにすると写真がimage07.jpgになります。

    私の知識では直せませんでした。お時間のある時に教えてください。

    返信削除
  13. こんにちは Ken さん。問題の指摘ありがとうございました。

    これは以前別の方からも指摘を受けた問題と多分同じで、まだ直せていません。確か UIScrollView の処理の仕方に問題があって回転時に一つずれてしまう現象が出ていたのだと思います。時間がとれたら直したいと思います。。
    では。

    返信削除
  14. 今回、PDFビューワーを作る事になり、参考資料を探していて辿り着きました。
    かなり勉強になりました。有難うございます。

    上記Kenさんのご質問の問題点ですが、私もそこで躓いています。

    この問題は解決されたのでしょうか?

    どうぞ宜しくお願い致します。

    返信削除
  15. 北島さん、こんにちは。
    Portrait/Landscape切替時に画像が変わってしまう件、直ったと思います。
    が、昔のことなので覚えていません。。。
    ちょっとした修正で直った記憶はあるのですがそれが何だったか。
    昔の記録などを調べてみてもしわかったら回答しますね。

    返信削除
  16. こんにちは。
    ご返信有難うございます。

    もし分かりましたら教えて頂ければ幸いです。

    どうぞ宜しくお願い致します。

    返信削除