Hatena::ブログ(Diary)

Over&Out その後 このページをアンテナに追加 RSSフィード Twitter

2011-02-28

「2月は毎日更新」達成!

ここは2/28のエントリーですが書いている現在日時は3/7(月)、AM4:54です。


この週末に計18本、このブログの記事を書きました。


なんで新着じゃなくてわざわざさかのぼって書いたかというと、

  • 「なかなかまだ誰も書いてないような技術について書くって大変だなぁ」
  • 「ちょっとしたTipsなら書きたいことあるんだけどなぁ」
  • 「毎日書いてますってことで量を出せばこまいネタでもありかも?」

ということで「2月は毎日更新」という個人的裏目標を課していたためです。


3月からは勉強会などにもっと出て同業の皆様方と交流しつつ、内容のレベルや質を高めていけたらと思っております。


というわけで目標達成記念エントリーでした!




2011-02-27

iOS SDK用音声認識機能ライブラリVocalKitの使い方


2010年12月4日に行われたyidev(横浜iPhone開発者勉強会)第七回で話した際の資料です。



iPhone SDKで利用できる音声認識ライブラリ(Pocket Sphinxのラッパー)VocalKitについて紹介しています。


(内容)

[vk startListening];
    • 認識を開始する
[vk postNotificationOfRecognizedText];
    • 認識結果を受け取る
      • (NSNotificationが飛んでくる)
  • 単語辞書
    • 単語名 音素1 音素2 音素3...
    • 同じ単語を複数の発音で定義したい場合は(2)とか(3)とかつける
    • 単語名は何でもいいわけじゃないらしい

me M IY

encourage EH N K ER IH JH

encourage(2) IH N K ER AH JH

  • 認識結果
    • NSNotificationのUserInfoプロパティ
NSDictionary *dict = [notification userInfo];	
NSString *phrase = [dict objectForKey:VKRecognizedPhraseNotificationTextKey];
NSNumber *score  = [dict objectForKey:VKRecognizedPhraseNotificationScoreKey];	
  • 辞書作成のコツ
    • 単語数が多いと、処理も重いし認識率も悪い
    • まず標準の辞書で、認識結果をためしてみる(sayコマンド便利)
    • 出てきた単語だけで辞書を再構成する



※『勇気をください』は、音声認識を使った、勇気をくれるアプリです。


(AppBank)

http://www.appbank.net/2010/11/06/iphone-application/182987.php


(AppStore)

http://itunes.apple.com/jp/app/id395958856?mt=8


f:id:shu223:20140112233503j:image:w230:left

f:id:shu223:20140112233644j:image:w230:right





2011-02-26

2011-02-25

Application Loader で iTunes Connectの別アカウントにログインする

受託開発などで申請手続きまで代行する場合など、

Application Loaderにログアウト機能がなく困ってしまう場合があります。


解決方法

下記ファイルを消去するとログインし直せます。

~/Library/Preferences/com.apple.itunes.connect.ApplicationLoader.plist


(参考ページ)

http://www.iphonedevsdk.com/forum/iphone-sdk-development/38556-getting-application-loader-logout-forget.html




2011-02-24

デバイスにインストールされているアプリ一覧を取得する

自分のiPhone(やiPad)に入っているアプリ一覧って当然取れないものと思っていたら、Bumpにアプリ一覧を表示する機能があって、どうやってるんだろうってことで検討してみました。


方法としては、次の2つが考えられます。


  1. アプリのプロセス名一覧を持っておいて、そのプロセスが存在すればインストールされていると判断する
  2. アプリのカスタムURLスキーム一覧を持っておいて、そのカスタムURLスキームが使用可能であればインストールされていると判断する

1の方法の長所は、どんなアプリでも適用可能なこと。ただし、起動中もしくはバックグラウンドでタスクが生きているアプリしかとれない。


2の方法はタスクが生きていないアプリでもとれる代わりに、カスタムURLスキームをもっているアプリしかとれない。


で、どちらの方法も、あらかじめアプリのプロセス名、あるいはURLスキームを知っている必要がある、という大きな欠点があります。


ただ2の場合、『handleOpenURL:』という、URL Schemeをまとめたサイトが存在します。


http://handleopenurl.com/


こちらのサイトからURLスキーマのリストを作り、それを元にインストールされているかどうかを調べる、ということを試してみました。



1. plistを作る

先に挙げたサイトの1ページ目の一部をコピペしてきて


f:id:shu223:20110307015405p:image:w400


このようなplistファイルを作成しました。


(schemes.plist)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
	<dict>
		<key>app_name</key>
		<string>360 Web Browser</string>
		<key>url_scheme</key>
		<string>360</string>
	</dict>
	<dict>
		<key>app_name</key>
		<string>Air Hockey</string>
		<key>url_scheme</key>
		<string>airhockey</string>
	</dict>
	<dict>
		<key>app_name</key>
		<string>Air Hockey Free</string>
		<key>url_scheme</key>
		<string>airhockeyfree</string>
	</dict>
	<dict>
		<key>app_name</key>
		<string>App Store</string>
		<key>url_scheme</key>
		<string>itms-apps</string>
	</dict>
</array>
</plist>

各アプリはapp_nameという要素とurl_schemeという要素を持ちます。



2. plistを読み込む

NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"schemes" ofType:@"plist"];
NSArray *schemes = [[[NSMutableArray alloc] initWithContentsOfFile:path] autorelease];


3. アプリの有無を調べる

そのURL Schemeが使用可能かどうかは、UIApplicationのcanOpenURL:というメソッドで調べることができます。


// 1つずつインストールされているか調べる
for (NSDictionary *aScheme in schemes) {
	NSString *url = [NSString stringWithFormat:@"%@://", [aScheme objectForKey:@"url_scheme"]];
	BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]];
	// オープン可能=インストールされている
	if (canOpen) {
		NSLog(@"%@\t:installed", [aScheme objectForKey:@"app_name"]);
	}
	// オープンできない=インストールされていない
	else {
		NSLog(@"%@\t:not installed", [aScheme objectForKey:@"app_name"]);
	}
}


