View Switcher(2)~SwitchViewController

2010. 08. 31
前回の『View Switcher(1)~下準備』で、アプリケーションデリゲートにSwitchViewControllerクラスを組み込みましたので、同クラスの中身を作成します。


●SwitchViewController.hの編集

2つのビューの切り替えを行うSwitchViewControllerでは、当該ビューのコントローラであるBlueViewControllerとYellowViewControllerのクラス導入とインスタンス変数、プロパティの宣言と、ビューの切り替えアクションであるswitchViewsの宣言を行います。
(太字が追加した部分)

#import <UIKit/UIKit.h>

@class BlueViewController;
@class YellowViewController;

@interface SwitchViewController : UIViewController {
    BlueViewController *blueViewController;
    YellowViewController *yellowViewController;
}
@property (retain, nonatomic) BlueViewController *blueViewController;
@property (retain, nonatomic) YellowViewController *yellowViewController;

- (IBAction)switchViews:(id)sender;

@end

745


●MainWindow.xibの編集

Xcodeの『グループとファイル』ペインにあるResources下のMainWindow.xibを開き、SwitchViewControllerのインスタンスを追加し、ビューとツールバーを追加します。

まず、LibraryウィンドウからViewControllerをDocumentウィンドウにドラッグ&ドロップして追加します。

746

この際、View Controllerのウィンドウが表示されます。

747

追加したView Controllerが選択されている状態で、InspectorウィンドウのIdentityタブを開き、『Class Identity』の『Class』を『SwitchViewController』に変更します。

748

続いてSwitchViewControllerの管理するビューとツールバーの追加を行います。

先程現れたView Controllerウィンドウに、LibraryウィンドウからViewをドラッグ&ドロップします。

749

ビューの下端にLibraryウィンドウからToolbarをドラッグ&ドロップして設置します。

750

デフォルトで付いてくるBar Button ItemのTitleを『Switch Views』に変更します。

751

次にこのBar Button Itemにアクションの接続を行います。

DocumentウィンドウでSwitch View Controllerを選択し、InspectorウィンドウのConnectionsタブで、『Received Actions』の『switchViews:』をView ControllerウィンドウのBar Button Itemに接続します。

752

最後にアプリケーションデリゲートとビューコントローラのインスタンスを接続します。

DocumentウィンドウでView Switcher App Delegateを選択し、InspectorウィンドウのConnectionsタブで、『Outlets』の『switchViewController』をDocumentウィンドウのSwitch View Controllerに接続します。

753


●SwitchViewController.mの編集

Xcodeに戻り、SwitchViewControllerのソースファイルを編集します。
(太字が追加・修正した部分)

#import "SwitchViewController.h"
#import "BlueViewController.h"
#import "YellowViewController.h"

@implementation SwitchViewController

@synthesize blueViewController;
@synthesize yellowViewController;

- (IBAction)switchViews:(id)sender {
    if (self.yellowViewController == nil) {
        YellowViewController *yellowController = [[YellowViewController alloc] initWithNibName:@"YellowView" bundle:nil];
        self.yellowViewController = yellowController;
        [yellowController release];
    }

    if (self.blueViewController.view.superview == nil) {
        [yellowViewController.view removeFromSuperview];
        [self.view insertSubview:blueViewController.view atIndex:0];
    }
    else {
        [blueViewController.view removeFromSuperview];
        [self.view insertSubview:yellowViewController.view atIndex:0];
    }
}

// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
    // Custom initialization
    }
    return self;
}

/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
    BlueViewController *blueController = [[BlueViewController alloc] initWithNibName:@"BlueView" bundle:nil];
    self.blueViewController = blueController;
    [self.view insertSubview:blueController.view atIndex:0];
    [blueController release];
}

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

- (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 {
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)dealloc {
    [blueViewController release];
    [yellowViewController release];
    [super dealloc];
}

@end

754

※2010.9.1訂正
switchViews:メソッドの2つ目の判定文のelseの場合の中で、
[self.view insertSubview:yellowViewController.view atIndex:0];
のyellowViewControllerがblueViewControllerになっていました。
お詫びして訂正させていただきます。

まず、SwitchViewControllerが管理する2つのビューコントローラ(BlueViewControllerとYellowViewController)のヘッダファイルをimportします。

そして両ビューコントローラのプロパティの実装と、deallocでの解放を行っています。

SwitchViewControllerではテンプレートで自動生成されるメソッドの内、initWithNibName:bundle:とviewDidLoad:、shouldAutorotateToInterfaceOrientation:のコメントアウトを解除しています。

shouldAutorotateToInterfaceOrientation:はデバイスの向きに対して、アプリケーションがどの方向に対応するかを示すメソッドで、今回はデフォルトの通常ポートレートのみとなっています。
(shouldAutorotateToInterfaceOrientation:の詳細は『Button Fun』を参照してください)

initWithNibName:bundle:はビューを読み込む前に呼び出されるメソッドで、UIViewControllerクラスの指定イニシャライザです。

今回の場合は、MainWindow.xibの内容に後からビューを追加するため、スーパークラスでの初期化を有効にしています。

viewDidLoadメソッドはnibファイルをメモリに読み込んだ後に追加する処理で、ここではアプリケーション起動時にBlueViewControllerを呼び出して表示するようにしています。

本書やサンプルコードではスーパークラスでの初期化の記述がありませんが、一応残しています。

BlueViewControllerクラスのインスタンスを生成し、initWithNibName:bundle:メソッドでBlueView.xibファイルで初期化してプロパティへ割り当て、insertSubview:atIndex:メソッドでビューを追加し、ビューコントローラのインスタンスはその場で解放しています。

アクションメソッドであるswitchViews:は、最初にYellowViewControllerのインスタンスの有無を確認し、無ければBlueViewControllerと同様にビューコントローラのインスタンスをプロパティへ割り当てます。

そしてどちらのビューに切り替えるかを判別するため、現在blueViewControllerのスーパービューがnilかどうかを判定し、nilであればYelloViewを破棄してBlueViewを挿入、そうでなければBlueViewを破棄してYelloViewを挿入します。


・initWithNibName:bundle:
(UIViewControllerクラス)

- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle

指定したバンドルのnibファイルで新規に初期化されたビューコントローラを返します。

戻り値は新規に初期化されたUIViewControllerオブジェクトになります。

これはこのクラスの指定イニシャライザです。

nibNameパラメータにnilを指定して、カスタムサブクラスでloadViewメソッドをオーバーライドしない場合、デフォルトのビューコントローラの動作は、ビューコントローラクラスの名前と一致する(.nib拡張子を除いた)nibファイル名を検索します。

もし一つ見つかった場合、クラス名がnibNameプロパティの値になり、結果として対応するnibファイルはこのビューコントローラに関連付けされます。

nibName:nibファイルの名前で、パス情報は含みません。
nib名を指定し、nibファイルを読み込んだ後に値を設定する必要がある場合は、viewDidLoadメソッドをオーバーライドしてください。
この引数がnilの場合、nibNameプロパティはnilに設定されます。
この場合、viewプロパティを設定するために、loadViewメソッドをオーバーライドする必要があります。

nibBundle:nibファイル検索するためのバンドルを指定します。
このメソッドはnibファイルを検索する際、最初にバンドルの言語指定のプロジェクトディレクトリ内を検索し、その後Resourcesディレクトリを検索します。
nilの場合、このメソッドはメインバンドル内のnibファイルを検索します。


・insertSubview:atIndex:
(UIViewクラス)

- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index

指定したインデックスにサブビューを挿入します。

ビューは一つだけスーパービューを持つことができます。

viewのスーパービューがnilではなく、現在のビューと同じではない場合、このメソッドは現在のビューのサブビューを作成する前に、以前のスーパービューから削除します。

view:挿入するビューを指定します。
この値はnilにすることはできません。

index:サブビューを示す0から始まる値で、サブビューの数以上にすることはできません。


・superview
(UIViewクラス)

@property(nonatomic, readonly) UIView *superview

レシーバのスーパービュー、無い場合はnilになります。(読み込みのみ)


・removeFromSuperview
(UIViewクラス)

- (void)removeFromSuperview

スーパービューとそのウィンドウからレシーバを解除し、レスポンダチェーンから削除します。

レシーバのスーパービューがnilでない場合、このメソッドはレシーバを解放します。

ビューを再利用する予定の場合は、このメソッドを呼び出す前に保持し、それが済んだ後または他のビュー階層に追加した後の適切な時に解放してください。

表示中にこのメソッドを呼び出すことは決してしないでください。



参考文献

UIViewController Class Reference

UIView Class Reference

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

商品詳細を見る

View Switcher(1)~下準備

2010. 08. 29
はじめてのiPhoneプログラミング』の次のお題は、マルチビューアプリケーションの作成です。

ツールバーの切り替えボタンをタップすることで、2つのビューを入れ替えるアプリケーションを、Window-Based Applicationテンプレートを使って一から構築します。


●プロジェクトの作成

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

734


●ビューコントローラとnibファイルの追加

ビューを切り替えるコントローラと、2つのビューの内容を表すソースファイル、ビューのベースとなる2つのnibファイルをプロジェクトに追加します。

まず、『グループとファイル』で『Classes』を右クリックし、『追加』→『新規ファイル...』を選択します。

左ペインで『iPhone OS』下の『Cocoa Touch Class』を選択し、右上ペインの『UIViewController Subclass』を選択して『次へ』に進みます。

735

ファイル名を『SwitchViewController.m』とし、『同時に"~.h"も作成』のチェックを入れたまま『完了』を押します。

736

同様にビュー内容を表す『BlueViewController』と『YellowViewController』を、UIViewController Subclassとして作成します。

737

続いてnibファイルを追加します。

『グループとファイル』で『Resources』を右クリックし、『追加』→『新規ファイル...』を選択します。

左ペインで『iPhone OS』下の『User Interface』を選択し、右上ペインの『View XIB』を選択して『次へ』に進みます。

738

ファイル名を『BlueView.xib』として『完了』を押します。

739

同様に『YellowView.xib』を作成します。

740


●アプリケーションデリゲートのコード修正

自動生成されるアプリケーションデリゲートに、メインの制御クラスであるSwitchViewControllerを扱えるようにコードを修正します。
(太字が追加した部分)

#import <UIKit/UIKit.h>

@class SwitchViewController;

@interface View_SwitcherAppDelegate : NSObject {
    UIWindow *window;
    SwitchViewController *switchViewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet SwitchViewController *switchViewController;

@end

741

※2010.8.30訂正
『@class SwitchViewController』の部分で、行末のセミコロンが抜けていました。
お詫びして訂正させていただきます。

View_SwitcherAppDelegate.hでは、最初にアプリケーションのメイン処理となるSwitchViewControllerクラスを追加します。

旧版の本文中では『@class View_SwitcherViewController;』を中線で消していますが、これはView-Based Applicationテンプレートを使用した際に自動生成されるもので、今回のようにWindow-Based Applicationテンプレートを使用した場合は存在しません。

@classは『詳解 Objective-C 2.0』によるとクラスを宣言するコンパイラ指示子とあり、アプリケーションデリゲートにSwitchViewControllerクラスを使うことを宣言していることになります。

ここで#importではなく@classを使用しているのは、STUDIOT::DEVLOGさんの『@classと#importの使い分け』を参考に考えてみると、SwitchViewControllerクラスのアウトレットをプロパティを扱うだけで、中身はSwitchViewControllerクラスそのもので行うためのようです。

実際、View_SwitcherをView-Based Applicationテンプレートでプロジェクトを作成してみると、View_SwitcherAppDelegate.hは以下のようになります。

#import <UIKit/UIKit.h>

@class View_SwitcherViewController;

@interface View_SwitcherAppDelegate : NSObject {
    UIWindow *window;
    View_SwitcherViewController *viewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet View_SwitcherViewController *viewController;

@end

742

このようにView-Basedテンプレートでは、ビューコントローラView_SwitcherViewControllerを@classで宣言しています。

次にインスタンス変数とプロパティの宣言ですが、これまでインスタンス変数の宣言時にIBOutletを付けていて、旧版本文中やサンプルコードでもそのようになっているのですが、テンプレートで自動生成された内容ではプロパティの宣言にIBOutletを付けていますので、ここではその書式に従うことにします。

ビューコントローラのインスタンスswitchViewControllerをアウトレットとし、プロパティを宣言します。

続いてView_SwitcherAppDelegate.mの修正を行います。
(太字が追加した部分)

#import "View_SwitcherAppDelegate.h"
#import "SwitchViewController.h"

@implementation View_SwitcherAppDelegate

@synthesize window;
@synthesize switchViewController;

#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // Override point for customization after application launch.
    [window addSubview:switchViewController.view];
    [window makeKeyAndVisible];

    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
    /*
    Sent when the application is about to move from active to inactive state. This can occur for certain types of
    temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the
    application and it begins the transition to the background state.
    Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games
    should use this method to pause the game.
    */
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    /*
    Use this method to release shared resources, save user data, invalidate timers, and store enough application
    state information to restore your application to its current state in case it is terminated later.
    If your application supports background execution, called instead of applicationWillTerminate: when the user
    quits.
    */
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    /*
    Called as part of transition from the background to the inactive state: here you can undo many of the changes
    made on entering the background.
    */
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    /*
    Restart any tasks that were paused (or not yet started) while the application was inactive. If the application
    was previously in the background, optionally refresh the user interface.
    */
}

- (void)applicationWillTerminate:(UIApplication *)application {
    /*
    Called when the application is about to terminate.
    See also applicationDidEnterBackground:.
    */
}

#pragma mark -
#pragma mark Memory management

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
    /*
    Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from
    disk) later.
    */
}

- (void)dealloc {
    [switchViewController release];
    [window release];
    [super dealloc];
}

@end

743

ビューコントローラのヘッダファイルSwitchViewController.hの読み込み、プロパティの実装、deallocでの解放は、View-Based Applicationテンプレートで自動生成される内容と同じです。

#import "View_SwitcherAppDelegate.h"
#import "View_SwitcherViewController.h"

@implementation View_SwitcherAppDelegate

@synthesize window;
@synthesize viewController;

#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // Override point for customization after application launch.

    // Add the view controller's view to the window and display.
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];

    return YES;
}

