Swap~ビューの切り替え

2010. 08. 25
オートローテーションの最後は、ポートレートとランドスケープでのビューの切り替えです。

Interface Builderでポートレート用とランドスケープ用のビューを作り、デバイスの回転に応じてビューを切り替えます。


●プロジェクトの作成

View-based Applicationテンプレートで、プロジェクト名を『Swap』とします。

724

本章の後半でCoreGraphics.frameworkの追加に触れられていますが、現在はView-based Applicationテンプレートでプロジェクトを作成すると自動で組み込まれています。

前回の『Autosize(2)~コードによる変更』でも特に意識せずにCGRectMakeを使っていましたので、紹介するタイミングとしては少し不可解な感じもしますが、気にせずに進みます。

フレームワークの追加については、機会があればその時に復習します。


●ビューとボタンの設置

本書の内容とは異なりますが、ボタン用の画像を趣味で追加します。

前回でも使った125x125ピクセルの画像を2枚、Swapプロジェクトのフォルダに入れ、XcodeのResourcesに追加します。

725

まずSwapViewController.xibを開き、デフォルトで組み込まれているViewを削除します。

本書での説明にもあるように、DocumentウィンドウでViewを選択してInspectorウィンドウのSizeタブを見ると、サイズおよび位置の欄がグレイアウトされて変更不可なので、新たにビューを作り直す必要がある訳です。

DocumentウィンドウでViewを選択し、Deleteキーを押すと削除できます。

LibraryウィンドウからViewを2つドラッグ&ドロップで追加し、識別し易くするためにNameをPortraitとLandscapeに変更します。

727

PortraitおよびLandscapeをダブルクリックすると、独立したViewウィンドウとして開けます。

ビューのサイズはステータスバーを除いた、Portraitが320x460、Landscapeが480x300ピクセルになります。

それぞれにRound Rect Buttonを2つずつLibraryウィンドウからViewウィンドウへドラッグ&ドロップします。

位置とサイズは下表のようになります。

  XYWH
PortraitAmi (Foo)9957125125
Mami (Bar)99276125125
LandscapeAmi (Foo)6188125125
Mami (Bar)29788125125

本書では画像を貼り付けずに、InspectorウィンドウのAttributesタブで『Button』の『Title』をFooとBarに設定しています。
(『foo』や『Bar』は『メタ構文変数』と呼ばれるもので意味の無い名前に使われるものです)

ここでは便宜上『Foo』を『Ami』、『Bar』を『Mami』と読み替えます。
(画像を貼り付けるので、Titleは設定しません)

InspectorウィンドウのAttributesタブで、『Button』の『Image』で画像を選択します。

729

730

アウトレットを接続する際の識別を容易にするために、DocumentウィンドウのRound Rect Buttonの名前に『(Ami)』『(Mami)』と識別子を追加しておきます。
(Titleを設定している場合は自動的に反映されるので、手動で入力する必要はありません)

728


●アウトレットとプロパティの宣言

XcodeでSwapViewController.hを開き、2つのビューと各ビュー2つずつのボタンのアウトレットとプロパティ、そしてボタンをタップすると不可視にするアクションメソッドの宣言を行います。
(太字が追加した部分)

#import <UIKit/UIKit.h>

#define degreesToRadian(x) (M_PI * (x) / 180.0)

@interface SwapViewController : UIViewController {
    IBOutlet UIView *landscape;
    IBOutlet UIView *portrait;
    // Ami
    IBOutlet UIButton *landscapeAmiButton;
    IBOutlet UIButton *portraitAmiButton;
    // Mami
    IBOutlet UIButton *landscapeMamiButton;
    IBOutlet UIButton *portraitMamiButton;
}
@property (nonatomic, retain) UIView *landscape;
@property (nonatomic, retain) UIView *portrait;
@property (nonatomic, retain) UIButton *landscapeAmiButton;
@property (nonatomic, retain) UIButton *portraitAmiButton;
@property (nonatomic, retain) UIButton *landscapeMamiButton;
@property (nonatomic, retain) UIButton *portraitMamiButton;

- (IBAction)buttonPressed:(id)sender;

@end

731

