Hatena::Groupxn--272ax3f

iOSアプリ開発勉強会#4

iOSアプリ開発勉強会#4

プレゼンテーション

今回の流れ

  1. 第7章: 複数のビューコントローラを持つアプリケーションを作る
  2. 第8章: 加速度センサを使う
  3. 第9章: 通知センター・デバイスの回転をハンドリングする

タブビュー

  • UITabBarControllerをwindowのrootViewControllerにする
    • これまではrootViewControllerを、Xcodeテンプレートで生成されていたのを使っていたが、今回は自前で設定する
  • さらに、rootViewControllerの下位をなすcontrollerをふたつ作る(viewControllers)
    • HypnosisViewController
    • CurrentTImeViewController

UIViewController#tabBarItem

- (id)init
{
    self = [super initWithNibName:nil bundle:nil];

    if (self) {
        UITabBarItem *tbi = [self tabBarItem]; # ココ
        [tbi setTitle:@"Hypnosis"];
    }

    return self;
}

viewDidLoadが呼ばれるタイミング

  • このNSLogが一度しか吐かれないのはなんで?(後述)
- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"Loaded the view for HypnosisViewController");
    [[self view] setBackgroundColor:[UIColor orangeColor]];
}

View Controllerのビューの作り方

設定方法は2つあります。

  • ビューをプログラムで生成する方法
  • XIBファイルを生成する方法

いつどちらの方法を選択するかは、どうやって決めたらよいでしょうか?簡単な目安として、ビューにサブビューがなければプログラムで生成し、サブビューがあればXIBファイルを生成することを推奨します。

  • HypnosisViewController: HypnosisViewだけなのでプログラム
  • CurrentTImeViewController: サブビューがあるのでXIBで

ビューを表示する流れ(現在までに出てきた分だけ)

  1. loadView (これを上書きして、HypnosisView)を表示する
    • これを上書きしていないと、デフォルトの空のビューが設定される。この前にオレンジ色にしたりしていたのは、その空ビュー
  2. viewDidLoad

Xibについて

  • File's Ownerとは?
    • placeholder object
    • XIBファイルがロードされてもplaceholder objectはinstanciateされない
    • ViewControllerは、XIBをロードすると、placeholderに自分をはめこむ

f:id:antipop:20120220202145p:image

ビュー生成の流れ

  • initWithNibName:bundle:
    • 指定したXIBファイルをロードする。nilがわたされたら、ViewControllerの名前と同じ名前のXIBをロードする
  • XIBのロードはloadViewが行うので、
    • CurrentTimeViewControllerのようにXIBを使う場合はloadViewを上書きしない
    • 一方、前述のHypnosisViewControllerのように自分でviewを設定する時は、上書きする

UIViewControllerのフックポイント

  • viewWillAppear: ビューがウインドウに追加されるとき
  • viewDidAppear: ビューがウインドウに追加されたとき
  • viewWillDisapear: ビューが消されるか、覆われるか、隠される時
  • viewDidDisappear: ビューが消されたか、覆われたか、隠された時
  • 都度処理したいロジックを書く
    • メモリ破棄したりとか
    • 表示を変更したりとか

didReceiveMemoryWarning

  • メモリ不足警告がおこると、ビューが表示中でなければ解放される
  • なので、View Controllerの初期化時(init)でviewの初期化をしていると、上記で解放されてしまった時にviewの初期化ができなくなる
  • ビューの初期化は、view controllerのviewDidLoadに書こう(これはビューのロード時に必ず呼ばれるので)
  • また、CurrentTimeViewControllerの例では、上記のようにviewが解放された時、view controllerからのtimeLabelへの参照を解放する必要がある → viewDidUnload

viewDid*系

  • viewDidLoad
  • viewDidUnload

このあたりのフックポイントは、Xcodeテンプレートが吐いてくれてる(HypnoTimeViewController.m参照)。

加速度センサを使う

f:id:antipop:20120219101354p:image

http://japan.internet.com/developer/20100803/26.html より

  • UIAccelerometer
    • updateIntervalの設定
    • delegateを設定
  • delegateにapplication:didFinishLaunchingWithOptions:を実装

コード