(中略)

- (void)dealloc {
    [viewController release];
    [window release];
    [super dealloc];
}

@end

744

applicationDidFinishLaunching:は、アプリケーションの起動が完了したことをデリゲートに伝えるメソッドで、iOS 3.0以降はapplication:didFinishLaunchingWithOptions:に置き換えられています。
(詳細は『UIApplicationDelegateプロトコル』を参照してください)

メソッドが置換されていますが内容はほぼ同じで、ビューコントローラの管理するビューをサブビューとしてウィンドウに追加するメソッドが書き加えられています。
(addSubView:メソッドについては『音楽プレイヤのメソッド(1)』を参照してください)


・view
(UIViewControllerクラス)

@property(nonatomic, retain) UIView *view

コントローラが管理するビューです。

このプロパティで格納されるビューはビューコントローラのビュー階層のルートビューを表します。

ビューコントローラが(モーダルまたはビューコントローラベースのインターフェイスの一部を)画面に提示する時は常に、アプリケーションのウィンドウにこのビューを取得して表示します。

このプロパティのデフォルト値はnilです。

現在この値がnilの時にこのプロパティでアクセスすると、ビューコントローラは自動的にloadViewメソッドを呼び出し、その結果のビューを返します。

デフォルトのloadViewメソッドは、(もしあれば)ビューコントローラに関連付けされたnibファイルからビューの読み込みを試みます。

ビューコントローラが関連付けされたビューコントローラを持っていない場合、loadViewメソッドをオーバーライドし、ルートビューと全てのサブビューの生成に使用します。

各ビューコントローラオブジェクトは、そのビューの唯一の所有者です。

複数のビューコントローラオブジェクトが、同じビューオブジェクトを関連付けすることはできません。

このプロパティがアクセスする自動的に読み込まれるビューは、isViewLoadedプロパティを使って現在のメモリ内にあるか確認することができます。

isViewLoadedプロパティはviewプロパティと異なり、現在のメモリ内にビューが存在しない場合はビューを読み込むことはできません。

UIViewControllerクラスは、メモリ不足状態の間はこのプロパティをnilに設定し、ビューコントローラ自身が最終的に解放します。



参考文献

STUDIOT::DEVLOG/@classと#importの使い分け

UIViewController Class Reference

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

商品詳細を見る

詳解 Objective-C 2.0 第3版詳解 Objective-C 2.0 第3版
(2011/12/28)
荻原 剛志

商品詳細を見る

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)
所 友太

商品詳細を見る

Autosize(2)~コードによる変更

2010. 08. 22
前回の『Autosize(1)~Autosizing』は、Interface BuilderのAutosizingを使って、ランドスケープ時のオブジェクトパーツのレイアウトを自動調整しました。

しかしシステムによる自動調整は簡単で便利ですが、必ずしも望む結果にはなりません。

本書でも例としてボタンのサイズを125x125ピクセルにした場合の例を示しています。

各ボタンの位置とサイズを下表のように変更してみます。

 XYWH
Rounded Rect Button (1)2020125125
Rounded Rect Button (2)17520125125
Rounded Rect Button (3)20168125125
Rounded Rect Button (4)175168125125
Rounded Rect Button (5)20315125125
Rounded Rect Button (6)175315125125

712

ランドスケープモードにすると高さが足りず、パーツが重なってしまいます。

713

ちなみに各ボタンのサイズをバネで自動調整すると下図のようになります。

714

これで構わない場合はいいのですが、パーツサイズを変えたくなかったり、中央の空間が気になる場合は手動で調整することができます。


●アウトレットの宣言

AutosizeViewController.hを開き、ボタン6個分のアウトレットとプロパティを宣言します。
(太字が追加した部分)

#import <UIKit/UIKit.h>

@interface AutosizeViewController : UIViewController {
    IBOutlet UIButton *button1;
    IBOutlet UIButton *button2;
    IBOutlet UIButton *button3;
    IBOutlet UIButton *button4;
    IBOutlet UIButton *button5;
    IBOutlet UIButton *button6;
}
@property (nonatomic, retain) UIView *button1;
@property (nonatomic, retain) UIView *button2;
@property (nonatomic, retain) UIView *button3;
@property (nonatomic, retain) UIView *button4;
@property (nonatomic, retain) UIView *button5;
@property (nonatomic, retain) UIView *button6;

@end

715


●画像の追加とアウトレットの接続

ただのRound Rect Buttonでは味気ないので画像を追加してみます。
(機能とは関係ありませんので省略しても構いません)

Resourcesに125x125のPNGファイルを6個追加します。

716

Interface Builderに移り、InspectorウィンドウのAttributesタブで『Title』の数字を削除し、『Image』で画像を選択します。

717

画像を入れ終わったらDocumentsウィンドウでFile's Ownerを選択し、InspectorウィンドウでConnectionsタブを開き、Round Rect Buttonとアウトレットを接続します。

718


●回転時のレイアウトのコード記述

ポートレートおよびランドスケープ時における、6つのボタンのレイアウト切り替えをコードで記述します。
(太字が追加した部分)

#import "AutosizeViewController.h"

@implementation AutosizeViewController

@synthesize button1;
@synthesize button2;
@synthesize button3;
@synthesize button4;
@synthesize button5;
@synthesize button6;

- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
        duration:(NSTimeInterval)duration {
    UIInterfaceOrientation toInterfaceOrientation = self.interfaceOrientation;

    [UIView beginAnimations:@"move buttons" context:nil];

    if (toInterfaceOrientation == UIInterfaceOrientationPortrait ||
        toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
        button1.frame = CGRectMake( 20, 20, 125, 125);
        button2.frame = CGRectMake(175, 20, 125, 125);
        button3.frame = CGRectMake( 20, 168, 125, 125);
        button4.frame = CGRectMake(175, 168, 125, 125);
        button5.frame = CGRectMake( 20, 315, 125, 125);
        button6.frame = CGRectMake(175, 315, 125, 125);
    }
    else {
        button1.frame = CGRectMake( 20, 20, 125, 125);
        button2.frame = CGRectMake(20, 155, 125, 125);
        button3.frame = CGRectMake(177, 20, 125, 125);
        button4.frame = CGRectMake(177, 155, 125, 125);
        button5.frame = CGRectMake(328,  20, 125, 125);
        button6.frame = CGRectMake(328, 155, 125, 125);
    }
    [UIView commitAnimations];
}

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