実行結果

自分のiPhoneで実行した結果

360 Web Browser :not installed

Air Hockey :not installed

Air Hockey Free :not installed

App Store :installed


シミュレータで実行した結果

360 Web Browser :not installed

Air Hockey :not installed

Air Hockey Free :not installed

App Store :not installed


iPhoneにはApp Storeが入っていて、シミュレータには入っていないことがわかります。



まとめ

カスタムURLスキーム一覧を作成して、デバイスにインストールされているアプリの一覧を取得する方法について試してみました。

目新しいことはやってないのでもちろんうまくいきましたが、

  • カスタムURLスキーム一覧作成をどう自動化するか
  • カスタムURLスキームを持たないアプリをどう扱うか

ってところに課題があります。


いずれプロセスを見る方も試してみたいと思います。





2011-02-23

カメラアプリにFacebookへの写真投稿機能をつける

1年前に作った「頭身カメラ」というアプリに、

Facebookへの投稿機能をつけてみました。


f:id:shu223:20110223074848j:image


(AppBankさんへの寄稿記事)

http://www.appbank.net/2010/07/15/iphone-application/140803.php


(AppStore)

http://itunes.apple.com/jp/app/id379198789?mt=8




1. facebook iOS SDKをダウンロード

https://github.com/facebook/facebook-ios-sdk


2. FBConnectヘッダを自分のプロジェクトに追加する

src/facebook-ios-sdk.xcodeproj を開き、

FBConnectフォルダを自分のプロジェクトにドラッグ&ドロップする。


3. FBConnectヘッダをインポート

Facebookの機能を使いたいクラスでヘッダインポート。

#import "FBConnect.h"

この時点でビルド可能。


4. Facebookでアプリケーションを登録する

http://www.facebook.com/developers/createapp.php

appID(アプリID)を控えておきます


(ここまでの手順はSDKのREADMEに書かれています。)


5. カスタムURLスキームを設定する

Facebookアプリでは認証をsafariで行い、カスタムURLスキームでアプリに戻ってきます。

そのための設定。

  1. Info.plistに項目を追加。
  2. keyの"Information Property List"の下に"URL types"を追加
  3. 続いて"URL types" > "Item0" > "URL Schemes" > "Item0"、なければこのように追加していく
  4. 最後の"Item0"の valueに "fb[appId]"を追加、appIdはいつも通り

(参考ページ)http://d.hatena.ne.jp/tkwn/20101215/1292402660


6. Facebookクラス初期化

3でFBConnect.hをインポートしたクラスで、

xxxx.h

Facebook *facebook;

xxxx.m

facebook = [[Facebook alloc] initWithAppId:appId];

7. カスタムURLスキームで戻ってくる際に呼ばれるデリゲートメソッドを実装

AppDelegateに。viewControllerは6でFacebookクラスオブジェクトを持たせたクラスのインスタンス。

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
	return [[viewController facebook] handleOpenURL:url];
}

8. 認証処理の実装

6でFacebookクラスオブジェクトを持たせたクラスで、

NSArray *permissions = [NSArray arrayWithObjects:@"publish_stream", @"offline_access",nil];
[facebook authorize:permissions delegate:self];

ログイン処理完了後に呼ばれるデリゲートメソッドを実装。

(ヘッダでFBSessionDelegateプロトコルへの準拠を宣言しておく)

- (void)fbDidLogin {
	NSLog(@"login");
}

-(void)fbDidNotLogin:(BOOL)cancelled {
	NSLog(@"did not login");
}

9. 写真投稿処理の実装

カメラアプリなので UIImageView 型の resultImageView というインスタンス変数を持っているとして、

NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
							   resultImageView.image, @"picture",
							   nil];
[facebook requestWithMethodName:@"photos.upload"
					   andParams:params
				   andHttpMethod:@"POST"
					 andDelegate:self];

投稿処理完了後に呼ばれるデリゲートメソッドを実装。

- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response {
	NSLog(@"received response");
};

- (void)request:(FBRequest *)request didLoad:(id)result {
	if ([result isKindOfClass:[NSArray class]]) {
		result = [result objectAtIndex:0];
	}
	if ([result objectForKey:@"owner"]) {
		NSLog(@"Photo upload Success");
	} else {
		NSLog(@"result name:%@",[result objectForKey:@"name"]);
	}
};

- (void)request:(FBRequest *)request didFailWithError:(NSError *)error {
	NSLog(@"didFailWithError:");
};

以上!SDKが用意されてるので簡単ですね。

サンプルコードは週末にアップする予定です。




2011-02-22

UITextFieldへの入力時にキーボードではなくUIPickerViewを出す方法

普通はキーボードがニュッと出てくるところを、ピッカー出すようにします。

(某アプリのクレジットカード対応でカードの有効期限入力するところで用いました)


ものすごくシンプルに書くと、


- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
    // Show UIPickerView

    return NO;
}

こういうことになります。

textFieldShouldBeginEditing:というUITextFieldのデリゲートメソッドでNOを返すことで、キーボードが出なくなります。




ピッカー出すところまで書くならこんな感じ。


- (void)showPicker {
	// ピッカーが下から出るアニメーション
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:0.4];
	[UIView setAnimationDelegate:self];
	picker.center = CGPointMake(160, SHOW_TARGET);
	[UIView commitAnimations];
	
	// 右上にdoneボタン
	if (!self.navigationItem.rightBarButtonItem) {
        UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(done:)];
        [self.navigationItem setRightBarButtonItem:doneButton animated:YES];
        [doneButton release];
    }	
}


- (void)hidePicker {
	// ピッカーが下に隠れるアニメーション
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:0.4];
	[UIView setAnimationDelegate:self];
	picker.center = CGPointMake(160, HIDE_TARGET);
	[UIView commitAnimations];

	// doneボタンを消す
	[self.navigationItem setRightBarButtonItem:nil animated:YES];
}


