CS193p - Lecture 16

iTunes U スタンフォード大学のiOSアプリ開発講義のLecure 16(Action Sheets, Image Picker, Core Motion) の講義メモです。前回の講義のNSTimerに関する補足とデモが最初にありました。

NSTimer

■メインキューにタイマー登録

NSTimer *timer =

[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)seconds

target:self
selector:@selector(doSomething:)
userInfo:(id)anyObject
repeats:(BOOL)yesOrNo];

 

■タイマーの停止

- (void)invalidate;

 

指定時間後に実行

■NSObjectのメソッド

- (void)performSelector:(SEL)aSelector

withObject:(id)argument
afterDelay:(NSTimeInterval)seconds;

 

・注意点

メインスレッドのランループで実行される。そのため、secondsを0に設定してもすぐに実行されるわけではない。

自身で再スケジュール可能。

ビューコントローラが画面から消えたらこのメソッドはコールされない。

<例>

[self.tableView performSelector:@selector(reloadData)

withObject:nil afterDelay:0];

 

■キャンセル

NSObjectのクラスメソッド:

+ (void)cancelPreviousPerformRequestsWithTarget:(id)target

selector:(SEL)aSelector
object:(id)object;

 

+ (void)cancelPreviousPerformRequestsWithTarget:(id)target;

 

デモ(NSTimerとperformSelector:afterDelayでリピート処理)

■NSTimerでリピート処理

- (void)startDraining
{

self.drainTimer =

[NSTimer scheduledTimerWithTimeInterval:DRAIN_DURATION/3

target:self
selector:@selector(drain:)
userInfo:nil
repeats:YES];

}

※self.drainTimerは、動作している間だけ使用するのでweakでプロパティ定義


- (void)stopDraining
{

[self.drainTimer invalidate];

}

※self.drainTimerはweakで定義しているので、nilをセットしていない

■performSelector:afterDelayでリピート処理

- (void)drip
{

if (self.kitchenSink.window) {

[self addLabel:nil];
[self performSelector:@selector(drip)

withObject:nil afterDelay:FAUCET_INTERVAL];

}

}

 

■ビュー起動時にタイマーを実行

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

 

■ビュー終了時にタイマーを終了

-(void)viewWillDisappear:(BOOL)animated
{

[self stopDraining];
[super viewWillDisappear:animated];

}

 

 

Alert

■UIActionSheet

・初期化

-(id)initWithTitle:(NSString *)title

delegate:(id <UIActionSheetDelegate>)delegate

cancelButtonTitle:(NSString *)cancelButtonTitle

destructiveButtonTitle:(NSString *)destructiveButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...;

 

・ボタンの追加

- (void)addButtonWithTitle:(NSString *)buttonTitle;

 

・表示

UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:...];

 

// iPhoneで表示(iPadでは中央に表示されてしまう)
[actionSheet showInView:(UIView *)]; //iPadでは中央に表示されてしまう

// iPadでも大丈夫

[actionSheet showFromRect:(CGRect) inView:(UIView *) animated:(BOOL)];  

// iPadでも大丈夫

[actionSheet showFromBarButtonItem:(UIBarButtonItem *) animated:(BOOL)];

 

・delegateでユーザの選択を取得

- (void)actionSheet:(UIActionSheet *)sender

clickedButtonAtIndex:(NSInteger)index;

//キャンセルボタンとデストラクティブボタン
@property NSInteger cancelButtonIndex;
@property NSInteger destructiveButtonIndex;
//その他のボタン
@property (readonly) NSInteger firstOtherButtonIndex;
@property (readonly) NSInteger numberOfButtons;
- (NSString *)buttonTitleAtIndex:(NSInteger)index;
※“other button”のindexは指定した順

・コードでアクションシートを消す方法

- (void)dismissWithClickedButtonIndex:(NSInteger)index

animated:(BOOL)animated;

※アプリがバックグラウンドに移動した時に使う。アプリがバックグラウンドに入ったかどうかは、UIApplicationDidEnterBackgroundNotificationで判断

・ポップオーバーで表示した時の注意点

a) キャンセルボタンは自動的に表示されない

    ポップオーバーでは外をタップでキャンセルされる

b) showFromBarButtonItem:animated: 使用時の注意

    ボタン連打時に複数表示されないようにハンドリングをする

    <バーボタンアイテムのハンドリング>

    UIActionSheetをポイントするプロパティはweakで持つこと。

    そして、バーボタンアイテムのアクションでは最初にそのプロパティを確認する。

nilではない場合)

すでに画面に表示されているのでそれをdismissで削除する。

nilの場合)

正常な状態なので、アクションシートの表示の準備をして表示する。

 

■UIAlertView

UIActionSheetにとてもよく似ている。

・初期化

-(id)initWithTitle:(NSString *)title

message:(NSString *)message // UIActionSheetにはない引数
delegate:(id <UIActionSheetDelegate>)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...;

 

・ボタンの追加

- (void)addButtonWithTitle:(NSString *)buttonTitle;

 

・表示

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:...];
[alertView show]; // UIActionSheetと違って、常に画面中央に表示される

 

デモ(popoverでアクションシートを表示)

UIImagePickerController

UIImagePickerControllerは、モーダルビューで、カメラもしくは写真ライブラリからメディアを取得する。

■表示方法

表示には、presentViewController:animated:completion: を使う。

※iPadでは、UIPopoverControllerを使ってポップオーバーの中に入れることができる

■使い方

  1. alloc/initで作成し、delegateを設定
  2. 取得したいものを設定(ソース、メディアの種類、ユーザが編集したものか)
  3. 表示
  4. ユーザがメディアを選択した時のdelegateメソッドに応答

■デバイス依存

デバイスによってカメラがあったりなかったり、ビデオをとれたりとれなかったりするが、

iPadでは、カメラか写真ライブラリからメディアを取得する。

※iPadは同時に両方は出来ない。iPhoneは可能。

■ソースタイプ

・ソースタイプが利用可能かどうかを確認

+ (BOOL)isSourceTypeAvailable:

(UIImagePickerControllerSourceType)sourceType;

UIImagePickerControllerSourceTypePhotoLibrary
UIImagePickerControllerSourceTypeCamera
UIImagePickerControllerSourceTypeSavedPhotosAlbum // アプリが保存した写真

・ビデオが使えるかどうかを確認

+ (NSArray *)availableMediaTypesForSourceType:

(UIImagePickerControllerSourceType)sourceType;

<戻り値(配列)>

kUTTypeImage // 全てのソースがこれを提供する
kUTTypeMovie // オーディオとビデオの両方

kUT〜は、MobileCoreServicesフレームワークなので、使う場合はこのフレームワークをリンクして、ヘッダファイルをインポートする。<MobileCoreServices/MobileCoreServices.h>

・前と後ろのどちらのカメラを使用するか

+ (BOOL)isCameraDeviceAvailable:

(UIImagePickerControllerCameraDevice)cameraDevice;

cameraDevice:

UIImagePickerControllerCameraDeviceFront
UIImagePickerControllerCameraDeviceRear

・その他のカメラの機能の確認

+ (BOOL)isFlashAvailableForCameraDevice:

(UIImagePickerControllerCameraDevice);

+ (NSArray *)availableCaptureModesForCameraDevice:

(UIImagePickerControllerCameraDevice);

 

■取得したいものを設定

・ソースとメディアの種類を設定

※UIImagePickerControllerをUIIPCで短縮表記

UIIPC *picker = [[UIIPC alloc] init];
picker.delegate = self; // UINavigationControllerDelegate のdelegateも必要

if ([UIIPC isSourceTypeAvailable:UIIPCSourceTypeCamera]) {

picker.sourceType = UIIPCSourceTypeCamera;

} // else 他に使えるものを確認(デフォルトは写真ライブラリ)

 

NSString *desired = (NSString *)kUTTypeMovie; // kUTTypeImage…
if ([[UIIPC availableMediaTypesForSourceType:picker.sourceType] containsObject:desired]) {

picker.mediaTypes = [NSArray arrayWithObject:desired];

// ピッカーで取得処理を続ける

} else {

// メディアの取得に失敗

}

※ピッカーはナビゲーションコントーラーなので、delegateは次の2つをサポートする

 UIImagePickerControllerDelegate

UINavigationControllerDelegate

※kUTTypeMovie,kUTTypeImageはCFString(Core Foundation)なので、Auto Reference Countの機能が働かない。NSStringにキャストして使う。

・編集許可

@property BOOL allowsEditing;

 

・ビデオキャプチャの制限

@property UIImagePickerControllerQualityType videoQuality;

UIImagePickerControllerQualityTypeHigh
UIImagePickerControllerQualityTypeMedium    //デフォルト
UIImagePickerControllerQualityTypeLow
UIImagePickerControllerQualityType640x480
UIImagePickerControllerQualityTypeIFrame1280x720
UIImagePickerControllerQualityTypeIFrame960x540

 

@property NSTimeInterval videoMaximumDuration;

 

■ピッカーの表示

iPadではカメラ以外からメディアを取得する場合は、ポオップオーバーで表示しなければならない。カメラから取得する場合は、ポップオーバーでもフルスクリーンでもよい。

※iPadではカメラか写真ライブラリのどちらかになり、一度に両方は出来ない。

 

■delegate

・メディアがピックアップされた時のdelegate

- (void)imagePickerController:(UIImagePickerController *)picker

didFinishPickingMediaWithInfo:(NSDictionary *)info

{

// 取得したメディアを使う処理

// ピッカーを削除

[self dismissModalViewControllerAnimated:YES]; // or popover dismissal

}

info dictionaryの内容

UIImagePickerControllerMediaType  // kUTTypeImage or kUTTypeMovie
UIImagePickerControllerOriginalImage     // UIImage
UIImagePickerControllerEditedImage       // UIImage
UIImagePickerControllerCropRect           // CGRect (NSValueで)
UIImagePickerControllerMediaMetadata  // NSDictionary
UIImagePickerControllerMediaURL          // NSURL 編集後のメディア
UIImagePickerControllerReferenceURL    // NSURL 編集前のメディア

・キャンセルボタンが押された時のdelegate

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{

[self dismissModalViewControllerAnimated:YES]; // or popover dismissal

}

 

■その他

・オーバーレイ

@property UIView *cameraOverlayView;

 

・カメラコントロールを隠す

@property BOOL showsCameraControls;

 

・イメージの変形(ズームなど)

@property CGAffineTransform cameraViewTransform;

 

デモ(カメラで撮影した写真を編集して使用)

Core Motion

Core Motionはデバイスの動きを検知しているハードウエアにアクセスするAPIである。

・主要な入力

加速度計

ジャイロスコープ

磁力計

・主なクラス

入力値を得るのにCMMotionManagerクラスを使う。

※パフォーマンスの観点から、1アプリで1つのCMMotionManagerを使う

■使い方

・都度計測値を取得する場合

1. どの計測器が利用可能かを確認

2. サンプリングを開始し、モーションマネージャに計測値を問い合わせる

・定期的に計測値を取得する場合

1. どの計測器が利用可能かを確認

2. 計測からデータを取得する頻度をセット

3. サンプリングされる毎の処理をブロックで登録する

■計測器が利用可能かを確認

@property (readonly) BOOL accelerometerAvailable;

@property (readonly) BOOL gyroAvailable;

@property (readonly) BOOL magnetometerAvailable;

@property (readonly) BOOL deviceMotionAvailable;

 

■計測開始

- (void)startAccelerometerUpdates;
- (void)startGyroUpdates;
- (void)startMagnetometerUpdates;
- (void)startDeviceMotionUpdates;

 

■計測中かどうかの確認

@property (readonly) BOOL accelerometerActive;
@property (readonly) BOOL gyroActive;
@property (readonly) BOOL magnetometerActive;
@property (readonly) BOOL deviceMotionActive;

 

■計測終了

- (void)stopAccelerometerUpdates;
- (void)stopGyroUpdates;
- (void)stopMagnetometerUpdates;
- (void)stopDeviceMotionUpdates;

 

■計測値の確認

@property (readonly) CMAccelerometerData *accelerometerData;

CMAccelerometerDataは次のプロパティを持っている

@property (readonly) CMAcceleration acceleration;

typedef struct {
    double x;
    double y;
    double z;
} CMAcceleration; // x, y, z は"g"


@property (readonly) CMGyroData *gyroData;

CMGyroDataは次のプロパティを持っている

@property (readonly) CMRotationRate rotationRate;

typedef struct {
    double x;
    double y;
    double z;
} CMRotationRate; // x, y, z はラジアン/秒


@property (readonly) CMMagnetometerData *magnetometerData;

CMMagnetometerDataは次のプロパティを持っている

@property (readonly) CMMagneticField magneticField;

typedef struct {
    double x;
    double y;
    double z;
} CMMagneticField; // x, y, z はマイクロテスラ


@property (readonly) CMDeviceMotion *deviceMotion;

CMDevieMotionはgyroとaccelerationの組み合わせ

 

■CMDDeviceMorionの加速度データ

@property (readonly) CMAcceleration gravity;
@property (readonly) CMAcceleration userAcceleration;

typedef struct {
    double x;
    double y;
    double z;
} CMAcceleration; // x, y, z は"g"


■CMDeviceMotionの回転データ

@property CMRotationRate rotationRate;

typedef struct {
    double x;
    double y;
    double z;
} CMRotationRate; // x, y, z はラジアン/秒


@property CMAttitude *attitude;

 

@interface CMAttitude : NSObject // roll, pitch, yaw はラジアン    
@property (readonly) double roll;
@property (readonly) double pitch;
@property (readonly) double yaw;
@end

 

■CMDeviceMotionの磁力データ

@property (readonly) CMCalibratedMagneticField magneticField; struct {
    CMMagneticField field;
    CMMagneticFieldCalibrationAccuracy accuracy;
} CMCalibratedMagneticField;

 

enum {

CMMagneticFieldCalibrationAccuracyUncalibrated,
Low,
Medium,
High

} CMMagneticFieldCalibrationAccuracy;

 

■加速度計からのデータ受信を登録

- (void)startAccelerometerUpdatesToQueue:(NSOperationQueue *)queue

withHandler:(CMAccelerometerHandler)handler;

typedef void (^CMAccelerationHandler)

(CMAccelerometerData *data, NSError *error);

 

■ジャイロ計からのデータ受信を登録

- (void)startGyroUpdatesToQueue:(NSOperationQueue *)queue

withHandler:(CMGyroHandler)handler;

typedef void (^CMGyroHandler)(CMGyroData *data, NSError *error)

 

■磁力計からのデータ受信を登録

- (void)startMagnetometerUpdatesToQueue:(NSOperationQueue *)queue

withHandler:(CMMagnetometerHandler)handler;

typedef void (^CMMagnetometerHandler)

(CMMagnetometerData *data, NSError *error)

■組み合わせデータの受信を登録

- (void)startDeviceMotionUpdatesToQueue:(NSOperationQueue *)queue

withHandler:(CMDeviceMotionHandler)handler;

typedef void (^CMDeviceMotionHandler)

(CMDeviceMotion *motion, NSError *error);

NSError:

CMErrorDeviceRequiresMovement
CMErrorTrueNorthNotAvailable

 

- (void)startDeviceMotionUpdatesUsingReferenceFrame:

(CMAttitudeReferenceFrame)frame
toQueue:(NSOperationQueue *)queue
withHandler:(CMDeviceMotionHandler)handler;

enum {

CMAttitudeReferenceFrameXArbitraryZVertical,
XArbitraryCorrectedZVertical, // 要磁力計。CPUパワーが必要
XMagneticZVertical, // 上記+デバイスの動き

XTrueNorthZVertical // 要 GPS+磁力計

}

@property (nonatomic) BOOL showsDeviceMovementDisplay;

 

■ブロックが実行される頻度の設定

@property NSTimeInterval accelerometerUpdateInterval;
@property NSTimeInterval gyroUpdateInterval;
@property NSTimeInterval magnetometerUpdateInterval;
@property NSTimeInterval deviceMotionUpdateInterval;

 

■複数のブロック

CMMotionManagerは1つしか使えないが、複数のブロックは登録できる。その場合は全てのブロックが同じ頻度で実行される。

<前の記事

CS193p - Lecture 15    

- iOS開発が楽しくなる小技 次の記事>