AppSettings(10)~UIViewControllerの状態通知

2011. 02. 01
メインビューとフリップサイドビューの切り替え時における、UIViewControllerの状態通知について説明します。

AppSettingsアプリケーションでは、フリップサイドビューで設定情報をSettingsアプリケーションのデフォルトオブジェクトに書き込み、メインビューで読み込みを行っています。

この際、FlipsideViewControllerはviewWillDisappear:(非表示にする時)で書き込み、MainViewControllerはViewDidAppear:(表示した時)で読み込みをしています。

読み込みのタイミングがViewWillAppear:(表示する時)にしていないのは、画面遷移における状態通知の順番によります。


●4つの状態通知の順序

UIViewControllerの状態通知メソッドには、viewWillAppear:、viewDidAppear:、viewWillDisappear:、viewDidDisappear:の4つがあります。

4つの状態通知の順番を確認するため、MainViewControllerのflipsideViewControllerDidFinish:メソッドでの、refreshFieldsの呼び出しをコメントアウトします。
(flipsideViewControllerDidFinish:メソッドが絡むと話が込み入ってくるので、後述します)
(太字が修正した部分)

- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller {
    [self dismissModalViewControllerAnimated:YES];
//  [self refreshFields];
}

1188

MainViewControllerにViewWillAppear:メソッドを追加し、viewWillAppear:とviewDidAppear:のスーパークラスによる初期化の行にブレークポイントを設定します。
(太字が追加した部分)

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self refreshFields];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self refreshFields];
}

1180

同様にFlipsideViewControllerにviewDidDisappear:メソッドを追加し、viewWillDisappear:とviewDidDisappear:のスーパークラスによる初期化の行にブレークポイントを設定します。
(太字が追加した部分)

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *prefValue = (strikerUnitSwitch.on) ? @"Equipped" : @"Released";
    [defaults setObject:prefValue forKey:kStrikerUnitKey];
    [defaults setFloat:breastsSlider.value forKey:kBreastsKey];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
}

1181

Xcodeの『実行』メニューで『デバッガ』を選択し、デバッガを表示します。

1182

『ビルドとデバッグ』を行うと、最初にメインビューを表示しようとしてviewWillAppear:で引っかかってしまいます。

1183

ここは重要でないので、直後のviewDidAppear:と共に『続ける』ボタンでスキップし、アプリケーションを起動させます。

1178

インフォメーションボタンをタップし、フリップサイドビューを表示させます。

1176

そしてナビゲーションバーのDoneボタンをタップし、フリップサイドビューからメインビューへ画面遷移を行います。

すると最初にMainViewControllerのviewWillAppear:が引っかかります。

1184

『続ける』で進めるとFlipsideViewControllerのviewWillDisappear:が引っかかります。

1185

更に『続ける』で進めると、(シミュレータの画面がメインビューに切り替わって)MainViewControllerのviewDidAppear:が引っかかります。

1186

『続ける』で進めるとFlipsideViewControllerのviewDidDisappear:が引っかかります。

1187

このように、2つの画面を切り替える際のUIViewControllerの通知順序は、

1)新しい画面を表示する通知(viewWillAppear:)
2)古い画面を非表示にする通知(viewWillDisappear:)
3)新しい画面を表示した通知(viewDidAppear:)
4)古い画面を非表示にした通知(viewDidDisappear:)

となっていることが分かります。

この状態で実行すると、(シミュレータを実行する開発環境に依存するかもしれませんが)メインビューに切り替わった一瞬後に、スイッチやスライダのラベルが書き換わるのが確認できます。

そこでviewDidAppear:でのrefreshFieldsの呼び出しをコメントアウトし、viewWillAppear:でのみrefreshFieldsの呼び出しをさせると、変更が反映されなくなります。
(ラベル書き換え後にスイッチやスライダを設定しているため)

当初、本書の説明が分かり難かったため、『iPhoneプログラミングUIKit詳解リファレンス(3.8.1 状態通知メソッド)』を参照したのですが、そこには『viewWillDisappear: → viewWillAppear: → viewDidDisappear: → viewDidAppear:』と書かれていました。

その順番なら問題にならないはずですが、実際にviewDidAppear:をviewWillAppear:に書き換えると反映されなかったので、上記のようにブレークポイントを設定して確認した次第です。

疑問に思ったら実際に試してみることが大切ですね。


viewDidAppear:

- (void)viewDidAppear:(BOOL)animated

ビューコントローラのビューが可視状態になった時に通知します。

このメソッドをオーバーライドして、ビューを提示された際に関連付けられたカスタムのタスクを実行することができます。

このメソッドをオーバーライドする場合は、実装する時点でsuperを呼び出す必要があります。

animated:YESの場合、ウィンドウにビューを追加する際にアニメーションを使います。


viewDidDisappear

- (void)viewDidDisappear:(BOOL)animated

ビューコントローラのビューが破棄されたり覆われたり、その他の方法でビューが非表示になった時に通知します。

このメソッドをオーバーライドして、ビューが削除または非表示になった時に関連する追加のタスクを実行することができます。

このメソッドをオーバーライドする場合は、実装する時点でsuperを呼び出す必要があります。

animated:YESの場合、ビューを破棄した際にアニメーションを行います。


viewWillAppear:

- (void)viewWillAppear:(BOOL)animated

ビューコントローラのビューが可視状態になる時に通知します。