- (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;
}

- (void)dealloc {
    [button1 release];
    [button2 release];
    [button3 release];
    [button4 release];
    [button5 release];
    [button6 release];
    [super dealloc];
}

@end

719

最初に6つのボタンのプロパティの実装、最後にdeallocで解放を行っています。

shouldAutorotateToInterfaceOrientation:メソッドは前回実装したままです。

willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:メソッドは、ユーザインターフェイスが回転する際の後半開始時に呼び出されるメソッドです。

このメソッドの引数fromInterfaceOrientationは回転する前の向きなので、回転後の向きの取得にinterfaceOrientationプロパティを使用します。

トランジションアニメーション(2)』で説明しましたが、beginAnimations:context:、commitAnimationsメソッドはiOS 4で非推奨となっているので、transitionWithView:duration:options:animations:completion:メソッドに置換する必要があるのですが、現在のシミュレーション環境ではアニメーションブロックの有無で視覚的差異が確認できず、またApress社のサイト内にサンプルコードでは使用していないため不要かもしれません。

そして条件分岐でポートレート/ランドスケープ時におけるボタンのレイアウトを指定しています。

ポートレート時の座標はInterface Builderで設定している内容と同じものです。

CGRectMakeでボタンの原点座標とサイズを指定し、frameプロパティで再配置用の座標とサイズとして設定します。
(frameプロパティの詳細は『スライドショーの簡易アニメーション(2)』を参照してください)

ランドスケープ時はボタンを3列2行に設定しています。

720


・willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:
(UIViewControllerクラス)

- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration

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

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

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

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

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

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

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

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

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


・interfaceOrientation
(UIViewControllerクラス)

@property(nonatomic, readonly) UIInterfaceOrientation interfaceOrientation

インターフェイスの現在の向きを示します(読み込み専用)

有効な値はUIInterfaceOrientationで説明します。


・CGRectMake
(CGGeometry)

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

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

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

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

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

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


●コードの修正

willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:メソッドの説明にもありますが、回転の前半の開始時に用いるwillAnimateFirstHalfOfRotationToInterfaceOrientation:duration:メソッドと合わせて一つとして扱うwillAnimateRotationToInterfaceOrientation:duration:メソッドに置き換えてみます。
(willAnimateRotationToInterfaceOrientation:duration:メソッドはiOS 3.0以降で使用できることから、Apress社のサイト内にサンプルコードでもOSのバージョンで条件分岐して使用する形になっています)

そして前述の通り(現行の開発環境ですと)視覚的効果が無さそうなのと、サンプルコードにも記述が無いので、アニメーションブロックの記述は削除します。

また、ランドスケープモード時のレイアウトが、

135
246

となっていますが、なんとなく違和感があるので

123
456

とします。

shouldAutorotateToInterfaceOrientation:メソッドも読み易いように、上下逆のポートレートの否定に条件を修正します。

また旧版の本書内では記述が抜けていますが、viewDidUnloadメソッドにアウトレットの所有権放棄を追加します。
(サンプルコードではviewDidUnloadメソッドにアウトレットの所有権放棄の記述があるものの、deallocでの解放の記述が抜けています)

コメントアウトしているメソッドを削除したAutosizeViewController.mの修正版は下記のようになります。
(太字が追加・修正した部分)

#import "AutosizeViewController.h"

@implementation AutosizeViewController

@synthesize button1;
@synthesize button2;
@synthesize button3;
@synthesize button4;
@synthesize button5;
@synthesize button6;

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
        duration:(NSTimeInterval)duration {

    if (interfaceOrientation == UIInterfaceOrientationPortrait ||
        interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
        button1.frame = CGRectMake( 20, 20, 125, 125);
        button2.frame = CGRectMake(175, 20, 125, 125);
        button3.frame = CGRectMake( 20, 168, 125, 125);
        button4.frame = CGRectMake(175, 168, 125, 125);
        button5.frame = CGRectMake( 20, 315, 125, 125);
        button6.frame = CGRectMake(175, 315, 125, 125);
    }
    else {
        button1.frame = CGRectMake( 20, 20, 125, 125);
        button2.frame = CGRectMake(177, 20, 125, 125);
        button3.frame = CGRectMake(328, 20, 125, 125);
        button4.frame = CGRectMake( 20, 155, 125, 125);
        button5.frame = CGRectMake(177, 155, 125, 125);
        button6.frame = CGRectMake(328, 155, 125, 125);
    }
}

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

- (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.button1 = nil;
    self.button2 = nil;
    self.button3 = nil;
    self.button4 = nil;
    self.button5 = nil;
    self.button6 = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [button1 release];
    [button2 release];
    [button3 release];
    [button4 release];
    [button5 release];
    [button6 release];
    [super dealloc];
}

@end

722

実行結果は下図のようになります。

723

・willAnimateRotationToInterfaceOrientation:duration:
(UIViewControllerクラス)

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration

インターフェイスの回転を一工程で実行する前に、ビューコントローラに送信します。

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

このメソッドをオーバーライドする場合は、willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:またはwillAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:メソッドをオーバーライドしないでください。

このメソッドはビューを回転するために、アニメーションブロック内から呼び出されます。

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

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

:ビューにスライドして出入りするヘッダとフッタのアニメーションの実行は、アニメーションブロックとは別に実行されます。

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

従って、このメソッド内でビューにレイアウトの追加要求を実行することができます。

interfaceOrientation:ユーザインターフェイスの新しい向きを指定します。
有効な値はUIInterfaceOrientationで説明します。

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



参考文献

UIViewController Class Reference

CGGeometry Reference

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

商品詳細を見る

Autosize(1)~Autosizing

2010. 08. 21
はじめてのiPhoneプログラミング』の次のお題は、オートローテーション(Autorotation)とオートサイジング(Autosizing)です。

デバイスの向きに合わせてビューを回転させると同時に、アスペクト比の変化に伴うオブジェクトパーツのレイアウトやサイズを調整するものです。

本書では三通りの方法が紹介されており、

1)Interface BuilderのInspectorウィンドウのSizeタブにある『Autosizing』の設定
2)コード記述によるオブジェクトパーツの手動調整
3)ポートレート/ランドスケープの各ビューの切り替え

となっています。

今回は1のAutosizingによる設定を説明します。


●プロジェクトの作成

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

703


●オートローテーションの有効化

AutosizeViewController.mを開き、テンプレートで自動生成されてコメントアウトしてあるshouldAutorotateToInterfaceOrientation:メソッドを有効にします。

このメソッドは、ビューコントローラがデバイスの向きに対応するかどうかをブール値で返すメソッドです。
(shouldAutorotateToInterfaceOrientation:メソッドと、interfaceOrientationパラメータについての詳細は『Button Fun』を参照してください)


デフォルトは通常のポートレート(UIInterfaceOrientationPortrait)のみ有効となっています。

今回はこれに加えて左右のランドスケープ(UIInterfaceOrientationLandscapeLeft/UIInterfaceOrientationLandscapeRight)も有効にします。

上下逆のポートレート(UIInterfaceOrientationPortraitUpsideDown)は非推奨とのことですので、無効とします。
(太字が追加した部分)

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

704

旧版の本文では、既存のポートレートに左右のランドスケープを加え、論理和で表しています。
(C言語の基本的な演算子については、ecbさんの目指せプログラマー!/C言語入門:基本的な決まり/1-6. 簡単な演算』などを参照してください)

Apress社のサイト内にサンプルコードの『Beginning iPhone Development Projects Oct 12 2009/05 Autosize 1』では、4つのパラメータの内1つのみ除外していることから、

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

と、上下逆のポートレートを否定で表しています。


●6つのボタンの設置

AutosizeViewController.xibを開き、LibraryウィンドウからRound Rect Buttonをドラッグ&ドロップで6つViewウィンドウに設置します。

各ボタンの位置とサイズは下表の通りです。

 XYWH
Rounded Rect Button (1)20207237
Rounded Rect Button (2)228207237
Rounded Rect Button (3)202127237
Rounded Rect Button (4)2282127237
Rounded Rect Button (5)204037237
Rounded Rect Button (6)2284037237

ButtonのTitleは左上から1~6の番号を設定します。

705

本書にもあるように、この時点では左右ランドスケープモードでビューの回転には対応するものの、ボタンが2つ隠れてしまいます。

706


●Autosizingの設定

Interface Builderでボタンを選択した状態で、InspectorウィンドウのSizeタブを開くと『Autosizing』という項目があります。

ここでビューのアスペクトが変更された場合における、選択されているオブジェクトのレイアウトとサイズを調整することができます。

707

・レイアウトの調整

Autosizingの左側にある二重の四角で、外側と内側の間にある『I』型の記号は支柱(Struts)と呼ばれるもので、ビューの端からオブジェクトを固定するかどうかを表します。

実線の『I』ではその端に固定され、破線では非固定となります。

・サイズの調整

内側の四角内にある『←→』記号はバネ(Spring)と呼ばれるもので、ビューのサイズ変化に対してオブジェクトのサイズを拡大縮小するかを表します。

実線の『←→』では拡大縮小し、破線ではサイズ固定となります。

・本書での記述内容

本書(およびサンプルコード)ではサイズ調整はせず、レイアウトの調整のみ行っています。

各ボタンの隣接するビューの端(『1』は『上と左』、『3』は『左』、『5』は『左と下』)に支柱を設定しています。

708

709

支柱はポートレート/ランドスケープに関わらず20ピクセルとなっています。

色々設定して試すと分かりますが、バネや支柱による自動調整は必ずしもレイアウトやサイズが期待している通りにはなるわけではないので、きっちり設定したい場合は手動調整やビューの切り替えを行う必要があります。


●ビューの自動サイズ調整の動作設定

ユーザがウィンドウのサイズを変更した時、システムはオートサイジングの規則に従って、自動的にウィンドウ内のビューをリサイズします。
(オートサイジングはビューにも適用され、例えば分割ビューではそれ自身が動的にサイズ変更することができます)