- (void)done:(id)sender {
	// ピッカーしまう
	[self hidePicker];
	
	// doneボタン消す
    [self.navigationItem setRightBarButtonItem:nil animated:YES];
}


- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
	// ピッカー表示開始
	[self showPicker];

    return NO;
}






2011-02-21

nibファイルの読み込みパフォーマンスを改善するUINibクラス

iOS4.0から追加されたUINibというクラスでは、nibファイルからのインスタンス生成においてキャッシュによるパフォーマンス向上が図られているとのこと。


すれ違い通信アプリ『EncountMe』はiOS4以降としているので、これはよさそうってことで調べてみました。


使い方

今までだとNSBundleのloadNibNamed:owner:options:メソッドで都度nibファイルを読み込んでいたところを、


UINib *nib = [UINib nibWithNibName:@"TestNib" bundle:[NSBundle mainBundle]];

とするだけ。


nibファイルに含まれるオブジェクトを取り出すには


NSArray *objects = [nib instantiateWithOwner:self options:nil];

とします。



Appleもこちらの使用を推奨しているとのことで、iOS4以降のアプリで、nibでUITableViewのカスタムセル作ってる場合などには適用して損はなさそうです。




2011-02-20

すれ違い通信アプリ開発で得たBluetoothの知見まとめ

南東京iPhone開発者勉強会で話した際の資料です。



すれ違い通信アプリ『EncountMe』の開発を通して得た

iPhoneのBluetoothまわりの諸々について、まとめています。


アジェンダだけ抜粋すると

  • Bluetoothを利用するメリット(EncountMeでBTを選んだ理由)
  • 実装方法
    • GKPeerPickerControllerを使う場合
    • GKPeerPickerControllerを使わない場合
  • できること/できないこと
    • ユーザー操作なしでの接続確立
    • 複数ピアとの同時接続
    • アプリ内でのOn/Of
    • AndroidとのBluetooth通信
  • 注意点
    • Wi-Fiとの干渉

といった内容になっております。


※間違い、勘違いを含んでいる可能性が多分にありますので、

 お気づきの点などあればご指摘いただけると幸いです。



(3/4追記)

@cutmailさんがメモしてくださっていました。

http://d.hatena.ne.jp/invent/20110220/1298218794

スライドみるのが面倒な方はこちらをどうぞ。

  • EncountMe
    • すれ違い通信アプリ
    • Bluetoothと位置情報を使用
  • Bluetoothを利用するメリット
    • 省電力
    • オフラインで利用可能
    • サーバーサイド不要(P2Pなので)
    • ちょうどすれ違い的な範囲(10mちょっと)
  • GKPeerPickerControllerを使う場合
  • 使わない場合
  • できること/できないこと1
    • ユーザー操作なしで接続確立
    • Pickerを使用すると、接続確立時にユーザーにOK/NGを選んでもらう必要がある。
  • できること/できないこと2
    • どっかのピアと接続した状態で新しいのが繋がるか
    • 複数ピアとの同時接続
      • 最大16人まで?ピアモードでは無関係?未検証
      • できる
  • できること/できないこと3
    • アプリ内でのON/OFF
      • できない
    • ピッカーは使える
    • 非公開API
      • BluetoothManager.framework
    • デバイスonnいしてもらうまでピッカーを使用して、そのあとGKSessionだけ使用ってのはできない
    • peesWithConnectionsStateをポーリングして、「状態変化が起きているか」は調べられる
    • ただoffだから起きないのか周りいに接続相手がいないからかはわからない
  • Notification
    • BluetoothAvailabilityChangedNotification
      • on/off検知には使えない
    • BluetoothPowerChangedNotification
      • on/offが切り替わったタイミングで飛んでくる
    • もしかしたらon/off検知はできるかも
    • session.didFailWithError:でエラー出てるっぽい
    • wifi/bluetoothが両方offだった場合に、30509エラーがでてる
  • AndroidとのBluetooth通信
    • できない
    • iPhone側がGameKit同士でしか接続できないため
  • Wi-Fiとの干渉
    • 問題:BTを使用しているとWi-Fiの速度が遅くなる
    • 原因:GameKitがWi-Fiを使用している(両方が有効な場合、フレームワークが勝手にWi-FiかBTかを選ぶ)
    • 対策:Wi-Fi接続時にはGKSessionを無効に(available=NO)する
  • GKSessionインスタンスを使い回す場合
    • そもそもGKSessionインスタンスを作りなおせば問題ない
  • まとめ
    • ユーザーにはバッテリーを喰うと思われている
    • アプリからon/offする手段がない
    • on/off検知できない
    • Androidと接続できない





2011-02-19

NSStringの全角/半角バリデーションチェック

某アプリにクレジットカード決済をつける際に、カード名義人入力欄で全角入力をはじく必要があり、ググってみたところ意外と「全角文字と半角文字を判定する方法」はあんまり出てこなかったのでこちらに書いておきます。

(全角→半角の変換の話はいっぱい出てきた)




結論として、下記コードのように、

1文字ずつURLエンコードし、文字列長が4以上だったら全角文字と判定する

という方法でうまくいきました。


/* URLエンコード */
-(NSString *)stringByURLEncoding:(NSStringEncoding)encoding
{
  NSArray *escapeChars = [NSArray arrayWithObjects:
             @";" ,@"/" ,@"?" ,@":"
            ,@"@" ,@"&" ,@"=" ,@"+"
            ,@"$" ,@"," ,@"[" ,@"]"
            ,@"#" ,@"!" ,@"'" ,@"("
            ,@")" ,@"*"
            ,nil];
 
  NSArray *replaceChars = [NSArray arrayWithObjects:
              @"%3B" ,@"%2F" ,@"%3F"
             ,@"%3A" ,@"%40" ,@"%26"
             ,@"%3D" ,@"%2B" ,@"%24"
             ,@"%2C" ,@"%5B" ,@"%5D"
             ,@"%23" ,@"%21" ,@"%27"
             ,@"%28" ,@"%29" ,@"%2A"
             ,nil];
 
  NSMutableString *encodedString = [[[self stringByAddingPercentEscapesUsingEncoding:encoding] mutableCopy] autorelease];
 
  for(int i=0; i<[escapeChars count]; i++) {
    [encodedString replaceOccurrencesOfString:[escapeChars objectAtIndex:i]
                                   withString:[replaceChars objectAtIndex:i]
                                      options:NSLiteralSearch
                                        range:NSMakeRange(0, [encodedString length])];
  }
 
  return [NSString stringWithString: encodedString];
}

