アプリ第4弾「練習カメラ」リリース
簡単操作の練習用カメラアプリができました!!
ピアノを練習していて、もっと簡単に録画再生ができないものか?といつも考えていました。iPhoneの標準のカメラを使うと「録画開始=>再生=>カメラに戻る」という一連の操作を行うには、6回の画面タップが必要です。録画の度にこれでは、練習の集中が途切れてしまいます。しかも、撮った動画がどんどん保存されてしまうので、後で消さないといけません。
そんな面倒を解決したい!というので生まれたのが「練習カメラ」。これは、同じ操作をするのに1回も画面をタップする必要がありません。操作はiPhoneに手をかざすだけ。
一度かざすと録画開始、もう一度かざすと録画停止、そして自動再生が始まります、終わったら手をかざして録画待機画面に。保存したいときだけ「保存」をタップする様になっています。これは切符がSUICAに変わった以来の大改革です(笑)。
無料ですので、iPhoneユーザの方はぜひお試しください。
また、アプリからピアノの横にiPhoneを設置できるスタンドをご購入いただけるようになっています。こちらもぜひよろしくお願いします。
技術的には、近接センサーを使っています。これ、ちょっと動作に癖があって、意外と実装には苦労しました。横向きでは動かないとか、Event発生にはON/OFFがあって、ONの状態で近接センサーをdisableにすると、次はOFFになったときしか検知しないとか。かといってずっとenableのままだと妙に感度が良くて、Video側の処理を待たずしてStatusが変わってダウンするとか、、。
それから、iPADには近接センサーがないため、同じ動作事はできないのですが、Appleの規定上、iPhoneアプリはiPADでも動かないといけないため(実際、一度リジェクトされました。動かないアプリは結構あるんだけど、文句言っても仕方ない、、、)iPADでは手をかざすという動作ではなく、画面のどこかを触ってもらうことにしています。透明ボタンを画面いっぱいに配置しています。
また、UIImagePickerControllerで撮った動画は、明示的に消さないと消えません。これも想定外でした。ずっと保存されていくので、カメラ終了時、またアプリを落としたときなどのタイミングで、消去しないといけません。
@interface ViewController () NSString *pathToVideo; - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *videoURL = [info valueForKey:UIImagePickerControllerMediaURL]; <span class="deco" style="color:#FF0000;"> pathToVideo = [videoURL path]; </span> //PickerControllerを閉じる [self dismissViewControllerAnimated:YES completion:nil]; // 取った画像の再生 NSURL *url = [info valueForKey:UIImagePickerControllerMediaURL]; playerItem = [[AVPlayerItem alloc] initWithURL:url]; [playerItem addObserver:self forKeyPath:@"status" options:0 context:&ItemStatusContext]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; player = [AVPlayer playerWithPlayerItem:playerItem]; [playerView setPlayer:player]; //再生開始 [player play]; } -(void) deleteFile { //ここで録画したファイルを消す NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:pathToVideo]) { NSError *error; // Attempt to delete the folder containing globalDel.videoPath if ([fileManager removeItemAtPath:[pathToVideo stringByDeletingLastPathComponent] error:&error] != YES) { NSLog(@"Unable to delete file: %@", [error localizedDescription]); }else{ NSLog (@"消去成功"); } } else { NSLog (@"消去失敗"); } }
アプリのデモ動画
no input file error
x-code 6.1 objective-c
Command /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang failed with exit code 1
というエラーが出て、
clang: error: no such file or directory: '/Users/hogehoge/hogehoge/....'
clang: error: no input files
などとあったのですが、確認するとhogehoge以下のファイルはある。
結局、Menu -> product -> clean をしたら直りました。なんだかなぁ、、。
NSTimerの正確性の検証
検証してみた。以下のプログラムで、NSDateによる時間経過のタイミングと、NSTimerによるタイマーのカウントアップ値の差分を比較。viewControllerをClassとするViewにSTART/STOPボタンを割り付けています。
NSDate : iPhoneの時計の経過により時間を計測
NSTimer : 1/1000に一回カウンターをカウントアップ
NSDate *stdate; NSDate *datesave; NSTimer *tm; int counter; - (void)viewDidLoad{ [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void) timeMJ{ counter ++; } - (IBAction)startButton:(UIButton *)sender { counter = 0; float interval; interval = 0.001; tm = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(timeMJ) userInfo:nil repeats:YES ]; datesave = [NSDate date]; [tm fire]; } - (IBAction)stopButton:(UIButton *)sender { [tm invalidate]; stdate = [NSDate date]; float x = [stdate timeIntervalSinceDate:datesave]; NSLog (@"Clock = %.0f Counter = %d 誤差=%.2f%", x*1000, counter,(x*1000-counter)/x/10); }
iPhone5Sでの実行結果。
Clock = 1868 Counter = 1865 誤差=0.15%
Clock = 1866 Counter = 1862 誤差=0.19%
Clock = 1983 Counter = 1979 誤差=0.21%
Clock = 1967 Counter = 1963 誤差=0.18%
Clock = 1867 Counter = 1864 誤差=0.16%
Clock = 2283 Counter = 2280 誤差=0.14%
Clock = 1917 Counter = 1914 誤差=0.14%
Clock = 2668 Counter = 2657 誤差=0.41%
Clock = 3850 Counter = 3845 誤差=0.13%
Clock = 3217 Counter = 3212 誤差=0.14%
Clock = 2617 Counter = 2614 誤差=0.10%
Clock = 1867 Counter = 1864 誤差=0.14%
Clock = 2182 Counter = 2154 誤差=1.30%
Clock = 2449 Counter = 2445 誤差=0.16%
Clock = 2268 Counter = 2265 誤差=0.12%
Clock = 2050 Counter = 2046 誤差=0.21%
Clock = 2616 Counter = 2613 誤差=0.10%
Clock = 2100 Counter = 2097 誤差=0.15%
Clock = 2034 Counter = 2030 誤差=0.17%
Clock = 2249 Counter = 2103 誤差=6.50%
Clock = 2084 Counter = 2082 誤差=0.12%
Clock = 1767 Counter = 1765 誤差=0.11%
これじゃ、STOP Watchとしてはダメですね。
iPhone動画カメラアプリは、撮ったファイルが自動で消えない
今作ってる動画カメラアプリですが、iTUNES Connectで申請まで終わって、やれやれ、ということで自分のiPhoneにローカルインストールして使っていたら、ストレージがいっぱいになったぞゴルァ!と怒られてしまいました。んなこたーないだろう!?とiPhoneのストレージ使用状況を見ると、何と私のカメラアプリが、数ギガにまで膨れ上がってるじゃないか!?ということで早速調べてみた。
このアプリは、明示的に「保存」しない限り、写真フォルダーに動画を保存しないつくりなのですが、それでも一時ファイルがどんどん残ってしまいます。なので消さないといけなかったのね。早速、申請を取り下げて修正をすることに。
//インスタンス変数として宣言 NSString *pathToVideo; //録画が終わったら起動されるメソッド - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *videoURL = [info valueForKey:UIImagePickerControllerMediaURL]; pathToVideo = [videoURL path]; _videoURL = pathToVideo; //PickerControllerを閉じる [self dismissViewControllerAnimated:YES completion:nil]; // 取った画像の再生 NSURL *url = [info valueForKey:UIImagePickerControllerMediaURL]; playerItem = [[AVPlayerItem alloc] initWithURL:url]; [playerItem addObserver:self forKeyPath:@"status" options:0 context:&ItemStatusContext]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; player = [AVPlayer playerWithPlayerItem:playerItem]; [playerView setPlayer:player]; //再生開始 [player play]; } //必要なタイミングでこのメソッドを呼び出す -(void) deleteFile { //ここで録画したファイルを消す NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:pathToVideo]) { NSError *error; // Attempt to delete the folder containing globalDel.videoPath if ([fileManager removeItemAtPath:[pathToVideo stringByDeletingLastPathComponent] error:&error] != YES) { NSLog(@"Unable to delete file: %@", [error localizedDescription]); }else{ NSLog (@"消去成功"); } } else { NSLog (@"消去失敗"); } }
アプリ開発用にディスプレイを新調
x-codeを使ってアプリ開発していますが、今までの開発環境はRatinaになる前の13インチのMBPと、BENQの15.6インチの外付けディスプレイでした。これだと手狭なので、拡張することにしました。
iiyama 23インチ、XUB2390HS-B1 1968 X 1080 ピクセル。
- 出版社/メーカー: マウスコンピューター
- 発売日: 2014/02/10
- メディア: 付属品
- この商品を含むブログを見る
これでx-codeを使うとどうなるかというと、こんな感じ。至って快適。マルチスクリーンで、MBP側をメール、ネット検索、素材編集とかの作業画面にして、プログラミングは大画面側で。今までが1366X768なので、ピクセルの数でいうと約2倍になったわけです。
これだと、アシスタントエディタを開いた状態でも、ガンガンコードが書けますね。今日1日作業してみて、良い点と悪い点を。
良い点:
・これが一番の目的で9割を占めるのだけど、でかい。同時に色々な作業ができる
・反射が少ないので、非常に目が楽
・モニタから離れても(70cmくらい)コードが見える(私、強制視力0.9)。これは、モニタを見るために特定の姿勢を維持しなくて良いということになります。椅子の背もたれによっかかってコードが書ける。非常に楽、腰にも優しい。
・高さ調整ができるため、サブ画面との接地位置調整が可能。
・価格も23K弱というのは安い
残念だった点:
・パネルがもうちょっと低い位置まで下がると嬉しい。
・パネルを縦に回転する仕組みがあるのだが、この機能のせいで横に使っていても水平がずれやすい。水平ってちょっとずれるだけで気になるんですよ。なので水平箇所でカチっと止まると嬉しい。
・スピーカーがついているのだが、音はしょぼい、実にしょぼい。
Thunderbold-HDMI変換ケーブルでつないでいます。
尚、HDMI接続すると、自動的に音声出力もHDMI側に出力されて、MACのヘッドホン端子から音が出なくなります。これを直すには、システム環境設定=>サウンドで、「出力」タブを選んでサウンドを出力する装置を選択から「ヘッドホン」を選びます。
MBPと外付けという使い方の場合、キーボード&マウスも外付けをお勧めします。左右に並べた状態でMBPのキーボードを使うと首が疲れますので。
というわけで、もっと早く導入すればよかった。
アプリ第2弾リリースしました。
絵を壁に飾ったらどうなるか?をシミュレーションできるアプリです。個人的に個人事業立ち上げについて色々アドバイスを頂いてお世話になっているクラーベルさん向け。
https://itunes.apple.com/jp/app/bini-huiwo-kuraberu/id901931669?mt=8&ign-mpt=uo%3D4
まだまだObjective-Cの勉強中なので、習作といった感じなのですが、自分の備忘録として記録しておくと今回このアプリで習得した技術は以下。
1) UIImagePickerContorollerによるカメラ、写真フォルダーの表示
2) 画像サイズの調整 (カメラで撮った画像は大きすぎる)
3) 複数Viewを貼ったときの、各Viewの管理と制御 (NSMutableArrayに保存)
4) Toolbarによる画面切り替え
5) Toolbarのアイコンのカスタマイズ
6) 縦/横の切り替え(これが一番苦労した)
7) 画像合成、保存
8) Scrollbarによる画像拡大、縮小
9) ブラウザ表示
10) collectionViewの使い方
11) textデータの保存、読み出し
12) AutoLayoutとか、そうじゃないときとか
特に3)については、今回画面上に複数の額絵を貼り付けられるようにしたため、ScrollViewで拡大されたときに全部一度に拡大しないといけない、とか、どれか一つを削除したいとか、そのあたり工夫しました。
具体的には、SubViewを増やす際、以下の様にNSMutableArrayにどんどんViewの情報を保存しています。
NSMutableArray *selectedPictureList;
//インスタンス作成
UIImageView *pictureView = [[UIImageView alloc] init];
pictureView = [[UIImageView alloc]initWithFrame:rect];
pictureView.image = image;
pictureView.contentMode = UIViewContentModeScaleAspectFit;
pictureView.tag = button.tag;
[_wallView addSubview:pictureView];
float x = pictureView.center.x;
float y = pictureView.center.y;
//Listに記録
[selectedPictureList addObject:@[@(button.tag), pictureView,@(x),@(y),@(width),@(height)]];
どのViewが選ばれたかというのは、eventが発生したときにsenderやgestureRecognizeにviewが入っていますので、それと比較してselectedPicutreListのpictureViewとどれかと一致しているかを検索します。こうしてpictuerViewを保存しておくと、UIImageViewはポインタ変数なので、selectedPicureList上でx,y,width,heightを変化させれば、実際のViewが変わるわけですね。
これによって、縦横の表示変更などに対応しています。
https://itunes.apple.com/jp/app/bini-huiwo-kuraberu/id901931669?mt=8&ign-mpt=uo%3D4