全てのiOSとMac OS Xアプリケーションはオートサイジング規則をサポートしており、ビューのリサイズにおいて手動でコードを記述する必要が無いようにしています。

オートサイジング規則は、ビューが拡大または縮小された新しい領域を満たすように、フレームと親ビューのフレーム間の領域を固定または可変値によって決定します。

Interface BuilderではInspectorのSizeを使って、下図に示すようにデフォルトでのビューのオートサイジング規則を構成することができます。

オートサイジングの制御はバネ(Springs)と支柱(Structs)を用いて、選択したビューと親フレームとの関係を定義します。

バネはスーパービューの幅と高さを元に、リサイズしたビューに比例して変更されます。

支柱はスーパービューでの縁からの固定された距離を、ビューでも保持します。

Inspectorでは、バネと支柱の変更によるビューのオートサイジングの動作結果を、アニメーションとして視覚的描写でコントロールの右側に表示します。

図:ビューのオートサイジング規則の構成

711

Interface Builderでのビューのオートサイジングの動作に、プログラミングによって構成を追加することができます。

UIViewオブジェクトの場合は、ビューのautoresizingMaskプロパティを使います。

NSViewオブジェクトの場合は、ビューのsetAutoresizingMask:メソッドを使います。

CarbonではHIViewSetLayoutInfo関数を使います。

重要:Cocoaのnibファイルで、Interface Builderでビューのバネや支柱を設定しない状態で、setAutoresizingMask:メソッドを使って実行時にオートサイジングの動作を追加しても、ビューはオートリサイジングの動作を訂正して提示することができません。
その理由は、Interface Builderにおいて親ビューの子がバネや支柱の設定をしていないと、オートサイジングが無効になるからです。
オートサイジングを再び有効にするには、親ビューにsetAutoresizesSubviews:メソッドでYESを渡す必要があります。
つまり、子ビューは正しくautosizeの設定をする必要があります。



参考文献

目指せプログラマー!/C言語入門:基本的な決まり/1-6. 簡単な演算

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

商品詳細を見る

Control Fun(5)~Sample Code

2010. 08. 17
先に述べていましたが、手元にある旧版の『はじめてのiPhoneプログラミング』の内容と、新版『はじめてのiPhone3プログラミング』の内容と思しきApress社のサイト内のサンプルコードではいくつかの相違点がありますので、その辺の解説をしてみたいと思います。


●外観上のインターフェイスの違い

旧版のコードを説明通りに作ると、『Control Fun(1)~Image ViewとText Field』から『Control Fun(4)~Action SheetとAlert』で説明してきたものになります。

687

一方、新版の内容と思われるApress社のサイト内のサンプルコードは下図のようになっています。

688

中央付近のセグメンテッドコントロールが『Show/Hidden』から『Switches/Button』に変更され、Switchesの場合は2つの連動するスイッチが、Buttonの場合はDo Somethingボタンが表示されるように変更され、起動時にはDo Somethingボタンが表示されないようになっています。

689

見た目上は、表示の切り替え方式が変更されただけで、スイッチやDo Somethingボタンの機能に違いはありません。


●nibファイルの違い

外見上はスイッチが乗っていたswitchViewをDo Somethingボタン用にもう一枚作って切り替えているように見えるのですが、Control_FunViewController.xibを見るとそうではないことが分かります。

690

旧版では親ビュー下にスイッチを乗せるサブビューがあり、その下に二組のラベルとスイッチが置かれています。

691

しかし新版ではサブビューが無く、スイッチとDo Somethingボタンが直接置かれています。

更にキーボードを片付けるための隠しボタンが無く、親ビューがUIViewオブジェクトからUIControlオブジェクトに変更されています。


・隠しボタンの置換

UIViewとUIControlは以下のような関係になっています。

NSObject
    |
    ∟UIResponder
            |
            ∟UIView
                    |
                    ∟UIImageView
                    |
                    ∟UIControl
                            ∟UIButton
                            ∟UISegmentedControl
                            ∟UISlider

UIViewはUIControlのスーパークラスなのでUIViewの機能を継承しており、且つUIControlはUIButtonのスーパークラスなので、UIViewとUIControlを置換できるというわけです。

何故置換したかというとUIViewにはアクションを設定できないからで、DocumentウィンドウでUIViewを選択してInspectorウィンドウのConnectionsタブを開くと分かります。

692

UIViewのConnectionsタブにはアウトレットしか表示されません。

693

しかしUIControlのConnectionsタブにはアウトレットの他にEventsの項目があり、アクションを設定できるようになっています。

そして隠しボタンで呼び出していたメソッド『backgroundClick:』は『backgroundTap:』と名前を変え、隠しボタン同様『Touch Up Inside』で設定されています。
(backgroundClick:とbackgroundTap:メソッドの内容は同一です)

このようにするには、まずDocumentウィンドウで隠しボタン(親Viewのすぐ下にあるCustim Button)をEditメニューのDeleteで削除します。
(このボタンを削除しないと親Viewより前面にあるために入力を受け付けてしまい、親Viewが応答できない)

次にDocumentウィンドウで親Viewを選択し、InspectorウィンドウのIdentityタブで『Class Identity』の『Class』を『UIControl』に変更します。

694

そしてDocumentウィンドウでFirst Responderを選択し、InspectorウィンドウのConnectionsタブでbackgroundClick:をControl(旧親View)に接続します。

695