/* 入力文字列を1文字ずつURLエンコードし、文字列長が4以上だったら全角文字と判定 */
+ (BOOL)isOnlyHalf:(NSString *)str {
	for(int i=0; i<[str length]; i++) {
		NSString *aChar = [str substringWithRange:NSMakeRange(i, 1)];
		NSString *encodedChar = [aChar stringByURLEncoding:NSUTF8StringEncoding];
		if ([encodedChar length] < 4) {
			continue;
		}
		else {
			return NO;
		}
	}
	return YES;
}


うまくいかなかった方法その1

ちなみに、NSCharacterSetで英数字のセットを作って判定する方法を最初に試したのですが、うまくいきませんでした。


/* 入力文字列が半角英数字と半角スペースのいずれかの文字を含む場合はYESを返す */
+ (BOOL)isHalfChar:(NSString *)str {
	NSMutableCharacterSet *charSet = [NSMutableCharacterSet alphanumericCharacterSet];
	[charSet formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
	NSRange range = [str rangeOfCharacterFromSet:charSet];
	if (range.location == NSNotFound) {
		return NO;
	}
	return YES;
}

/* 1文字ずつisHalfCharで判定を行う */
+ (BOOL)isOnlyHalf:(NSString *)str {
	for (int i=0; i<[str length]; i++) {
		NSString *aChar = [str substringWithRange:NSMakeRange(i, 1)];
		if (![Hoge isHalfChar:aChar]) {
			return NO;
		}
	}
	
	return YES;
}

全角文字を入れても、rangeOfCharacterFromSet:で、先頭文字が検索結果として返ってきてしまい、NG。



うまくいかなかった方法その2

全角文字を文字コードで指定して判定する方法もうまくいきませんでした。


/* 全角文字のいずれかであればNOを返す(半角カナはYESを返す)*/
+ (BOOL) isHalfChar:(NSString *)aValue {
	NSRange match = [aValue rangeOfString:@"[\x20-\x7E\xA1-\xDF]" options:NSRegularExpressionSearch];
	if (match.location != NSNotFound) {
		return YES;
	}
	return NO;
}

/* 1文字ずつisHalfCharで判定を行う */
+ (BOOL)isOnlyHalf:(NSString *)str {
	for (int i=0; i<[str length]; i++) {
		NSString *aChar = [str substringWithRange:NSMakeRange(i, 1)];
		if (![Hoge isHalfChar:aChar]) {
			return NO;
		}
	}
	
	return YES;
}

英数字のセットを判定する方法と同様、全角文字を入れても、rangeOfString:で、NSNotFoundにならず、NG。

(いや、ちがう挙動だったかも。とにかくNGでした)




というわけで、結論として一番上の、「エンコードして文字列長を判定する」方法をご使用ください。




2011-02-18

UITextViewのheight調整

UITextViewの高さ調整を、UILabelと同様に下記のようにやってしまうと、

ちゃんと合わないことがあります。

CGSize size = [textView.text sizeWithFont:textView.font
                        constrainedToSize:CGSizeMake(TITLE_WIDTH, MAX_HEIGHT)
                            lineBreakMode:UILineBreakModeTailTruncation];
frame.size.height = size.height;

UITextViewはcontentSizeってものを持ってるので、

下記のようにすればOK.

CGRect frame = textView.frame;
frame.size.height = textView.contentSize.height;
textView.frame = frame;

(参考)

http://stackoverflow.com/questions/50467/how-do-i-size-a-uitextview-to-its-content




2011-02-17

アップロード時に An error occurred uploading to the iTunes Store エラーが出た場合の対処方法

iTunes Connectにバイナリをアップロードする際に、

"An error occurred uploading to the iTunes Store"

というエラーが出てしまいアップロードできないことがあります。


ググってみると、

  • /Developer/Application/Utilities/Application Loader.appを直接起動してやってみるといいよ(オーガナイザからアップロードしてる人に対して)
  • ただのネットワークエラーなので何度もやり直すといいよ

などなど出ていますが、僕の場合は、


「zip(ipa)ファイル名を変更する」


でいけました。


たとえばバイナリのzipファイル名が以下の場合にエラーになるようです。

  • "Domino's" のようにアポストロフィが入っている
  • "頭身カメラ"のように全角文字が入っている




2011-02-16

寄稿を受け付けているアプリ紹介ブログ/サイトリスト

もう1年近く前にググってまとめただけのものですが・・・


リストの見方

  • ブログ名
    • URL
    • 申請タイプ(投稿/寄稿/掲載申請など)
    • 掲載方法(説明ページのURLなど)

リスト



2011-02-15

iPhoneバイブの制御

にゃらんの肉球なにょだ』という目覚まし機能付き肉球アプリ(無料)をつくってたときのメモより。


f:id:shu223:20110307020427p:image:w240



まずバイブを鳴らす(動作させる)方法は


#import <AudioToolbox/AudioServices.h>

した上で、

AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

いろいろ調べたけどこれしかないっぽい。


で、これだと一度ブルッとなって終わるので、

アラームのように繰り返し再生するには、

#pragma mark AudioService callback function prototypes
void MyAudioServicesSystemSoundCompletionProc (
   SystemSoundID  ssID,
   void           *clientData
);

#pragma mark AudioService callback function implementation

void MyAudioServicesSystemSoundCompletionProc (
   SystemSoundID  ssID,
   void           *clientData
) {
  if (iShouldKeepBuzzing) { // Your logic here...
      AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); 
  } else {
      //Unregister, so we don't get called again...
      AudioServicesRemoveSystemSoundCompletion(kSystemSoundID_Vibrate);
  }  
}