最初に#defineで定数degreesToRadian(x)を設定しています。

これはビューの回転でアフィン変換を使う際に度(degree)をラジアンに変換するためのものです。

アウトレットは各ビューのボタンだけでなく、回転させるビュー自体もアウトレットとしています。

ボタンの識別子は先に述べたように、『Foo』を『Ami』、『Bar』を『Mami』としています。


●アウトレットの接続

Interface Builderに戻り、DocumentウィンドウでFile's Ownerを選択し、InspectorウィンドウでConnectionタブを開きます。

2つのビューと各ビュー2つずつのボタン、そして起動時に開くビューとして『Outlets』の『View』を『Portrait』ビューに、buttonPressed:アクションメソッドは4つのボタンに『Touch Up Inside』で接続します。

732


●プロパティとアクションメソッドの実装

Xcodeに戻ってSwapViewController.mを開き、ヘッダファイルで定義したアウトレットとアクションメソッドの実装を行います。
(太字が追加・修正した部分)

#import "SwapViewController.h"

@implementation SwapViewController

@synthesize landscape;
@synthesize portrait;
@synthesize landscapeAmiButton;
@synthesize portraitAmiButton;
@synthesize landscapeMamiButton;
@synthesize portraitMamiButton;

- (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    if (toInterfaceOrientation == UIInterfaceOrientationPortrait) {
        self.view = self.portrait;
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(0));
        self.view.bounds = CGRectMake(0.0, 0.0, 300.0, 480.0);
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
        self.view = self.landscape;
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(-90));
        self.view.bounds = CGRectMake(0.0, 0.0, 460.0, 320.0);
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
        self.view = self.portrait;
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(180));
        self.view.bounds = CGRectMake(0.0, 0.0, 300.0, 480.0);
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
        self.view = self.landscape;
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
        self.view.bounds = CGRectMake(0.0, 0.0, 460.0, 320.0);
    }
}

- (IBAction)buttonPressed:(id)sender {
    if (sender == portraitAmiButton || sender == landscapeAmiButton) {
        portraitAmiButton.hidden = YES;
        landscapeAmiButton.hidden = YES;
    }
    else {
        portraitMamiButton.hidden = YES;
        landscapeMamiButton.hidden = YES;
    }
}

// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return YES;
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.landscape = nil;
    self.portrait = nil;
    self.landscapeAmiButton = nil;
    self.portraitAmiButton = nil;
    self.landscapeMamiButton = nil;
    self.portraitMamiButton = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [landscape release];
    [portrait release];
    [landscapeAmiButton release];
    [portraitAmiButton release];
    [landscapeMamiButton release];
    [portraitMamiButton release];
    [super dealloc];
}

@end

733

プロパティの実装とviewDidUnloadでのアウトレットの所有権放棄、deallocでの解放はいつもの通りです。

今回は4方向に対応するため、shouldAutorotateToInterfaceOrientation:メソッドはYESを返すのみに修正しています。

willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:は回転の前半開始前に呼び出されるメソッドで、ここでは4つの各向きごとに条件分岐しています。

デバイスの向きの判定後、portraitまたはlandscapeプロパティで、表示するビューを指定します。

続いてビューをCGAffineTransformIdentity(無変換のアフィン変換)で一度初期化し、CGAffineTransformMakeRotationで座標の回転を行っています。

CGAffineTransformMakeRotationの引数はラジアンで指定しますが、ヘッダファイルで定義したdegreesToRadian(x)を用いることで、度で指定できるようになっています。

最後にCGRectMakeで原点とサイズを指定した矩形区域の設定をしているのですが、このサイズが不可解です。

サンプルコードを見ても、Interface Builderで作成したビューのサイズはステータスバーを除いたportrait:320x460、landscape:480x300ピクセルなのですが、コードではportrait:300x480、landscape:460x320ピクセルとなっています。

旧版の本書内だけなら記述ミスかとも思うのですが、新版用のサンプルコードも同じですので記述ミスとは考え難くく、とはいえportraitでlandscape用のアスペクトの矩形区域を指定(またはその逆)をする意味がよく分かりません。