サンプルコードではFirst Responderと接続していますが、File's Ownerから接続しても動作します。
(サンプルコードが何故First Responderから接続しているのか、File's Ownerから接続するのとどう違うのかは分かりません)


・セグメンテッドコントロールの修正

nibファイル上でのセグメンテッドコントロールの違いは少なく、Attributesタブで言えばTitleが『Show/Hide』から『Switches/Button』になっているくらいです。

そしてConnectionsタブを見ると、toggleShowHide:メソッドからtoggleControls:メソッドに変わっています。

両メソッドは中身が異なりますので後述しますが、ここではそのままにしておきます。
(メソッド名を変えると接続し直さなければならないので)


・スイッチの変更

サンプルコードでは、スイッチのラベルが無く、サブビューも使用していません。

最小限の変更で動作確認を済ませたいので、まずDocumentウィンドウでスイッチ二つをドラッグ&ドロップでサブビューから外します。

696

そしてサブビューを選択し、InspectorウィンドウのAttributesタブで、『View』の『Drawing』で『Hidden』にチェックを入れます。

697

この状態ですと、サブビューの後面にスイッチがある状態ですが、サブビューをHiddenにすることで無効にできますので、(後で元に戻す手間などを考えると)動作確認する分にはこれで構わないと思います。

また、起動時にDo Somethingボタンを非表示にしたいので、InspectorウィンドウのAttributesタブで、『View』の『Drawing』で『Hidden』にチェックを入れます。

698

Do Somethingボタンはスイッチと同じような位置にありますが、動作確認するだけですのでそのままの位置に置いておきます。


●ヘッダファイルの違い

サブビューを使用しないため、switchViewアウトレットとプロパティが不要になります。

キーボードを片付けるメソッド名がbackgroundClick:からbackgroundTap:になっていますが、前述の通り中身は一緒です。
(リファレンス類を読んでると、『タップする』ところが『クリックする』になっていることがよくありますし)

アクションシートを表示するメソッド名がdoSomething:からbuttonPressed:になっていますが、これも中身は一緒です。

セグメンテッドコントロールのメソッド名がtoggleShowHide:がtoggleControls:になっていて中身が違うのですが、再接続の手間を考えてメソッド名はそのままにし、中身だけ書き換えます。

それとセグメンテッドコントロールの判定に使う定数名も、kShowSegmentIndexからkSwitchesSegmentIndexに変わっていますが、使用方法は変わらないのでそのままにします。

また、通常インスタンス変数の宣言で『IBOutlet ~』と記述することが多いのですが、新版サンプルコードではプロパティの宣言で『IBOutlet ~』と記述しています。

新版ではこの辺の説明がされているのかもしれませんが、私の手元に無いので違いが分かりません。


●ソースファイルの違い

・セグメンテッドコントロールのメソッド

まずセグメンテッドコントロールのメソッドから見てみます。

旧版のtoggleShowHide:は、スイッチの乗っているビューの表示/非表示を切り替えていましたが、新版のサンプルコードのtoggleControls:ではスイッチおよびDo Somethingボタンをhiddenプロパティで直接表示/非表示の切り替えを行っています。

また細かいところでは、引数senderをUISegmentedControlクラスにキャストして、selectedSegmentIndexプロパティでNSIntegerの変数segmentに代入していた部分が、引数senderからselectedSegmentIndexプロパティで値を得てそのまま比較に使用するよう、簡略化が図られています。

- (IBAction)toggleShowHide:(id)sender {
/*
    UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
    NSInteger segment = segmentedControl.selectedSegmentIndex;

    if (segment == kShowSegmentIndex) [switchView setHidden:NO];
    else [switchView setHidden:YES];
*/

    if ([sender selectedSegmentIndex] == kShowSegmentIndex) {
        leftSwitch.hidden = NO;
        rightSwitch.hidden = NO;
        doSomethingButton.hidden = YES;
    }
    else {
        leftSwitch.hidden = YES;
        rightSwitch.hidden = YES;
        doSomethingButton.hidden = NO;
    }
}

699

hiddenプロパティはUIViewクラスのものです。
(『スライドショーのフルスクリーン化(2)』を参照してください)

700

701


・viewDidUnloadとdealloc

旧版の本書内では説明されていませんが、新版のサンプルコードではアウトレットに対してviewDidUnloadでの所有権の放棄とdeallocでの解放を行っています。
(太字が追加した部分)

- (void)viewDidUnload {
    self.nameField = nil;
    self.numberField = nil;
    self.sliderLabel = nil;
    self.leftSwitch = nil;
    self.rightSwitch = nil;
    self.switchView = nil;
    self.doSomethingButton = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [nameField release];
    [numberField release];
    [sliderLabel release];
    [leftSwitch release];
    [rightSwitch release];
    [switchView release];
    [doSomethingButton release];
    [super dealloc];
}

702

この辺は説明が無くても定型文として書くものと考えた方が良さそうです。
(viewDidUnloadメソッドについては『Button Fun』を参照してください)


・dealloc
(NSObjectクラス)

- (void)dealloc

レシーバにより占有されているメモリを解放します。

(解放されたメモリがまだ再使用されていない場合)解放されたオブジェクトにメッセージが送信されると、後のメッセージはエラーと示されて生成されレシーバに送られます。

deallocメッセージは決して直接送信しないでください。

代わりにNSObjectプロトコルのreleaseメソッドを通して、オブジェクトのdeallocメソッドを間接的に呼び出すことができます。
(releaseメッセージを送信した結果、レシーバのretainカウントが0になれば解放します)

これらのメソッドの詳細については『高度なメモリ管理プログラミングガイド』を参照してください。

解放されたオブジェクトが所有していたデータまたはインスタンス変数に、動的にストレージを割り当てるなど、オブジェクトによる任意に追加されたメモリ消費を解放するためには、独自バージョンのdeallocをサブクラスで実装する必要があります。

クラス固有の解放を行った後、サブクラスメソッドはsuper:にスーパークラス用のdeallocメッセージを渡すように組み込む必要があります。

- (void)dealloc {
    [companion release];
    NSZoneFree(private, [self zone])
    [super dealloc];
}

重要:工程のメモリが終了時に自動的にクリアされている場合、アプリケーション終了時にdeallocメッセージが送信できないことがあります。
OSがリソースのクリーンアップを全てのメモリ管理メソッドを呼び出すのに比べて、その方がより効率的です。
その他の理由も含め、deallocで不足がちなリソースの管理をしないでください。
詳細は『高度なメモリ管理プログラミングガイド』を参照してください。

特別な考慮事項

ガベージコレクションが有効な場合、ガベージコレクションはdeallocの代わりにfinalizeをレシーバに送信します。
ガベージコレクションが有効な場合、このメソッドは何もしません。

(編注:現在、iOSはガベージコレクションに対応していません)



参考文献

NSObject Class Reference

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

商品詳細を見る

UIViewControllerクラス

2010. 08. 16
●UIViewControllerクラス

UIViewControllerクラスは、iPhoneアプリケーションのビュー管理モデルの基礎を提供します。

基本的なビューコントローラクラスは、関連付けされたビューの提示に加え、モーダルビューの管理のための基本的なサポートと、デバイスの向きの変更に対応したビューの回転をサポートします。

UINavigationControllerやUITabBarControllerなどのサブクラスは、ビューやビューコントローラの複雑な階層の管理に、補足的な動作を提供します。

フルスクリーンのビューの管理には、UIViewControllerの各インスタンスを使用します。

単純なビューコントローラでは、アプリケーションのコンテンツを提示するために、ビュー階層の管理を伴います。

典型的なビュー階層は、ルートビューと実際にコンテンツを提示する(このクラスのビュープロパティ内で参照可能な)一つ以上のサブビューで構成されます。

ナビゲーションとタブバーコントローラの場合は、ビュー階層の高いレベルだけでなく(これはナビゲーションコントロールが提供します)、アプリケーションのコンテンツを提示する処理として一つ以上の追加されたビューコントローラの管理も行います。

:ウィンドウの一部分のみを満たすビュー(つまりアプリケーションのコンテンツの矩形により定義された一部の領域のみ)の管理に、ビューコントローラを使用しないでください。
いくつかの小さなビューで構成されたインターフェイスにしたい場合は、一つのルートビューに全て埋め込み、ビューコントローラでビューを管理してください。

ビューコントローラはビューを管理する義務があり、またイベント処理に使われるレスポンダチェーンの一部でもあります。

ビューコントローラは自身がUIResponderクラスの子孫であり、ビューとスーパービュー間の管理をレスポンダチェーン内に挿入します。

したがって、ビューコントローラにより管理されているビューでイベント処理ができない場合、ビューコントローラにイベントが渡され、その次のイベント処理が選択されるか、またはビューのスーパービューに転送されます。

UIViewControllerクラスは、デバイスの向きの変更に応答してビューコントローラのビューを回転させるためのサポートを、自動的に提供します。

自動回転の動作の一部として、ビューコントローラは全てのタブバーまたはナビゲーションバーをビューの外へスライドし、基礎となるビューを新しい向きにし、バーを元の位置にスライドします。

ビューの自動リサイズプロパティが既に設定されいる場合、この動作は基本的に自動で行われます。

回転動作をカスタマイズしたい場合は、アニメーションを追加指定して実行することもできます。

ビューコントローラは、ほとんどのiPhoneアプリケーション設計の基礎となります。

以下の項で、UIViewControllerクラスのメソッドとプロパティの使い方について、基本的な情報を提供します。

アプリケーションのユーザインターフェイスの構築と管理でのビューコントローラの使い方についての詳細は、『iOS View Controllerプログラミングガイド』を参照してください。


他のビューコントローラを伴ったビューコントローラの使用

ビューコントローラは単独で動作することはほとんどありません。

アプリケーションでナビゲーションやタブバーコントローラ、またはモーダルビューを提示する場合、通常それらナビゲーション機能の実装には、いくつかのビューコントローラが相互に作用します。

ナビゲーションコントローラのインターフェイスはUINavigationControllerオブジェクトで構成され、別のナビゲーション画面に一つ以上のカスタムビューコントローラを提供します。

ユーザがインターフェイス内の新しいアイテムを選択すると、ナビゲーションスタックに新しいビューコントローラをコードがプッシュします。

新しいビューコントローラは、それぞれ新しい画面にコンテンツの内容を表示します。

スタックからのビューコントローラの削除を管理するには、各ビューコントローラに関連付けされたナビゲーションアイテムにナビゲーションを戻します。

navigationItemプロパティを変更することによって、ビューコントローラに与えるナビゲーションアイテムを設定することができます。

タブバーコントローラのインターフェイスはUITabBarControllerオブジェクトで構成され、各タブに一つ以上のビューコントローラがあります。

tabBarItemプロパティ内のオブジェクトを変更することによって、ルートビューコントローラが各タブに表示する情報を設定することができます。

iOSでは、現在のビューコントローラからモーダルビューのコントローラを提示して、モーダルビューを表示することができます。

presentModalViewController:animated:メソッドを使ってモーダルビューを提示する場合、ビューコントローラは指定した処理方式を使ってビューの外観をアニメーションします。
(modalTransitionStyleプロパティを設定することにより、望む処理方式を指定することができます)

このメソッドは同時に、現在のビューコントローラとモーダルビューコントローラの間に親子関係を生成します。

ビューコントローラ間の関係はかなり複雑で、各ビューコントローラオブジェクトは他のビューコントローラによって管理されているかどうかを示すプロパティを持っています。

タブバーまたはナビゲーションバーインターフェイスの内部に埋め込まれている場合、ビューコントローラのtabBarControllerまたはnavigationControllerプロパティで確認することができます。

また、parentViewControllerプロパティを使って、コントローラの直接の親コントローラを見つけることができます。

複雑なナビゲーションインターフェイスの構築と、ビューコントローラとの関係についての詳細は『iOS View Controllerプログラミングガイド』を参照してください。


サブクラス化の注意

通常iPhoneアプリケーションには、通常少なくとも一つ、たいていは複数のUIViewControllerのカスタムサブクラスがあります。

iOSベースのデバイスには使用可能な画面空間が限られるため、インターフェイスは通常表示する情報を一つ以上に分割する必要があります。

UIViewControllerのサブクラスによって管理されたそれぞれ異なる画面を使ってビューを表示します。

各ビューコントローラオブジェクトのジョブは二倍です。

ビューコントローラはアプリケーションのコントローラレイヤの一部で、アプリケーションの視覚的な提示(カスタムビュー)とアプリケーションのデータモデル(カスタムオブジェクト)間の相互作用を調整する責任があるからです。

ビューコントローラはまた、ビューレイヤを構成するビューの変更を処理する責任があります。

例えばデバイスの向きをポートレートからランドスケープに回転させた時、ビューコントローラはそれに応じてビューを新しい方向に向けます。

幸いなことに、ビューレイヤの管理に必要な作業のほとんどの処理は、UIViewControllerクラスのデフォルトの動作になっています。

ビューの初期設定とデフォルトの動作は全て指定しなければなりません。

その後、ビューとデータモデル間の相互作用に焦点を当てることができます。

UIViewControllerの新しいサブクラスを定義する時、コントローラで管理するビューを指定する必要があります。

これらのビューの指定には、手動またはnibファイルを使った二つの相互に排他的な方法があります。

ビューを手動で指定する場合、loadViewメソッドの実装と、それを使用してビュープロパティにルートビューオブジェクトを割り当てる必要があります。

ビューをnibファイルを使って指定する場合、loadViewをオーバーライドせず、代わりにInterface Builderでnibファイルを生成し、initWithNibName:bundle:メソッドを使ってビューコントローラオブジェクトを初期化してください。

nibファイルを使ったビューの生成は、(プログラミングによるものとは対照的に)ビューをグラフィカルに構成でき、Interface Builderアプリケーションを使って多くの場合簡単に生成することができます。

双方の手法は最終的に同じ結果を得られますが、ビューの設定に適した生成をし、ビュープロパティを経て公開します。

重要:ビューコントローラは、ビューと関連する全てのサブビューの唯一の所有者です。
したがって、メモリ不足状態の間やビューコントローラ自身が解放された時を含め、適切な時にビューの生成と解放する責任があります。
ビューがnibファイルに格納されている場合、各ビューコントローラオブジェクトは、nibファイル内のビューの独自コピーを生成します。
手動でビューを生成する場合、決して複数のビューコントローラで同じビューコントローラを使用しないでください。

ビュー階層のビューを生成する場合、ビューの自動リサイズプロパティを常に設定する必要があります。

ビューコントローラが画面表示する時、ルートビューは通常使用可能な領域に合わせてリサイズし、ウィンドウの現在の向きやステータスバーなど他のインターフェイスの要素によって変更されます。

自動リサイズプロパティは、Interface BuilderのInspectorウィンドウを使用して、または各ビューのautoresizesSubviewsとautoresizingMaskプロパティをプログラミングで変更して構成することができます。

これらのプロパティの設定は、ビューコントローラでポートレートとランドスケープの両方をサポートする際にも重要です。

向きを変更する際、システムはこれらのプロパティを使用して、ビューの再配置とリサイズを新しい向きに自動的に適合させます。


メモリ管理

メモリはiOSの重要なリソースで、ビューコントローラは重要な時にメモリのフットプリントを減らすためのサポートを組み込みで提供します。

UIViewControllerクラスはメモリ不足の状態になると、didMemoryWarningメソッドを通していくつかの自動処理を提供し、不要なメモリの解放を行います。

iOS 3.0より前は、このメソッドがカスタムビューコントローラクラスに関連付けされた追加メモリを解放する唯一の方法でしたが、iOS 3.0以降はviewDidUnloadメソッドによって最も必要な時により適切な場所で行うことができます。

メモリ不足の警告が発生した時、UIViewControllerクラスは後で再び再読み込みあるいは再生成可能なビューを解放します。

この場合viewDidUnloadメソッドを呼び出し、nibファイルで読み込まれたオブジェクトを含め、viewDidLoadメソッドで生成されたオブジェクト、実行時に後から生成されてビュー階層に追加されたオブジェクトなど、ビュー階層で関連付けされた全てのオブジェクトの所有権を放棄する機会をコードで与えます。

通常、ビューコントローラが(プロパティまたはIBOutletキーワードを含む変数の)アウトレットを含んでいる場合、アウトレットの所有権または不要になったビューに関連付けされた全ての他のデータを、viewDidUnloadメソッドを使って放棄する必要があります。

iOSのメモリ管理の実践についての一般的な情報とガイダンスについては、『高度なメモリ管理プログラミングガイド』を参照してください。


ビューの回転の処理

UIViewControllerクラスはデフォルトでは、ポートレートモードのみビューの表示をします。

向きを追加してサポートするにはshouldAutorotateToInterfaceOrientation:メソッドをオーバーライドし、YESを返される任意の方向をサブクラスでサポートする必要があります。

ビューの自動リサイズプロパティが正しく構成されている場合、全てしなければならない可能性があります。

しかし、必要に応じて追加動作を実装するための追加するフックをUIViewControllerクラスは提供しています。

不要になったまたは他の理由によって問題が生ずる可能性があって機能を一時的にオフにするには、willRotateToInterfaceOrientation:duration:メソッドをオーバーライドして必要なアクションを実行することができます。

その後、didRotateFromInterfaceOrientation:メソッドをオーバーライドし、向きの変更が完了したら機能を再度有効にするために使用します。

向きを変更する時のアニメーションをカスタマイズするには二つの方法があります。

向きの変更には二つの手順があり、回転が開始した通知、中間、そして終了した点を使用します。

ただしiOS 3.0では、一つの手順で向きの変更を実行できるようにサポートが追加されました。

向きを変更するには一つの手順の方が古い二つの手順より処理が速いので、一般的には新しいコードを推奨します。

一つの手順で向きを変更する追加されたアニメーションは、willAnimateRotationToInterfaceOrientation:duration:メソッドをオーバーライドしてアニメーションを実行します。

古い二つの手順のメソッドは、willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:とwillAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:メソッドの一つまたは両方をオーバーライドし、アニメーションの各手順の前に設定します。

これらの手法はどちらか一つのみ選択し、その手法に関連付けられたメソッドのみをオーバーライドする必要があります。

両方の手法のメソッドをオーバーライドした場合、システムはデフォルトで一つの手順の回転メソッドを使用します。



参考文献

UIViewController Class Reference

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

商品詳細を見る

Control Fun(4)~Action SheetとAlert

2010. 08. 14
最後に伸縮画像を使ったボタンの設置と、アクションシートとアラートの表示を行います。

676

画面下部に伸縮画像を使ったボタンを置き、このボタンをタップするとアクションシートが表示されます。

677

アクションシートはユーザに選択肢を提示して、タスクの継続を続行するか取り止めるかを問い合わせします。

取り止める場合は元に戻り、継続する場合はアラートを表示します。

678

アラートは重要な変更をする場合などに、ユーザに警告するものです。


●ボタンの設置

LibraryウィンドウからRound Rect Buttonをドラッグ&ドロップでViewウィンドウに置き、X:80、Y:403、W:160、H:37とします。

InspectorウィンドウのAttributesタブを開き、後程伸縮画像を貼り付けるため『Button』の『Type』を『Custom』にします。

『Title』は『Do Something』と設定します。

気になる方は、『View』の『Drawing』でデフォルトでチェックされているClear Context Before DrawingとAutoresize Subviewsのチェックを外してください。

『Interaction』の『User Interaction Enabled』はチェックを入れたままにします。

679

680


●アウトレットとプロパティ、アクションの宣言

パーツのレイアウトが済んだので、Interface BuilderからXcodeに戻りコードの記述を行います。

まずControl_FunViewController.hを開いて、アウトレットとプロパティ、アクションを宣言します。
(太字が追加した部分)

#import <UIKit/UIKit.h>

#define kShowSegmentIndex 0

@interface Control_FunViewController : UIViewController <UIActionSheetDelegate, UIAlertViewDelegate> {
    IBOutlet UITextField *nameField;
    IBOutlet UITextField *numberField;
    IBOutlet UILabel *sliderLabel;
    IBOutlet UISwitch *leftSwitch;
    IBOutlet UISwitch *rightSwitch;
    IBOutlet UIView *switchView;
    IBOutlet UIButton *doSomethingButton;
}
@property (nonatomic, retain) UITextField *nameField;
@property (nonatomic, retain) UITextField *numberField;
@property (nonatomic, retain) UILabel *sliderLabel;
@property (nonatomic, retain) UISwitch *leftSwitch;
@property (nonatomic, retain) UISwitch *rightSwitch;
@property (nonatomic, retain) UIView *switchView;
@property (nonatomic, retain) UIButton *doSomethingButton;

- (IBAction)textFieldDoneEditing:(id)sender;
- (IBAction)backgroundClick:(id)sender;
- (IBAction)sliderChanged:(id)sender;
- (IBAction)switchChanged:(id)sender;
- (IBAction)toggleShowHide:(id)sender;
- (IBAction)doSomething:(id)sender;

@end

681

アクションシートとアラートを実装するため、UIActionSheetDelegateプロトコルとUIAlertViewDelegateプロトコルに準拠させます。

UIAlertViewDelegateプロトコルに関しては本書中で触れられていませんし、無くてもエラー無くコンパイルも実行もできるのですが、念のため入れておきます。
(詳細は『メモ帳の作成(6)~アラート』を参照してください)

アウトレットは先程追加したボタン用にdoSomethingButtonを用意し、同名のプロパティとアクションdoSomething:も作ります。


●プロパティとアクションの実装

Control_FunViewController.mを開き、宣言したプロパティとアクションを実装します。
(太字が追加した部分)

#import "Control_FunViewController.h"

@implementation Control_FunViewController

@synthesize nameField;
@synthesize numberField;
@synthesize sliderLabel;
@synthesize leftSwitch;
@synthesize rightSwitch;
@synthesize switchView;
@synthesize doSomethingButton;

- (IBAction)doSomething:(id)sender {
    UIActionSheet *actionSheet = [[UIActionSheet alloc]
        initWithTitle:@"Are you Sure?"
        delegate:self
        cancelButtonTitle:@"No way!"
        destructiveButtonTitle:@"Yes, I'm Sure!"
        otherButtonTitles:nil];
    [actionSheet showInView:self.view];
    [actionSheet release];
}

- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
    if (!buttonIndex == [actionSheet cancelButtonIndex]) {
        NSString *msg = nil;

        if (nameField.text.length > 0)
            msg = [[NSString alloc]
                initWithFormat:@"You can breathe easy, %@, everything went OK.", nameField.text];
        else
            msg = @"You can breathe easy, everything went OK.";

        UIAlertView *alert = [[UIAlertView alloc]
            initWithTitle:@"Something was done"
            message:msg
            delegate:self
            cancelButtonTitle:@"Phew"
            otherButtonTitles:nil];
        [alert show];
        [alert release];
        [msg release];
    }
}