(参考ページ)

http://stackoverflow.com/questions/2718837/how-to-run-vibrate-continuously-in-iphone



まだ問題がある。

これだとバイブの間隔がなく連続でブルブルなってしまうので、

繰り返し間隔を空けるには次のようにする。

void MyAudioServicesSystemSoundCompletionProc (
   SystemSoundID  ssID,
   void           *clientData
) {
  if (iShouldKeepBuzzing) { // Your logic here...
      [NSThread sleepForTimeInterval:(NSTimeInterval)interval];
      AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); 
  } else {
      //Unregister, so we don't get called again...
      AudioServicesRemoveSystemSoundCompletion(kSystemSoundID_Vibrate);
  }  
}

(参考ページ)

http://stackoverflow.com/questions/1490028/iphone-dev-performselectorwithobjectafterdelay-or-nstimer





2011-02-14

UIKit の UIResponderクラスで「こする」「フリック」をどう実装するか

過去のメモからの掘り起こしシリーズ。


「こする」の検知方法


・肉球方式・・・touchesBeganの時点でタップの処理を行い、touchesMovedではこするの処理を行う。

・レシーブ方式・・・touchesBeganでタップ開始時間を記録し、touchesEndで変位と経過時間を判定する(=フリック)


解説

「肉球方式」とは、『にゃらんの肉球なにょだ』で「肉球をこする」操作の判定に用いている処理方式のことです。

タップはタップとして扱い、「それとは別に」こするという操作を検知しています。


f:id:shu223:20110306075857p:image:w240




「レシーブ方式」とは、『世バレシーブ』で、ボールをフリックでサーブする際の操作に用いている処理方式のことです。

肉球方式と違ってタップをとる必要がないかわりに、フリックの速さをとる必要があるためこのような処理方法になっています。


f:id:shu223:20110306080108p:image:w320




(にゃらんの肉球なにょだ)

http://itunes.apple.com/jp/app/id389129063?mt=8


(世バレシーブ)

http://itunes.apple.com/jp/app/id397889044?mt=8




2011-02-13

UIWebViewの中のリンク文字列をタップ&ホールドすると「開く」「コピー」といったアクションシートが出てくるのを無効にする

過去のメモからの掘り起こしシリーズ。


こちらは『Hi! Tweet 〜つぶやいて覚える英会話〜』を開発中のときのメモと思われます。


UIWebViewの中の文字列を押すとOpen、Copyというポップアップ表示が出てくるのを無効にする


CSSの中で

-webkit-touch-callout:none;

と書く


・・・以上です。

(参考ページ)http://www.iphonedevsdk.com/forum/iphone-sdk-development/22338-disabling-uiwebview-tap-hold-popup.html




(AppBankさんの記事)

http://www.appbank.net/2010/08/26/iphone-application/160468.php


(AppStore)

http://itunes.apple.com/jp/app/id387103486?mt=8


(紹介動画)





2011-02-12

OpenCV 顔認識でビルド時に Null pointer (NULL filename) in cvOpenFileStorage エラーがでる場合の対処法

過去のメモからの掘り起こしシリーズ。


恋愛偏差値』という、ロンブー田村淳さんと、早稲田大学森川教授とのコラボアプリ開発中のメモより。


OpenCV Error: Null pointer (NULL filename) in cvOpenFileStorage, file ../../src/cxcore/cxpersistence.cpp, line 2570

terminate called after throwing an instance of 'cv::Exception'


アプリ名.appが全角文字だと発生する。

=>プロダクト名は半角英字に、bundle display nameを変更する


解説

はい、上に書いてある通り、アプリ名が全角文字だと、Haarカスケードのxmlファイルを読み込む際に"OpenCV Error: Null pointer (NULL filename) in cvOpenFileStorage"というエラーが出るのでプロダクト名は半角文字にしましょう、ってだけの話です。





ちなみに『恋愛偏差値』でOpenCVをどう使用しているかというと、森川教授の研究に基づき「顔の対称度」を診断する機能があり、そこでOpenCVによる顔認識を用いています。



(AppBankさんの記事)

http://www.appbank.net/2010/11/03/iphone-application/185586.php


(AppStore)

http://itunes.apple.com/jp/app/id397971413?mt=8


f:id:shu223:20110306064052j:image:w240





2011-02-11

Animation GIFをフォトライブラリに保存する試行錯誤

過去のメモ書きからの掘り起こしシリーズ。


内容から察するに『とびでるカメラ』というアプリの開発時に、Animation GIFをなんとかiPhoneのフォトライブラリに保存できないか試行錯誤したメモのようです。


  • iMotionというアプリを使ってみたところ、フォトライブラリへの保存はビデオとして行っている
  • UIImageWriteToSavedPhotosAlbumはUIImageを保存することしかできず、UIImageがアニメGIFに対応していない
  • gifをそのままビデオとして保存出来ないか試したところNG
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(outFilePath)) {
	UISaveVideoAtPathToSavedPhotosAlbum(outFilePath, self, nil, nil);
}
  • フォトライブラリに直接書き込めるらしいが、下記フォルダにアニメGIFをmoveItemAtPathでコピーしても画像ファイルが認識されなかった(iphotoでも認識されず)
    • /var/mobile/Media/Photos/
    • /var/mobile/Media/DCIM/
  • 代替案
    • メール送信
    • twitpic投稿

結論としてAnimation GIFはフォトライブラリに保存できません



最近知ったところによるとフォトライブラリってDBになってるらしいので、だからフォルダに直接moveItemAtPathでファイル書き込みしても認識されなかったわけですね。