このメソッドは、レシーバのビューが画面に表示される前で、ビューの表示用に構成されているアニメーションの前に呼び出されます。

このメソッドをオーバーライドして、ビューを提示する際に関連付けられたカスタムのタスクを実行することができます。

例えばこのメソッドを使って、ステータスバーの向きやスタイルに応じて、これから提示するビューの向きやスタイルを調整することができます。

このメソッドをオーバーライドする場合は、実装する時点でsuperを呼び出す必要があります。

ウィンドウにビューを追加する方法や、メッセージの発声する順序に関する詳細については、『iOS View Controllerプログラミングガイド』の『カスタムContent View Controllerの作成』内にあるビューコントローラのビューを提示する情報を参照してください。

animated:YESの場合、ウィンドウにビューを追加する際にアニメーションを使います。


viewWillDisappear:

- (void)viewWillDisappear:(BOOL)animated

ビューコントローラのビューが破棄されたり覆われたり、その他の方法でビューが非表示になる時に通知します。

このメソッドはウィンドウからビューが削除されたり、他のビューに覆われる際に呼び出されます。

このメソッドは、ビューが実際に削除や覆われたりする前で、構成されているアニメーションの前に呼び出されます。

サブクラスはこのメソッドをオーバーライドして委任を編集して変更し、ビューのファーストレスポンダ状態を破棄したり、関連する他のタスクを実行することができます。

例えばビューを最初に提示する時に、viewDidDisappear:メソッドで作成されたステータスバーの向きまたはスタイルの変更を元に戻すのに、このメソッドを使用します。

このメソッドをオーバーライドする場合は、実装する時点でsuperを呼び出す必要があります。

animated:YESの場合、ビューを破棄する際にアニメーションが始まります。


●flipsideViewControllerDidFinish:について

Utility Applicationテンプレートで自動生成されるflipsideViewControllerDidFinish:メソッドは、フリップサイドビューでナビゲーションバーのDoneボタンをタップした際に呼び出されますが、ブレークポイントを設定してみると、呼び出される順番は上記の4つの状態通知メソッドよりも早いことが分かります。

つまり、メソッドの呼び出し順序としては

1)古い画面を破棄(flipsideViewControllerDidFinish:)
2)新しい画面を表示する通知(viewWillAppear:)
3)古い画面を非表示にする通知(viewWillDisappear:)
4)新しい画面を表示した通知(viewDidAppear:)
5)古い画面を非表示にした通知(viewDidDisappear:)

となります。

しかし、最初にコメントアウトしたflipsideViewControllerDidFinish:内のrefreshFieldsの呼び出しを解除すると、メインビューに戻った際に設定情報が更新されています。

flipsideViewControllerDidFinish:メソッドはデリゲートのメソッドなので、呼び出した後はその処理の完了を待たずに次の処理が進むためです。

さらにflipsideViewControllerDidFinish:メソッドでは、refreshFieldsを呼び出す前にdismissModalViewControllerAnimated:メソッドを呼び出しているので、その処理分のラグが更新に反映されているようです。

話を整理すると、下記のようになります。
(viewWillDisappear:でのスイッチとスライダの更新は変わりません)

・viewWillAppear:でrefreshFields呼び出し

viewWillAppear:でラベル書き換え
viewWillDisappear:で情報更新
ラベルが反映されず

・viewDidAppear:でrefreshFields呼び出し

viewWillDisappear:で情報更新
画面の切り替え
viewDidAppear:でラベル書き換え
ラベルが反映される

・flipsideViewControllerDidFinish:でrefreshFields呼び出し

flipsideViewControllerDidFinish:呼び出し
viewWillDisappear: 
で情報更新
dismissModalView
ControllerAnimated:の実行

画面の切り替え
refreshFieldsで
ラベル書き換え
ラベルが反映される

※処理が平行して進むことを示しているだけでタイミングを正確に表しているものではありません。

今回の場合は、たまたま情報更新と画面切り替えの間にrefreshFieldsが呼び出されたために上手く処理されています。

試しにdismissModalViewControllerAnimated:とrefreshFieldsの呼び出しを入れ替えると、情報更新前にラベルが書き換えられてしまい、ラベルは反映されません。

開発環境やシミュレータと実機でタイミングが異なりそうなので、注意が必要です。


●画面遷移のスタイルについて

色々試している内に気付いた画面遷移のスタイルについての情報も書いておきます。

画面遷移のスタイルは、MainViewControllerのインフォメーションボタンをタップした際のアクションメソッドshowInfo:において、UIModalTransitionStyleの定数で設定されています。

4つの定数の内、iOS 3.0以降で有効な3つ(UIModalTransitionStyleCoverVerticalUIModalTransitionStyleFlipHorizontalUIModalTransitionStyleCrossDissolve)はviewWillAppear:とviewDidAppear:が呼び出されるのですが、iOS 3.2で追加されたUIModalTransitionStylePartialCurlは特殊で、viewWillAppear:とviewDidAppear:が呼び出されません。

従って、情報を反映させるにはflipsideViewControllerDidFinish:メソッドを使用することになりますので注意してください。



参考文献

UIViewController Class Reference

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

商品詳細を見る

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

商品詳細を見る






Lifestyle 650 home entertainment system
0 Comments
Leave a comment
管理者にだけ表示を許可する
Top
0 Trackbacks
Top
Calendar
10 | 2017/11 | 12
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

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