...

682

doSomething:メソッドは、Do Somethingボタンがタップされた際にアクションシートを呼び出すメソッドです。

initWithTitle:delegate:cancelButtonTitle:destructiveButtonTitle:otherButtonTitles:メソッドでアクションシートの初期化を行い、showInView:メソッドで表示します。
(両メソッドについての詳細は『メモ帳の作成(5)~アクションシート』を参照してください)

アクションシートは表示後にシステムに管理が移行するので、直後に解放しています。

actionSheet:didDismissWithButtonIndex:は、ボタンをタップしてアクションシートを画面から撤去した後に呼び出されるメソッドです。

最初にタップされたのがキャンセルボタンかどうかをcancelButtonIndexプロパティを使って判別し、キャンセルボタンであればそのままスルーし、そうでない場合は以降の処理を行います。

キャンセルボタンでない場合、まずアラートに表示するメッセージ文字列を生成します。

メッセージに使用する文字列msgは、Nameのテキストフィールドに文字が入力されているかどうかを判別し、入力されていればメッセージの内容にテキストフィールドの文字列を組み込み、そうでなければ用意した文字列を設定します。

続いて生成したメッセージ文字列を使って、アラートをinitWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:メソッドで初期化します。

showメソッドでアラートを表示した後、アラートもシステムに管理が移行するので直後に解放し、メッセージ文字列も解放します。
(両メソッドについての詳細は『メモ帳の作成(6)~アラート』を参照してください)


・actionSheet:didDismissWithButtonIndex:
(UIActionSheetDelegateプロトコル)

- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex

画面からアクションシートを撤去した後でデリゲートに送信します。

このメソッドは、アニメーションが終了し、ビューが非表示になった後で呼び出されます。

actionSheet:撤去されるアクションシートを指定します。

buttonIndex:クリックされたボタンのインデックスです。
ボタンを示す値は0から始まります。
キャンセルボタンのインデックスの場合、アクションシートはキャンセルされます。
-1の場合、キャンセルボタンのインデックスは設定されません。