ちなみに「代替案」ってとこでtwitpic投稿とありますが、その後アプリからAPIたたいてAnimationGIFをTwitterアイコンとして設定することを思いつき、それがアプリのヒットにつながったのでした。

無料期間中に総合4位、3日で6万ダウンロード



ちなみにこのブログの右上にある僕のアイコンも『とびでるカメラ』製です。

画像をちょろちょろっとなぞるだけで動くアイコンが作成できてなかなか楽しいアプリとなっていますので、よろしければ遊んでみてください。

http://itunes.apple.com/jp/app/id397619605?mt=8


(AppBankさんへの寄稿記事)

http://www.appbank.net/2010/11/14/iphone-application/188437.php


f:id:shu223:20110306060640p:image:w230:left

f:id:shu223:20110306060638p:image:w230:right





2011-02-10

UINavigationItemのleftBarButtonItemとbackBarButtonItemの違い

自分の手元にある古いメモ書きを見ると、下記のようにありました。


leftBarButtonとbackBarButtonの違い

  • 形が違う(leftは左側がとがった矢印形、backは四角形)
  • self.navigationItem.leftBarButtonItem = someBarBtn; は現在のビューに反映されるが、self.navigationItem.backBarButtonItem = someBarBtn; は次のビューに反映される

・・・え、、、2つめのってほんと?


過去の自分が信用できず改めてドキュメントを確認してみました。

http://developer.apple.com/library/ios/#documentation/uikit/reference/UINavigationItem_Class/Reference/UINavigationItem.html


(backBarButtonItem)

The bar button item to use when this item is represented by a back button on the navigation bar.

Discussion

When this item is the back item of the navigation bar―when it is the next item below the top item―it may be represented as a back button on the navigation bar. Use this property to specify the back button. The target and action of the back bar button item you set should be nil. The default value is a bar button item displaying the navigation item’s title.


(leftBarButtonItem)

A custom bar item displayed on the left of the navigation bar when this item is the top item.


んーたぶんそんなことは書いてない。

たぶん一番上に書いたメモ書きは間違ってて、コードを書く場所がpushする直前だったりそうじゃなかったりしたせいでそういう勘違いをしたのではなかろうか。




おまけ:UINavigationControllerのBackボタンの表示文字列を、前画面のタイトルでなく"Back"や「戻る」に変更する

何の役にも立たないエントリーになってしまったのでおまけ。


既にいろんな方が書かれているTipsですが、最初はなかなかやり方わからないのでこちらにも載せておきます。


下記処理をpushViewController:する直前あたりで行います。

UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc]
		initWithTitle:@"戻る"
		style:UIBarButtonItemStyleBordered
		target:nil
		action:nil];
self.navigationItem.backBarButtonItem = backButtonItem;



2011-02-09

MGTwitterEngine で大量のlibxml2メモリリークが出る場合の対処法

MGTwitterEngineを使用しているアプリで、leaksでメモリリークがないか調べてみると、libxml2関係で大量(ってほどでもないけど)のリークが検出されてしまってました。


MGTwiterLibXMLParser.mで、xmlReaderForMemoryしたあとの解放の仕方が間違っているようです。


下記修正でメモリリークが治まりました。

//	xmlFree(_reader);
	xmlFreeTextReader(_reader);



2011-02-08

iPhoneアプリの起動アニメーションの実装方法

まずは弊社で出したすれ違い通信アプリ『EncountMe』のプロモーション動画をご覧ください。



あれ、貼付けてみて気づいたけど、動画内でオープニングアニメーション出てこないな・・・


まあとにかく、EncountMeには起動アニメーションがありまして、こういうのどうやるの?って聞かれることがたまにある(実は1度しかない)ので、ご紹介させていただきます。




方法

AppDelegate の application:didFinishLaunchingWithOptions: メソッド内でアニメーション処理をスタートさせればOKです。


application:didFinishLaunchingWithOptions: は、

アプリの起動処理が完了した時点でコールされるメソッドです。


つまり、起動に表示される画像である Default.png の代わりにAnimationGIFを使えるとか動画を入れられるとかではなく、

起動したあとにアニメーション処理を実行することになります。


EncountMe を例にとると、

Default.png にはこのように背景だけの画像を用い、


f:id:shu223:20110305235638p:image:w240


application:didFinishLaunchingWithOptions:で、下記のようにアニメーション用のビューをaddSubviewしています。


[window addSubview:rootViewController.view];
openingViewController = [[OpeningViewController alloc] initWithNibName:@"OpeningViewController" bundle:nil];
openingViewController.delegate = self;
[window addSubview:openingViewController.view];
[window makeKeyAndVisible];


ちなみにEncountMeは「無料」ですので、ぜひDLしてお試しいただけると幸いです!

http://itunes.apple.com/jp/app/encountme/id414446072?mt=8




2011-02-07

アプリアップデート後の初回起動時にUIAlertViewでお知らせを表示する方法

アップデートしたあとに、「このバージョンの新機能」とかがポップアップで出てくるアレの実装方法です。

(パッと思い出せるのはAppBankさんのアプリとか)


下記をAppDelegateのapplicationDidFinishLaunching:あたりで実行します。


// ロードしたことあるバージョンを調べる
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
float loadedVersion = [[defaults objectForKey:@"version"] floatValue];

// このバンドルのバージョンを調べる
float bundleVersion = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] floatValue];

// バージョンアップされてればバージョンアップ情報を表示
if (!loadedVersion || bundleVersion > loadedVersion) {
	
	// アラート表示
	NSString *titleStr = @"バージョンXXの新機能";
	NSString *bodyStr = @"本文";

    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:titleStr 
                                                        message:bodyStr 
                                                       delegate:self
                                              cancelButtonTitle:@"OK" 
                                              otherButtonTitles:nil];
                [alertView show];
                [alertView release];
	
	// 現在のバンドルバージョンを記録
	[defaults setObject:[NSNumber numberWithFloat:bundleVersion] forKey:@"version"];
}