UIAccelerometer *accelerometerはreleaseしなくてよいぽい。

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    NSLog(@"Monitoring accelerometer");
    UIAccelerometer *accelerometer = [UIAccelerometer sharedAccelerometer];
    
    [accelerometer setUpdateInterval:0.1];
    [accelerometer setDelegate:self];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [[UIAccelerometer sharedAccelerometer] setDelegate:nil];
}

self.viewのキャスト

  • ここ、self.viewとってくるのにキャストせにゃならんの……(ビルドはできるけど、警告がでる)。
    • UIViewControllerの宣言: @property(nonatomic, retain) UIView *view
- (void)accelerometer:(UIAccelerometer *)accelerometer
        didAccelerate:(UIAcceleration *)acceleration
{
    NSLog(@"%f, %f, %f", [acceleration x], [acceleration y], [acceleration z]);

    HypnosisView *hv = (HypnosisView *)[self view];
    [hv setXShift:10.0  * [acceleration x]];
    [hv setYShift:-10.0 * [acceleration y]];
    [hv setNeedsDisplay];
}

ローパスフィルタ

  • unptedaIntervalが一定だけどデバイスの動きは一定でない
  • 適当に滑らかな感じにする
  • 前回までの値との加重平均
float filteringFActor = 0.1;
lowPassed = newValue * filteringFactor + lowPassed * (1.0 - filteringFactor);

ハイパスフィルタ

  • シェイクのような突発的な変化に使う
float filteringFactor = 0.1;
lowPassed  = newValue * filteringFactor + lowPassed * (1.0 - filteringFactor);
highPassed = newValue - lowPassed;

シェイク

  • UIResponder:
    • motionBegan:withEvent
    • motionEnded:withEvent
    • motionCancelled:withEvent
  • UIViewはUIResponderを継承しているので、これを加速度センサからの値をハンドリングする
  • UIVewのカスタマイズはinitWithFrameで行う
  • モーションを受けとるHypnosisViewをfirst reaponderにしなければならない

(問題発生)

  • HypnosisViewに設定したinitWithFrame:が何度も呼ばれまくった挙句、EXC_BAD_ACCESSが発生する
  • いろいろやってたらmainでSIGABRTくらうようになった。エミュレータだと大丈夫
    • 実機再起動したらSIGABRTは治った……。
    • ぐぐったら、実機再起動しろみたいな記事がけっこうある
  • HypnosisViewのinitWithFrameをコメントアウトしたら動くようになった……。なにが悪いのかわからない……。
  • と思ったら、HypnosisViewでこんなん書いてたせいだった><
- (id)initWithFrame:(CGRect)frame
{
    self = [self initWithFrame:frame];
    (以下略)
}

通知センター・デバイスの回転をハンドリングする

通知センタ: NSNotificationCenter

通知内容オブジェクト: NSNotification

  1. notification centerにobserverを登録
  2. observerはコールバックを実装
  3. 通知元は、NSNotificationオブジェクトをnotification centerにpush
  4. observerがそれを処理する

便利。

注意点

  • NSNotificationCenterはobserverをretainしない。
  • observerのdealloc時に、NSNotificationCenterからobserverの登録を解除しなければならない。
    • そうしないと、observerが解放された後でも通知を送ってきて、クラッシュ
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}

UIDeviceの通知

  • 回転
  • バッテリ状態
  • 近接センサ

回転をハンドリング

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.

    UIDevice *device = [UIDevice currentDevice];
    [device beginGeneratingDeviceOrientationNotifications];

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self
           selector:@selector(orientationChanged:)
               name:UIDeviceOrientationDidChangeNotification
             object:device];
    
}

...

- (void)orientationChanged:(NSNotification *)notification
{
    NSLog(@"orientationChanged: %d", [[notification object] orientation]);
}

自動回転

  • デバイスが回転すると…
    • ビューの縦横がかわる
    • サブビューの大きさは、autoresizing maskに応じて代わる
    • universal applicationの場合は、デバイスの大きさがかわる
  • コードからも変更できる。
    • 適当にビット演算したものを指定する。
[view setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin |
                          UIViewAutoresizingFlexibleHeight];
  • 強制横向きモードとかも可能

自動回転のカスタマイズ

  • willAnimateRotationToInterfaceOrientation:duration:をオーバーライド
    • たとえば、ボタン配置を横長画面にあわせて変えたりとか