・cancelButtonIndex
(UIActionSheetクラス)

@property(nonatomic) NSInteger cancelButtonIndex

キャンセルボタンのインデックス番号です。

ボタンを示す値は0から始まります。

プロパティのデフォルトの値は通常-1で、キャンセルボタンが設定されていないことを示します。

しかしinitWithTitle:delegate:cancelButtonTitle:destructiveButtonTitle:otherButtonTitles:メソッドは、自動的にキャンセルボタンの生成と設定を行います。

上記メソッドを使ってキャンセルボタンの生成をする場合は、このプロパティの値を変更しないでください。

iPadでアクションシートを提示する場合、キャンセルボタンを含めることはできません。

キャンセルボタンを含める場合の詳細は、クラスの概要(『メモ帳の作成(5)~アクションシート』)や『iOSヒューマンインターフェイスガイドライン』を参照してください。


●アウトレットとアクションの接続

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

『Outlets』の『doSomethingButton』と『Received Actions』の『doSomething:』をViewウィンドウのDo Somethingボタンに接続します。
(アクションの接続では『Touch Up Inside』を選択します)

683

この状態でも機能上は動作しますが、Do Somethingボタンが背景の無い状態ですので、伸縮画像を使って背景画像を設定します。

684


●ボタンへの伸縮画像の設定

今回使用するボタンの背景画像は、本書中で紹介されているiOS Reference Libraryのサンプルコード『UICatalog』のものを利用します。
Apress社のサイト内にサンプルコードの『Beginning iPhone Development Projects Oct 12 2009/04 Control Fun』にも同じ画像ファイルはあります)

UICatalogの場合、『Download Sample Code』ボタンを押してダウンロードし、生成されるUICatalogフォルダ下のimagesフォルダにあるblueButton.pngとwhiteButton.pngを、自身のControl Funプロジェクトのフォルダにコピーし、XcodeのResourcesに追加します。

使用する画像ファイルをプロジェクトに追加しましたら、Control_FunViewController.mを開き、コメントアウトされているviewDidLoadメソッドを有効にします。
(太字が追加した部分)

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *buttonImageNormal = [UIImage imageNamed:@"whiteButton.png"];
    UIImage *strechableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    [doSomethingButton setBackgroundImage:strechableButtonImageNormal forState:UIControlStateNormal];
    UIImage *buttonImagePressed = [UIImage imageNamed:@"blueButton.png"];
    UIImage *strechableButtonImagePressed = [buttonImagePressed stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    [doSomethingButton setBackgroundImage:strechableButtonImagePressed forState:UIControlStateHighlighted];
}

685

viewDidLoadメソッドには、nibファイルをメモリに読み込んだ(展開した)後に追加する処理を記述します。

本書では触れられていませんが、自動で記述されているスーパークラスによる初期化はそのまま残しておきます。

imageNamed:メソッドで指定したファイル名の画像を読み込み、stretchableImageWithLeftCapWidth:topCapHeight:メソッドで伸縮画像を生成して、setBackgroundImage:forState:で通常時および強調表示時のボタンの背景画像として設定します。
(3つのメソッドの詳細は『スライドショーのメソッド(1)』『スライドショーのメソッド(2)』を参照してください)

保存してビルドと実行を行い動作を確認してください。

686

・viewDidLoad
(UIViewControllerクラス)

- (void)viewDidLoad

コントローラのビューをメモリに読み込んだ後で呼び出されます。

このメソッドは、ビューコントローラの持つ関連付けされたビューをメモリに読み込んだ後で呼び出されます。

このメソッドは、ビューがnibファイルに格納されている、またはloadViewメソッドでプログラムによって生成されるかどうかに関係なく呼び出されます。

このメソッドは、nibファイルから読み込んだビューに初期化行程を追加する際に、もっとも一般的に使用されます。



参考文献

UIActionSheetDelegate Protocol Reference

UIActionSheet Class Reference

UIViewController Class Reference

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

商品詳細を見る

Control Fun(3)~SwitchとSegmented Control

2010. 08. 13
続いてセグメンテッドコントロールとスイッチを設置します。

674

セグメンテッドコントロールには2つのボタンがあり、その下に配置するスイッチを2個配したビューの表示/非表示を切り替えます。

ビュー上の2つのスイッチは連動しており、片方を操作するともう一方も追従します。


●サンプルコードと旧版本との違い

Apress社のサイト内にサンプルコードの『Beginning iPhone Development Projects Oct 12 2009/04 Control Fun』は、手元にある旧版『はじめてのiPhoneプログラミング』とレイアウトが異なります。

665

サンプルコードでは、セグメンテッドコントロールのボタンが、スイッチを配したビューの表示/非表示ではなく、スイッチと(後述するアクションシートを呼び出す)ボタンとの表示/非表示の切り替えになっています。

ここでは旧版本の内容を説明し、サンプルコードの内容は後日別途解説します。


●セグメンテッドコントロールとスイッチの設置

セグメンテッドコントロールやスイッチを設置するにあたり、本書のP.89の図にはP.63にあるようなアクションシートを呼び出すボタンが考慮されていません。

したがって手順を参考にレイアウトはそれっぽく決めます。
(頼りのサンプルコードは前述の通り異なりますので)

まずLibraryウィンドウからSegmented Controlをドラッグ&ドロップでViewウィンドウに置き、X:20、Y:244、W:280、H:44とします。

Viewウィンドウのテキスト部分をダブルクリックし、左側のSegment 0を『First』から『Show』に、右側のSegment 1を『Second』から『Hidden』に変更します。

気になる方は、InspectorウィンドウのAttributesタブで、『View』の『Drawing』でデフォルトでチェックされているClear Context Before DrawingとAutoresize Subviewsのチェックを外してください。

666

次にセグメンテッドコントロールで表示/非表示を切り替えるビューを設置します。

このビューの上にサブビューとしてスイッチ(とラベル)を配することで、複数のオブジェクトを一度に表示制御できます。

LibraryウィンドウからViewをドラッグ&ドロップでViewウィンドウに置き、X:20、Y:295、W:280、H:100とします。
(間違ってImage Viewを置くと、その上にスイッチが置けなくて悩むので注意してください)

View領域を明示するため、InspectorウィンドウのAttributesタブで、『View』の『Background』を『Light Gray Color』にします。
(逆に『Clear Color』に設定すれば、View領域を意識させなくできます)

気になる方は、『Drawing』でデフォルトでチェックされているClear Context Before DrawingとAutoresize Subviewsのチェックを外してください。

『Opaque』のチェックは入れたままにします。
(『Opaque』のチェックが入っていても、『Background』で『Clear Color』を選択した場合には、ちゃんと透明化されます)

『Interaction』の『User Interaction Enabled』はチェックを入れたままにします。
(Viewそのものは操作しませんが、サブビューとしてスイッチを配する際に継承されるため)

667

次にViewの上にLabelとSwitchを2個ずつドラッグ&ドロップします。

各パーツの位置とサイズは下表の通りです。

 XYWH
Label (Left)46244221
Label (Right)192244221
Switch (Left)20539427
Switch (Right)166539427

ラベルの属性はInspectorウィンドウのAttributesタブで、『Label』の『Text』をそれぞれ『Left』と『Right』に、『Layout』の『Alignment』を中央揃えにします。

気になる方は、『View』の『Drawing』でデフォルトでチェックされているClear Context Before Drawing、Clip Subviews、Autoresize Subviewsのチェックを外してください。

668

スイッチの属性は特に設定する項目は無く、強いて言えば識別を容易にする為に、DocumentウィンドウのNameを修正するくらいです。

気になる方は、『View』の『Drawing』でデフォルトでチェックされているClear Context Before DrawingとAutoresize Subviewsのチェックを外してください。

『Interaction』の『User Interaction Enabled』はチェックを入れたままにします。

669

Documentウィンドウでは下図のような構成になります。

670


●アウトレットとプロパティ、アクションの宣言

パーツのレイアウトが済んだので、Interface BuilderからXcodeに戻りコードの記述を行います。

まずControl_FunViewController.hを開いて、アウトレットとプロパティ、アクションを宣言します。
(太字が追加した部分)

#import <UIKit/UIKit.h>

#define kShowSegmentIndex 0

@interface Control_FunViewController : UIViewController {
    IBOutlet UITextField *nameField;
    IBOutlet UITextField *numberField;
    IBOutlet UILabel *sliderLabel;
    IBOutlet UISwitch *leftSwitch;
    IBOutlet UISwitch *rightSwitch;
    IBOutlet UIView *switchView;
}
@property (nonatomic, retain) UITextField *nameField;
@property (nonatomic, retain) UITextField *numberField;
@property (nonatomic, retain) UILabel *sliderLabel;
@property (nonatomic, retain) UISwitch *leftSwitch;
@property (nonatomic, retain) UISwitch *rightSwitch;
@property (nonatomic, retain) UIView *switchView;

- (IBAction)textFieldDoneEditing:(id)sender;
- (IBAction)backgroundClick:(id)sender;
- (IBAction)sliderChanged:(id)sender;
- (IBAction)switchChanged:(id)sender;
- (IBAction)toggleShowHide:(id)sender;

@end

671

最初に#defineで、セグメンテッドコントロールの定数kShowSegmentIndexを設定しています。

この定数はビューの表示/非表示を行うアクションメソッドtoggleShowHide:で使用します。

セグメンテッドコントロールは前回のスライダと同様に、アクションメソッドで選択値を取得するのでアウトレットは不要になりますので、必要なアウトレットはスイッチ2つと表示/非表示を行うビュー用の3つになります。

プロパティはアウトレットと同名のものが3つになります。

アクションは、連動して動作するスイッチ用のswitchChanged:と、ビューの表示/非表示を切り替えるセグメンテッドコントロール用のtoggleShowHide:になります。


●プロパティとアクションの実装

Control_FunViewController.mを開き、宣言したプロパティとアクションを実装します。
(太字が追加した部分)

#import "Control_FunViewController.h"

@implementation Control_FunViewController

@synthesize nameField;
@synthesize numberField;
@synthesize sliderLabel;
@synthesize leftSwitch;
@synthesize rightSwitch;
@synthesize switchView;

- (IBAction)switchChanged:(id)sender {
    UISwitch *whichSwitch = (UISwitch *)sender;
    BOOL setting = whichSwitch.isOn;
    [leftSwitch setOn:setting animated:YES];
    [rightSwitch setOn:setting animated:YES];
}

- (IBAction)toggleShowHide:(id)sender {
    UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
    NSInteger segment = segmentedControl.selectedSegmentIndex;

    if (segment == kShowSegmentIndex) [switchView setHidden:NO];
    else [switchView setHidden:YES];
}

...

672

switchChanged:メソッドは、ビュー上にある2つのスイッチの内、一方を操作するともう一方も連動させるメソッドです。

前回のスライダ同様、引数のsenderをUISwitchでキャストし、onプロパティでスイッチがONかOFFかを取得します。

そして、取得したプロパティ値で両スイッチを設定し直しています。

普通に考えれば条件分岐を使いそうなところですが、今回のような場合ですと条件分岐で個別に設定するより両方に設定した方が、コードの記述量も減りますし処理が速くなるとのことです。
(『こういうやり方もある』というtipsは、初心者には地味に嬉しいですね)

toggleShowHide:メソッドは、セグメンテッドコントロールの値を取得してビューの表示/非表示を切り替えるメソッドです。

引数のsenderをUISegmentedControlでキャストし、selectedSegmentIndexプロパティで選択されたボタンの値を取得します。

その値をkShowSegmentIndex定数で比較し、hiddenプロパティでビューの表示/非表示を設定します。

setHidden:メソッドはアクセサメソッドの命名規則で、BOOL値のプロパティを表しています。

この場合UIViewのhiddenプロパティで、ゲッタがisHidden、セッタがsetHiddenとなります。
(詳細は『Cocoa Core Competencies:Accessor method』を参照してください)


・on
(UISwitchクラス)

@property(nonatomic, getter=isOn) BOOL on

スイッチのON/OFF状態を決定するブール値です。

このプロパティは、UISwitchオブジェクトがONまたはOFFかどうかを、取得または(アニメーションを除く)値の設定を行います。


・setOn:animated:
(UISwitchクラス)

- (void)setOn:(BOOL)on animated:(BOOL)animated

スイッチをONまたはOFFの状態に設定し、オプションで遷移アニメーションを指定できます。

(※上手く訳せなかったので原文を掲載します)
Setting the switch to either position does not result in an action message being sent.

on:YESの場合、スイッチはONの位置に、NOの場合OFFの位置に反転します。
スイッチが既に指定された位置の場合は何も起こりません。

animated:YESではスイッチの反転アニメーションを行い、NOではアニメーションしません。


・hidden
(UIViewクラス)

@property(nonatomic, getter=isHidden) BOOL hidden

レシーバを非表示にするかを決定するブール値です。

YESの場合はレシーバが非表示に、そうでない場合はNOになります。

デフォルト値はNOです。

ビューを非表示にするとウィンドウからは見えなくなり、入力イベントに応答できなくなります。

しかしスーパービューのサブビューリストには残っており、自動リサイズにも対応します。

ビューを非表示にすると、それらが持つサブビューとすべてのビューの子孫を隠す効果があります。

この効果は暗黙的で、レシーバの子孫の非表示状態を変更しません。

ウィンドウの現在のファーストレスポンダのビューが非表示になると、次に有効なキービューが新しいファーストレスポンダになります。

このプロパティの値はレシーバの状態のみに反映され、ビュー階層内のレシーバの先祖の状態には反映されません。

したがって、先祖が非表示になっているために非表示になっているレシーバは、このプロパティをNOにすることができます。


●アウトレットとアクションの接続

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

アウトレットとアクションを以下のように接続します。

・Outlets

leftSwitch → 左スイッチ
rightSwitch → 右スイッチ
switchView → スイッチの親ビュー

・Received Actions

switchChanged: → 両スイッチ
toggleShowHide: → セグメンテッドコントロール

673

接続が完了しましたら、保存してビルドと実行を行い動作を確認してください。

674

675


●UISwitchクラス

UISwitchクラスは、機内モードなどのサービス設定などで使用されるON/OFFボタンの生成と管理に使用します。

これらのオブジェクトはスイッチとして知られています。

UISwitchクラスは、ON/OFF状態を制御するプロパティとメソッドを宣言します。

UISliderのように、ユーザがスイッチ制御を操作(反転)するとUIControlEventValueChangedイベントが生成され、その制御の結果が(適切に構成されていれば)アクションメッセージとして送信されます。

UISwitchクラスはカスタマイズできません。



参考文献

UISwitch Class Reference

UISegmentedControl Class Reference

UIView Class Reference

Cocoa Core Competencies:Accessor method

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

商品詳細を見る

Control Fun(2)~SliderとLabel

2010. 08. 13
続いてスライダと連動して値を表示するラベルを設置します。

664

●スライダとラベルの設置

LibraryウィンドウからLabelとSliderをドラッグ&ドロップでViewウィンドウに置きます。

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

 XYWH
Label (Slider)202152921
Slider5521424723

ラベルの属性は、InspectorウィンドウのAttributesタブで『Label』の『Text』をスライダの初期値として『50』とするだけです。

気になる方は、『View』の『Drawing』でデフォルトでチェックされているClear Context Before Drawing、Clip Subviews、Autoresize Subviewsのチェックを外してください。

ラベルの値はスライダの操作に連動して変化しますが、ラベル自体を操作するわけではないので『Interaction』の『User Interaction Enabled』をチェックする必要はありません。

658

スライダは、最小値と最大値、そして初期値を設定する必要があります。

今回は1.00~100.00の範囲で初期値を50.00とします。

InspectorウィンドウのAttributesタブで『Slider』の『Values』を、Minimum:1.00、Maximum:100.00、Initial:50.00とします。

気になる方は、『View』の『Drawing』でデフォルトでチェックされているClear Context Before DrawingとAutoresize Subviewsのチェックを外してください。

ラベルと同様にOpaqueにチェックを入れると、透過部分が黒くなるのでチェックは入れないでください。

660

スライダは操作するので『Interaction』の『User Interaction Enabled』はチェックされたままにしておきます。

659


●アウトレットとプロパティ、アクションの宣言

パーツのレイアウトが済んだので、Interface BuilderからXcodeに戻りコードの記述を行います。

まずControl_FunViewController.hを開いて、アウトレットとプロパティ、アクションを宣言します。
(太字が追加した部分)

#import <UIKit/UIKit.h>

@interface Control_FunViewController : UIViewController {
    IBOutlet UITextField *nameField;
    IBOutlet UITextField *numberField;
    IBOutlet UILabel *sliderLabel;
}
@property (nonatomic, retain) UITextField *nameField;
@property (nonatomic, retain) UITextField *numberField;
@property (nonatomic, retain) UILabel *sliderLabel;

- (IBAction)textFieldDoneEditing:(id)sender;
- (IBAction)backgroundClick:(id)sender;
- (IBAction)sliderChanged:(id)sender;

@end

661

アウトレットはラベル用のsliderLabelだけで、スライダ用には宣言しません。

スライダの値の取得はアクションで行うので、アウトレットは不要だからです。

プロパティはラベル用のアウトレットと同名のものを用意します。

アクションはスライダの値を取得するsliderChanged:メソッドになります。


●プロパティとアクションの実装

Control_FunViewController.mを開き、宣言したプロパティとアクションを実装します。
(太字が追加した部分)

#import "Control_FunViewController.h"

@implementation Control_FunViewController

@synthesize nameField;
@synthesize numberField;
@synthesize sliderLabel;

- (IBAction)sliderChanged:(id)sender {
    UISlider *slider = (UISlider *)sender;
    int progressAsInt = (int)(slider.value + 0.5f);
    NSString *newText = [[NSString alloc] initWithFormat:@"%d", progressAsInt];
    sliderLabel.text = newText;
    [newText release];
}

...

662

sliderChanged:アクションメソッドは、最初に引数のsenderをUISliderクラスの変数sliderにキャストしています。

その引数からUISliderクラスのvalueプロパティでスライダの値を取得しています。
(valueプロパティの詳細は『スライドショーのメソッド(2)』を参照してください)

取得した値valueはfloatなので、整数に丸めるために0.5を加算して、intにキャストしています。

得られたint値progressAsIntを、ラベル表示用の文字列newTextに変換し、textプロパティでラベルのアウトレットsliderLabelに設定します。

最後にメソッド内で生成したNSStringオブジェクトのnewTextを解放します。


・text
(UILabelクラス)

@property(nonatomic, copy) NSString *text

ラベルで表示するテキストです。

この文字列はデフォルトでnilになっています。


●アウトレットとアクションの接続

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

『Outlets』の『SliderLabel』はラベルに接続し、『Received Actions』の『sliderChanged:』はスライダに接続してValue Changedを選択します。

663

接続が完了しましたら、保存してビルドと実行を行い動作を確認してください。

664


●UILabelクラス

UILabelクラスは、読み込み専用のテキストビューを実装します。

このクラスは一行または複数行の静的テキストの描画に使用することができ、ユーザインターフェイスで他のパーツとの識別などに使われます。

基本的にUILabelクラスは、陰影を使用したり強調表示を含む、テキストの外観の制御を提供します。

必要があれば、サブクラス化でテキストの外観を更にカスタマイズすることができます。

UILabelクラスのデフォルトのコンテンツモードはUIViewContentModeRedrawです。

このモードは、矩形領域が変更される度にコンテンツを再描画します。

このモードは、クラスの継承されたcontentModeプロパティで変更することができます。

新規に生成されたラベルオブジェクトは、デフォルトでユーザイベントを無視する設定になっています。

UILabelのカスタムサブクラスでイベント処理を行いたい場合は、オブジェクトを初期化した後に、明示的にuserInteractionEnabledプロパティの値をYESに変更する必要があります。



参考文献

UILabel Class Reference

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

商品詳細を見る






Lifestyle 650 home entertainment system
Calendar
07 | 2010/08 | 09
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 31 - - - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

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

Wish List
WACOM


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

Lifestyle 650 home entertainment system
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