かなり省略しましたが表示するバージョンアップの文言はバージョン番号と言語(日本語/英語/etc)で出し分けるため、また別の担当者が編集できるようplistで管理したりしてます。





2011-02-06

入門本から得たTipsなど

一番最初に買った入門本を「もうそろそろ必要ないかな」と思い処分する際に、

改めて読むと知らないことが沢山あって色々とツイートしたのが昨年5月。


それを改めて読んでみるとまだ知らない(忘れている)ことが

多々あったので以下に抜粋。


  • uiwebviewで表示するhtmlソースにはmetaタグのviewportで横幅を320に指定するとよい
  • UIViewAnimationTransitionの第三引数であるcacheをNOにすると、アニメーション実行中にビューの中身が書き換わったときにリアルタイムに反映される。YESにしておくと処理負荷が軽いのでトランジションは滑らかに。
  • UIResponderのtouchesXXXメソッドの第二引数、UIEventのallTouchesメソッドを呼ぶと、他のビューを含めた全タッチ情報を取得できる。
  • touch down repeatは繰り返しタッチの「2回目以降」で発生、touch drag exit/enterはドラッグ操作でビューの「枠を横切ったとき」に発生。touch drag inside/outsideはビューの内/外をドラッグ中に「指を動かす度」に発生
  • アプリ起動時のnibファイル読み込み処理シーケンス。
    1. nib内の全インスタンスに対してallocとinit
    2. IBで設定した属性の反映。アウトレットやアクションの接続もここ。
    3. 全インスタンスに対してawakeFromNibメソッドの呼び出し。
  • なので、initにはクラス自身の初期化を、awakeFromNibにはアウトレットに接続されているインスタンスの初期化を記述するとよい。
  • afconvertはUNIXのサウンドフォーマット変換コマンド
  • AAC/MP3/AppleLosslessはiphoneのハードがデコードするので再生時のCPU負荷が少ないが、複数の音声を再生できない。なのでBGMなど長いものはファイルサイズを考慮し圧縮フォーマットを、効果音的なものはCAF等を使うとよい。
  • NSDateは2001.1.1からの経過秒数で日時を扱うクラス、NSDateCompornentsはカレンダー上の値で日時を扱うクラス、NSCalenderは両者をカレンダーのルール(とタイムゾーン)に従って相互変換するクラス。なるほどすっきりした。
  • ビルド時にxibからnibが作られるので、アプリ実行時にはnibを扱うことになる。基本的にはnibとxibは同じものと思って差し支えない。

基礎からのiOS SDK
鶴薗 賢吾 松浦 健一郎 司 ゆき
ソフトバンククリエイティブ
売り上げランキング: 306,162



2011-02-05

iAd 、テスト広告しか出ないけどこれで実装あってるの?

iAdは実装自体は簡単(あっちこっちに親切なサンプルコードがある)なんですが、

開発中、ずっと不安がありました。


この画像のように、デバッグモードはもちろん、

アドホックでもテストバナーしか出ないこと。

f:id:shu223:20110206075810p:image:w240


でもちゃんとドキュメントにこう書いてあります


ApplicationAudienceDisplayed Ads
Developer buildDeveloperiAd Network serves test ads.
Ad-hoc distribution buildBeta TestersiAd Network serves test ads.
Signed Distribution buildEnd UsersiAd Network serves live ads if you signed the iAd Network Agreement and enabled advertising for your application.

Distributionビルド版ではちゃんと広告出るよ、とのことです。




2011-02-04

UIAlertViewを左寄せにする

UIAlertViewのsubviewsからUILabel型のオブジェクトを探してきて、textAlignmentプロパティを設定します。

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title
	message:msg
	delegate:delegate
	cancelButtonTitle:@"OK"
	otherButtonTitles:nil];

id obj = nil;
// タイトル文字列が存在しない場合はsubviewsの1番目の要素
if (!title || !([title length] > 0)) {
	if ([[alertView subviews] count] > 0) {
		obj = [[alertView subviews] objectAtIndex:0];
	}
}
// タイトル文字列が存在する場合はsubviewsの2番目の要素
else if ([title length] > 0) {
	if ([[alertView subviews] count] > 1) {
		obj = [[alertView subviews] objectAtIndex:1];
	}
}

// UILabel型であればtextAlignmentを左寄せにセット
if (obj && [obj isKindOfClass:[UILabel class]]) {
	((UILabel *)obj).textAlignment = UITextAlignmentLeft;
}

[alertView show];
[alertView release];	
}

ちなみにこういうのって審査通るの?と不安でしたが無事通りました。

(非公開APIはだめだけどsubviewsを勝手に取り出してアレコレするのはOKなのか。。?)




2011-02-03

アニメーションをAS3のTweenerっぽく書けるObjective-Cライブラリ TweenC

フリーで公開されてるライブラリ。

http://www.tweenc.com/


iPhoneにおけるアニメーションをこんな感じでTweener風に書けるようになります。

// animationViewを1秒かけて(20, 400)に移動させる
[TweenC animateView:animateView 
	withAnimation:kTweenEaseInExpo
		andDuration:1 
			toPoint:CGPointMake(20, 400) delegate:self];
	
// rotateViewを3秒かけて180度回転させる
[TweenC rotateView:rotateView 
			onAxis:@"x"
	 withAnimation:kTweenEaseInOutExpo
	   andDuration:3.0 
		 toDegrees:180 delegate:self];
	
// fadeOutViewのアルファを1秒かけて0.25にする
[TweenC fadeView:fadeOutView
		 toAlpha:0.25 
   withAnimation:kTweenEaseOutSine
	withDuration:1.0 
		delegate:self];
	
// scaleViewを1秒かけて0.5倍に縮小する
[TweenC scaleView:scaleView
		  toScale:0.5
	withAnimation:kTweenEaseOutSine 
	 withDuration:1.0 
		 delegate:self];

TweenerにあるBounce系のトランジションも使えます。