新版の本書内で説明があるのかもしれませんが、旧版の本書内ではこの辺の数値に関して言及していませんので、理由は定かではありません。

今回の場合は表示に影響が無い領域なのでさほど気になりませんが、数値を色々試すとアニメーション時に一瞬挙動が異なるようなので、機会があれば詰めたいと思います。

buttonPressed:メソッドはボタンがタップされた際に呼び出されるメソッドで、ポートレートおよびランドスケープどちらかのAmi(Foo)ボタンがタップされたら、両ビューのAmiボタンを非表示に、Amiボタンでなければ両ビューのMami(Bar)ボタンを非表示にします。

座標系の操作に関しては『フルスクリーン化で使用しているクラス(1)』の『UIViewクラス』の概要を参照してください。

CGAffineTransformIdentity、transformプロパティ、CGAffineTransformMakeRotationについては『スライドショーの簡易アニメーション(3)』を参照してください。

またframeとboundsの違いなど、『iPhoneプログラミングUIKit詳解リファレンス』が参考になります。


・willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:
(UIViewControllerクラス)

- (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

ユーザインターフェイスが回転する際の前半の前にビューコントローラに送信します。

このメソッドはデフォルトの実装では何もしません。

アニメーションの構成を一工程で済ませたい場合は、代わりにwillAnimateRotationToInterfaceOrientation:duration:メソッドをオーバーライドして使用してください。

アニメーションブロック内からこのメソッドが呼び出された場合、ヘッダとフッタがビューの元の位置に戻り、ビューの回転とスライドを完了するために使用されます。

このメソッドをオーバーライドすることによって、ビューの回転の前半の期間にアニメーションの構成を追加することができます。

例えば、コンテンツのズームレベルの調整やスクロール位置の変更、またはビューの他のアニメーションプロパティの修正に使用することができます。

このメソッドが呼び出される時、interfaceOrientationプロパティは新しい向きに設定されます。

toInterfaceOrientation:回転する前のアプリケーションのユーザインターフェイスの向きの状態を指定します。
有効な値はUIInterfaceOrientationで説明します。
(UIInterfaceOrientationについては『Button Fun』を参照してください)

duration:回転を保留する前半の継続時間を秒単位で指定します。


・CGRectMake
(CGGeometry)

CGRect CGRectMake (
    CGFloat x,
    CGFloat y,
    CGFloat width,
    CGFloat height
);

指定した座標とサイズの値の矩形を返します。

x:矩形の原点のx座標を指定します。

y:矩形の原点のy座標を指定します。

width:矩形の幅を指定します。

height:矩形の高さを指定します。


・bounds
(UIViewクラス)

@property(nonatomic) CGRect bounds

レシーバの矩形区域で、独自の座標系の位置とサイズを表します。

ビューの座標系内にあるフレーム矩形における、矩形区域の原点とスケールを決定します。

このプロパティの設定すると、それに応じてフレームプロパティの値も変更されます。

フレーム矩形を変更すると、drawRect:メソッドを呼び出すこと無く、レシーバを自動的に再表示します。

フレーム矩形を変更した時にdrawRect:メソッドを呼び出したい場合は、UIViewContentModeRedrawにcontentModeプロパティをを設定してください。

このプロパティの変更はアニメーションすることができます。

アニメーションブロックの開始にはbeginAnimations:context:クラスメソッド、終了にはcommitAnimationsクラスメソッドを使用します。

※編注:両メソッドはiOS 4以降は非推奨です(『トランジションアニメーション(2)』参照)

デフォルトでの区域の原点は (0, 0)で、サイズはフレーム矩形のサイズと同じです。



参考文献

UIViewController Class Reference

CGGeometry Reference

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

iPhoneプログラミングUIKit詳解リファレンスiPhoneプログラミングUIKit詳解リファレンス
(2010/01/12)
所 友太

商品詳細を見る






SoundLink Mini Bluetooth speaker_ii_limited
0 Comments
Leave a comment
管理者にだけ表示を許可する
Top
0 Trackbacks
Top
Calendar
05 | 2017/06 | 07
Sun Mon Tue Wed Thu Fri Sat
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

SoundLink Mini Bluetooth speaker_ii_limited
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR