CATiledLayer
は表示時にデフォルトでフェードインエフェクトがかかるが CATiledLayer
のサブクラスを作って以下のクラスメソッドをオーバーライドして 0 秒を返してやる事でフェードインしないようにする事が出来る。
+ (CFTimeInterval)fadeDuration;
実装は以下
あとは通常通り自前の描画処理を書くだけ。
CATiledLayer
は表示時にデフォルトでフェードインエフェクトがかかるが CATiledLayer
のサブクラスを作って以下のクラスメソッドをオーバーライドして 0 秒を返してやる事でフェードインしないようにする事が出来る。
+ (CFTimeInterval)fadeDuration;
実装は以下
あとは通常通り自前の描画処理を書くだけ。
撮影は UIImagePickerController
にお任せして撮影後のデータを Photos で保存する実装方法。
まず UIImagePickerController
に sourceType
でカメラ撮影を指定。
デフォルトのままだと写真撮影しかできないので、カメラ撮影で利用可能な形式を全て設定して動画撮影もできる様に mediaTypes
を設定する。
これで写真と動画の撮影はOK
UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.sourceType = sourceType; picker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType]; picker.delegate = self; [self presentViewController:picker animated:YES completion:nil];
撮影したデータの取得は UIImagePickerControllerDelegate
のデリゲートメソッドで行う
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
写真撮影時は info に UIImagePickerControllerOriginalImage
キーで UIImage
で画像データが入ってきて、動画撮影時には UIImagePickerControllerMediaURL
に NSURL
で動画ファイルのURLが入ってくるという違いがあるので撮影方法によって条件分岐して処理する必要がある。
どちらで撮影されたかは info から UIImagePickerControllerMediaType
キーで取得して判定できる。
NSString *mediaType = info[UIImagePickerControllerMediaType]; if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) { } else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) { }
なおここの kUTTypeImage
と kUTTypeMovie
のキーを使うために MobileCoreServices.framework も import しておく必要がある。
分岐の後、画像であれば PHAssetChangeRequest
の
+ (instancetype)creationRequestForAssetFromImage:(UIImage *)image;
動画ファイルのURLであれば
+ (nullable instancetype)creationRequestForAssetFromVideoAtFileURL:(NSURL *)fileURL;
これらのメソッドを使ってライブラリへのデータ保存を行う。
PHAssetChangeRequest
は若干変わった利用方法になっていて、以下のように PHPhotoLibrary
の変更を行うブロック内でこのインスタンスを作成すると PHPhotoLibrary
が勝手に保存処理を行ってくれる。
目に見えて解りやすい save, write などの直接的なメソッド呼び出しでの保存ではないのでちょっと注意。
PHPhotoLibrary *library = [PHPhotoLibrary sharedPhotoLibrary]; [library performChanges:^{ [PHAssetChangeRequest creationRequestForAssetFromImage:image]; } completionHandler:^(BOOL success, NSError * _Nullable error) { }];
コード全体は以下のようになる
watchOS 1 から watchOS 2 への移行は、Xcode のプロジェクトを選択して
Editor > Validate Settings...
を選択して Xcode にお任せてプロジェクト更新するのが良い。
でもなぜかこの時に watchOS の移行についての項目が出てこなくてプロジェクトがうまくアップデートされない事があった。
その際 Xcode プロジェクト hoge.xcodeproj のパッケージを開いて中の project.pbxproj ファイルを直接エディタで開き、
以下のように記載されているところを
productType = "com.apple.product-type.application.watchapp";
productType = "com.apple.product-type.watchkit-extension";
それぞれ以下のように変更したら Xcode がちゃんと watchOS 2 のターゲットだと認識してうまくビルドできるようになった。
productType = "com.apple.product-type.application.watchapp2";
productType = "com.apple.product-type.watchkit2-extension";
あまり無いケースだと思うけど、もし同様の問題が起きたら上記の様な事はしないで一旦 git でプロジェクトを元の状態に戻してキャッシュを消すなり Xcode を起動し直すなりして正しく Validate Settings
が完了する様リトライした方が良い(と後から思った)。
参考程度に。
自作Farmework内で watchOS で利用できないAPIを使っていると not available on watchOS
のエラーが出て watchOS 向けにビルドできない。
Apple純正の Framework と同じようにヘッダのメソッド定義に __WATCHOS_PROHIBITED
を付けて watchOS では使えません宣言をすると ifdef を使ったりせずメソッドをそのままビルド可能になる。
// .h - (void)test:(EKEventStore *)eventStore; // .m - (void)test:(EKEventStore *)eventStore { [eventStore commit:nil]; // 'commit:' is unavailable: not available on watchOS }
// .h - (void)test:(EKEventStore *)eventStore __WATCHOS_PROHIBITED; // .m - (void)test:(EKEventStore *)eventStore { [eventStore commit:nil]; }
クラス自体を watchOS で使えないように宣言する場合はクラス宣言の @interface の前にこれを付けると良い
__WATCHOS_PROHIBITED @interface MyClass : NSObject
NSString
で表される文字列の真偽値を判定するには boolValue
メソッドが使える。
@property (readonly) BOOL boolValue NS_AVAILABLE(10_5, 2_0);
数字、YES/NO、true/false などの文字列を boolValue
メソッドで判定できる
NSLog(@"%d", @"1".boolValue); // 1 NSLog(@"%d", @"0".boolValue); // 0 NSLog(@"%d", @"YES".boolValue); // 1 NSLog(@"%d", @"NO".boolValue); // 0 NSLog(@"%d", @"true".boolValue); // 1 NSLog(@"%d", @"false".boolValue); // 0
ドキュメントによれば最初の文字が Y
, y
, T
, t
または1~9の数字だとYESを返すとの事。
よって以下は全てYES
NSLog(@"%d", @"Y".boolValue); // 1 NSLog(@"%d", @"y".boolValue); // 1 NSLog(@"%d", @"T".boolValue); // 1 NSLog(@"%d", @"t".boolValue); // 1 NSLog(@"%d", @"1".boolValue); // 1 NSLog(@"%d", @"9".boolValue); // 1
以下のような文字列も最初の1文字で評価されるので全てYES
NSLog(@"%d", @"Yabc".boolValue); // 1 NSLog(@"%d", @"yNO".boolValue); // 1 NSLog(@"%d", @"tfalse".boolValue); // 1
また先頭文字列のスペースは無視される、
文字列が数字の場合、先頭文字列の0の連続は無視される、0の連続の前に置かれる+
と-
の記号は無視されるという仕様があるので以下のケースも全てYESになる。
// スペース無視 NSLog(@"%d", @" y".boolValue); // 1 // 先頭の0の連続は無視 NSLog(@"%d", @"001".boolValue); // 1 // 数字の前の+と-は無視 NSLog(@"%d", @"+1".boolValue); // 1 NSLog(@"%d", @"-1".boolValue); // 1 // 数字の前の+と-と0の連続は無視 NSLog(@"%d", @"+01".boolValue); // 1 NSLog(@"%d", @"-001".boolValue); // 1
以下のケースは全てNOになる。
// 先頭文字列がスペースではない NSLog(@"%d", @"- 1".boolValue); // 0 NSLog(@"%d", @"- t".boolValue); // 0 // 文字列が数字ではないので0の連続は無視されない NSLog(@"%d", @"00y".boolValue); // 0 NSLog(@"%d", @"00t".boolValue); // 0 // 数字の文字列の前の+と-の記号を無視するのは1つまで NSLog(@"%d", @"++001".boolValue); // 0 NSLog(@"%d", @"++1".boolValue); // 0
LAContext
の以下のAPI
- (void)evaluatePolicy:(LAPolicy)policy localizedReason:(NSString *)localizedReason reply:(void(^)(BOOL success, NSError * __nullable error))reply;
このAPIで指紋認証の LAPolicyDeviceOwnerAuthenticationWithBiometrics
を使ってみて実際に遭遇したエラー集
LocalAuthentication.framework の LAError.h に書かれているエラーの定義も合わせて確認すると良いかも。
Error Domain=com.apple.LocalAuthentication Code=-1 "Application retry limit exceeded." UserInfo={NSLocalizedDescription=Application retry limit exceeded.
Touch IDの認証失敗を繰り返して制限回数に達した時
LAPolicyDeviceOwnerAuthentication でパスコード認証中にキャンセル Error Domain=com.apple.LocalAuthentication Code=-2 "Canceled by user." UserInfo={NSLocalizedDescription=Canceled by user.}
Touch IDのシステムアラートを表示中にホームボタンを押す
またはTouch IDのシステムアラートの「キャンセル」ボタンを押した時
Error Domain=com.apple.LocalAuthentication Code=-3 "Fallback authentication mechanism selected." UserInfo={NSLocalizedDescription=Fallback authentication mechanism selected.}
Touch IDのシステムアラートを表示中に「パスコードを入力」ボタンを押した時
Error Domain=com.apple.LocalAuthentication Code=-4 "Canceled by another authentication." UserInfo={NSLocalizedDescription=Canceled by another authentication.}
Touch IDのシステムアラートを表示中に再度APIでTouch IDを表示しようとした時
Error Domain=com.apple.LocalAuthentication Code=-4 "Caller moved to background." UserInfo={NSLocalizedDescription=Caller moved to background.}
Touch IDのシステムアラートをAPIで表示するのと同時にホームボタンを押してホーム画面を表示した時
Error Domain=com.apple.LocalAuthentication Code=-4 "UI canceled by system." UserInfo={NSLocalizedDescription=UI canceled by system.}
Touch IDのシステムアラートを表示中に端末をロックした時
Error Domain=com.apple.LocalAuthentication Code=-8 "Biometry is locked out." UserInfo={NSLocalizedDescription=Biometry is locked out.}
Touch IDの認証制限回数に達した状態でAPIからTouch IDを呼んだ時
Error Domain=com.apple.LocalAuthentication Code=-1004 "User interaction is required." UserInfo={NSLocalizedDescription=User interaction is required.}
バックグラウンドにいる時にTouch IDのAPIを呼んだ時
これは - (void)applicationDidEnterBackground:(UIApplication *)application
のタイミングでTouch IDのAPIを呼ぶとたまに起こる。
このエラーを避けるにはAPIの呼び出しは - (void)applicationDidBecomeActive:(UIApplication *)application
で行いつつ、2重にAPIコールをしてしまわないようにAPIの呼び出し時に使った LAContext
のインスタンスを reply ブロックが呼ばれるまで保持したりして状態管理すると良い。
iOS, macOS でプログラム内でUUIDを使う場合 CFUUID
か NSUUID
が使える。
プログラム外で識別子としてUUIDが欲しい時は uuidgen
というコマンドが用意されているのでこれを使う。
ターミナルで
$uuidgen
結果は以下のように生成されたUUIDが表示される
14B78297-8560-4144-AFA4-41563A4BE71B
-hdr オプションを使うと CFUUID
を使ったヘッダ定義用のスニペットが生成される。
$uuidgen -hdr
hdr は header の略らしい。
結果は以下
// 14B78297-8560-4144-AFA4-41563A4BE71B #warning Change the macro name MYUUID below to something useful! #define MYUUID CFUUIDGetConstantUUIDWithBytes(kCFAllocatorSystemDefault, 0x14, 0xB7, 0x82, 0x97, 0x85, 0x60, 0x41, 0x44, 0xAF, 0xA4, 0x41, 0x56, 0x3A, 0x4B, 0xE7, 0x1B)
このままコピペしてソースコード内で使用できるように #define が記載されている。