(Bounceはボヨヨーンと弾む感じのアレです)


(TweenC.hより)

// Bounce
@interface EaseInBounce : NSObject <TweenObject>{}
@end
@interface EaseOutBounce : NSObject <TweenObject>{}
@end
@interface EaseInOutBounce : NSObject <TweenObject>{}
@end



・・・で、ここまで紹介しておいてアレなんですが、

このライブラリ半年程前に案件で使用したことあって、

使用感としては「かなり微妙」でした。。


何がダメだったかというと、


  • バグバグ。全然指定した率で拡大縮小しない、とか、scaleViewとanimateViewを呼ぶ順番によって挙動が違う、とか。
  • Bounce系アニメーションを楽に書けそうだから導入したのに、Bounceの動作がなんかイマイチだった。
  • 2009年(iOS3.Xの時代)から更新されてない。もうメンテされないのかも。

というわけでそれ以降は使用しておりません。




2011-02-02

演算子の優先順位で勘違いしてたこと

!(not演算子)は、>(比較演算子)よりも優先順位が高い

ってご存知でした?


僕はつい最近気づきました。

びびりました、なんせ挙動かわっちゃうので。。



どういうことかというと

NSArray型のarrという変数があるとして、

if (![arr count] > 4) {
	return NO;
}
return YES;

は、


if (!([arr count] > 4)) {
	return NO;
}
return YES;

とは結果が違います。


!(not演算子)は、比較演算子よりも先に処理されるからです。



試してみましょう

NSLog(@"arr:%@",arr);
NSLog(@"test:%d", ![arr count] > 0);
NSLog(@"test:%d", !([arr count] > 0));
NSLog(@"test:%d", ![arr count] > 4);
NSLog(@"test:%d", !([arr count] > 4));

(実行結果)

arr:(

)

test:1

test:1

test:0

test:1


0と比較するときにはたまたま結果が同じになるので

今まで意識することがなかったのかもしれません。



知ってる人にとっちゃ当たり前すぎることなのかもしれないけど、

"!"って"-"とか"+"みたいに符号的な感覚で使っちゃってたので、

僕以外にも勘違いしてる人いるんじゃないかと。



ちなみに余談ですがなぜ

if ([arr count] <= 4) {
	return NO;
}
return YES;

としてないのかというと、

arrを初期化してない場合の[arr count]の挙動まで把握してないので

(そういうのって言語仕様ちゃんと調べて把握したつもりでも

コンパイラやら実行環境やらで変わったりする気がして)

「正常条件以外はすべて弾く」という考え方で上記のように書いております。



(参考)演算子の優先順位

http://www.cppreference.com/wiki/jp/operator_precedence




2011-02-01

UnitTestでハマった点

UnitTestのやり方、ちょっと検索したらザクザクでてきました。

親切な日本語の記事がたくさん。


その中でこちらの記事が超親切で、更新日もつい最近でとても参考になりました。

http://kozy.heteml.jp/l4l/2011/01/iphonetddsentestingkit.html


ただ少しハマったのが、新規ターゲット作成時。


今まで新規ターゲット追加なんてやったことなかったので、

"Cocoa Touch" ではなく、"Cocoa" の方で Unit Test Bundleを作成していて、

AppKitがないよとかのエラーに悩まされました・・・



f:id:shu223:20110202025450p:image:w440

(↑こっちを選ばないようお気をつけ下さい・・・)




iPhone用グラフ描画ライブラリ s7graphview の使い方

OpenCVを使って画像処理する際に、パラメータチューニングなどで

画像のヒストグラムをみる必要があって試したライブラリ。


値を渡せばグラフを描画したviewを生成してくれて、

それをaddSubviewするだけ、という大変ありがたいシロモノです。



以下、大まかな使い方を紹介します。

(ソースコード一式)https://github.com/shu223/GraphViewTest


1. ファイルを追加する

こちらからs7graphviewをダウンロードし、

以下の3ファイルをプロジェクトに追加します。

  • S7GraphView.h
  • S7GraphView.m
  • S7Macros.h

2. グラフを貼付ける位置と大きさを設定する

S7GraphViewはUIViewのサブクラスなので、

位置や大きさを変えるにはinitWithFrameでCGRectを設定するだけです。

CGRect graphRect = CGRectMake(0, 0, 320, 460);
self.graphView = [[S7GraphView alloc] initWithFrame:graphRect];

3. グラフの本数を設定する

デリゲートメソッドで描画したいグラフの本数を渡します。

- (NSUInteger)graphViewNumberOfPlots:(S7GraphView *)graphView {	
	// ここではとりあえず1本
	return 1;
}

4. データソースの設定

デリゲートメソッドで X軸と Y軸別々に配列を渡します。

ただし配列の要素数は一致している必要があります。

- (NSArray *)graphViewXValues:(S7GraphView *)graphView {

	// ここではとりあえずX軸を 0〜255 に
	NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:256];
	for ( int i = 0 ; i <= 255 ; i ++ ) {
		[array addObject:[NSNumber numberWithInt:i]];
	}
	
	return array;	
}

- (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex {

	// Y軸は0〜999までの整数をランダムに
	// 値の数はX軸と同じく256個
	NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:256];
	
	switch (plotIndex) {
			
		default:
		case 0:
			for ( int i = 0 ; i <= 255 ; i ++ ) {
				[array addObject:[NSNumber numberWithInt:rand()%1000]];
			}
			break;
	}
	return array;	
}


x軸、y軸のラベルの設定は、渡した値の範囲をみて勝手にやってくれるようです。

xValuesFormatter / yValuesFormatter プロパティでフォーマット指定や、

xValuesColor / yValuesColor で文字色の指定、

gridXColor / gridYColor で軸の色の指定もできるようです。



上記手順で作成したコードを実行すると、下記のようなグラフが表示されます。


f:id:shu223:20110130010156p:image:w240




2009 | 08 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 11 | 12 |
2017 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2018 | 02 |