24/7 twenty-four seven

iOS/OS X application programing topics.

iOS 5.1 の音声入力を使ってアプリケーションを操作してみる

iOS 5.1 から日本語の音声入力がサポートされました。さらに API にも UIDictationPhrase など音声認識ができそうなクラスが追加されています。
これはプログラムから音声認識を使うことができるのか?そうすると音声を使ってハンズフリーでアプリケーションの操作ができるかと思いましたが調べてみるとそんなにうまい話ではありませんでした。


まず iOS 5.1 で追加された関係のありそうな API を見てみましょう。

UITextInput.h
  • Added UIDictationPhrase
  • Added UIDictationPhrase.alternativeInterpretations
  • Added UIDictationPhrase.text
  • Added -[UITextInput dictationRecognitionFailed]
  • Added -[UITextInput dictationRecordingDidEnd]
  • Added -[UITextInput insertDictationResult:]


UIDictationPhrase といかにも便利そうな名前のクラスですが、リファレンスをよく読んでみると text プロパティと alternativeInterpretations プロパティ以外はメソッドもなく、単に UITextInput protocol の insertDictationResult: の引数に結果を詰めるだけのクラスということがわかります。

さらに読み進めていくと追加された API はすべて UITextInput protocol のメソッドであり要するに音声入力はあくまでもテキスト入力の一環で、 UITextInput protocol を通して入力を受け取ることしかできないということがわかります。

つまり手順としては

  1. UITextInput protocol を実装して
  2. ファーストレスポンダになりキーボードを出して
  3. キーボードの音声入力ボタンで録音を開始して
  4. 完了ボタンで録音した音声をサーバに送り
  5. 結果を UITextInput protocol のメソッドを通して受け取る

ということになります。


キーボードは自動で表示するとしても、入力の開始と終了でキーボードのボタンを手で押す必要があるというのは今回の目的においてはあまり便利ではありません。

そこでどうにか自動的に入力を開始/終了することができないか調べてみたところ、UIDictationController というクラスにそれっぽいメソッドがありました。

@interface UIDictationController
〜(略)〜
+ (id)sharedInstance;
+ (id)activeInstance;
- (void)stopDictation;
- (void)cancelDictation;
- (void)startDictation;
- (void)startRecordingLimitTimer;
- (void)cancelRecordingLimitTimer;
〜(略)〜
@end

実験してみると、startDictation, stopDictation, cancelDictation メソッドでそれぞれ、開始・終了・キャンセルを実行できることがわかりましたので今回はこれを利用することにしました。
公開されていないクラスのため、このままでは AppStore に申請することはできませんが、今回は実験ということで。


ということでだいたい実現できそうな気になってきたので以下のように実装してみました。
まず、なにはなくとも UITextInput protocol を実装します。 UITextInput protocol は UIKeyInput protocol を継承しているのでそちらのメソッドも実装します。
カスタムのテキストビューを作るためにテキスト入力システムとやりとりするものなので実装しなければいけないメソッドがかなりたくさんありますが、今回はテキスト入力をまじめにハンドリングする必要はないので、ほとんど空の実装でオーケーです。

@interface VNTextInputView : UIView<UITextInput, UIGestureRecognizerDelegate>

@end

#pragma mark UIKeyInput methods

- (void)deleteBackward {
    
}

〜(略)〜

#pragma mark UITextInput methods

- (NSString *)textInRange:(UITextRange *)range {
    return nil;
}

〜(略)〜

#pragma mark -

- (void)insertDictationResult:(NSArray *)dictationResult {
    [[NSNotificationCenter defaultCenter] postNotificationName:VNDictationRecognitionSucceededNotification
                                                        object:self 
                                                      userInfo:[NSDictionary dictionaryWithObject:dictationResult forKey:VNDictationResultKey]];
}

- (void)dictationRecordingDidEnd {
    [[NSNotificationCenter defaultCenter] postNotificationName:VNDictationRecordingDidEndNotification object:self];
}

- (void)dictationRecognitionFailed {
    [[NSNotificationCenter defaultCenter] postNotificationName:VNDictationRecognitionFailedNotification object:self];
}

@end


iOS 5.1 で追加された3つのメソッドで音声入力をハンドリングします。
音声入力ボタンを押す、あるいは startDictation メソッドで開始したあと、完了ボタン、あるいは stopDictation メソッドが呼ばれたタイミングで dictationRecordingDidEnd が呼ばれます。
そして自動的にサーバに録音された音声データが送られます。
サーバの処理が完了した段階で、何らかのテキストが認識された場合は insertDictationResult: が、音声認識ができなかった場合は dictationRecognitionFailed のいずれかが呼ばれます。

結果は文字列として UIDictationPhrase の text プロパティに格納されており、1つの音声データに対して複数の候補がある場合は alternativeInterpretations プロパティに配列で格納されて受け取れます。UIDictationPhrase はある程度のまとまりごとに配列になっています。


これで任意のタイミングで音声入力を利用できるようになりました。
認識の精度はかなり高いのでパターンマッチを工夫するだけでも実用になりそうです。
使い道としてはハンズフリーでもアプリケーションの操作や、別のプログラムを呼び出したり、家電のリモコンとして使ってもおもしろそうです。


ここまでを簡単な形に整理して GitHub で公開しています。
ぜひ試して改良してみてください。

kishikawakatsumi/VoiceNavigation · GitHub

Xcode 4.3ではファイルをドラッグ&ドロップで追加するとき、ターゲットに追加するかどうかのチェックが外れることが多いので注意!

バージョン 4.2.x までの Xcode では、プロジェクトにファイルをドラッグ&ドロップして追加するとき、コピーするかどうかや、ターゲットに追加するかどうかのチェックボックスは前回の選択状態を引き継ぐという挙動でした。
しかし、バージョン 4.3 になってからはターゲットに追加するかどうかのチェックボックスについてのみ、追加しようとしているファイルの拡張子によって、選択状態が変化するようになりました。

ざっと調べた限りでは、追加しようとするファイルが .m ファイルだけの場合はチェックが選択済みになり、.h が含まれる場合は選択が解除されているようです。


具体的には下記のように JSONKit.h と JSONKit.m を追加しようとすると、.h ファイルが含まれているために選択状態は解除された状態でダイアログが表示されます。



もし、選択状態が解除されていることに気づかずに、そのままプロジェクトに追加してしまうと、プロジェクトには追加されるもののコンパイル対象には含まれないという状態になってしまいます。


この状態は非常に危険で、ヘッダファイルは参照できるためにコンパイル時に警告は出ないし、Objective−Cの場合はたいていリンクもできてしまいます。特に例のJSONKitのような標準クラスをカテゴリで拡張するようなライブラリだとまずビルド時に問題は起こりません。


もちろんコンパイルされないのでオブジェクトができませんから実行時にエラーになりますが、この「プロジェクトに追加されているけれどターゲットに含まれていない」という状態は分かりにくいので問題の解決が遅れることも多々あります。


というわけで、けっこう何も考えずにリターンを押しがちなところだと思いますが、少し注意する必要があるという話でした。


※Xcode 4.3.1 は今ダウンロード中なのでアップデートが済んだら検証します。
Xcode 4.3.1 でも同じ挙動でしたorz... ファイル追加するたびに神経使うのもアレなので、要望としてバグレポートですね。






twitter:177334116054863873:tree

CAEmitterLayer でクリスマスは爆発しろ - iOS Advent Calendar 2011

iOS 5 からCAEmitterLayer と CAEmitterCell クラスが iOS でも使えるようになり、パーティクルを簡単に表示することができるようになりました。


これを使ったクリスマスネタが何かできないかと思って、タップするとクリスマスが爆発するゲームを作ってみました。

みんなでクリスマスを爆発させてください。

kishikawakatsumi/ExplodeChristmas · GitHub


ゲームが始まるとクリスマス(っぽいもの)が落ちてくるので……


タップして爆発させてください。


根こそぎ爆発させてください。


Titanium mobile の KeyChain モジュール TiKeyChainStore を書きました。【添削希望】

UICKeyChainStore を応用して Titanium mobile の KeyChain モジュール TiKeyChainStore を書きました。

kishikawakatsumi/TiKeyChainStore · GitHub


下記のように使います。

var store = tikeychainstore.createKeychainStore({
  service: 'com.kishikawakatsumi.ti' // optional
});

store.setKeyChainItem ({
  key: 'userame',
  value: 'kishikawakatsumi@mac.com'
});

store.setKeyChainItem ({
  key: 'password',
  value: 'password1234'
});

store.synchronize;

Ti.API.info(store.description); // debug print


Titanium のモジュールは初めて書いたので、詳しい方に添削してもらえるとうれしいです。

KeyChain のデータを操作するラッパークラス UICKeyChainStore を書きました。

アップルのサンプルコード GenericKeychain に含まれる KeyChain のラッパー KeychainItemWrapper.m の実装が微妙だったので書きました。

kishikawakatsumi/UICKeyChainStore · GitHub


KeychainItemWrapper クラスには下記で報告されている問題や、
A-Liaison BLOG: KeychainItemWrapper を改造して、複数の Keychain Item に同時にアクセス出来るようにしてみた


下記の箇所でメモリーリークする問題があったり、使い勝手もよくないので、そのまま使うのはおすすめしません。

- (void)resetKeychainItem
{
	OSStatus junk = noErr;
    if (!keychainItemData) 
    {
        self.keychainItemData = [[NSMutableDictionary alloc] init]; // <= メモリーリークする!


というわけで、あたらしく書いて見ました。

使い方

  1. リンクするフレームワークに Security.framework を追加します。
  2. UICKeyChainStore.h と UICKeyChainStore.m をプロジェクトに追加します。

クラスメソッドを使って値を操作する

便利メソッドとしてクラスメソッドを用意してあります。キーと値を指定するだけで簡単に使えます。
サービス名を指定しない場合は自動的に Bundle ID がサービス名になります。


キーと値を指定して値を追加・更新する。

[UICKeyChainStore setString:@"kishikawakatsumi" forKey:@"username"];
[UICKeyChainStore setString:@"password1234" forKey:@"password"];

//=> ["username" = "kishikawakatsumi", "password" = "password1234"]

サービス名を明示的に指定することもできます。

[UICKeyChainStore setString:@"kishikawakatsumi" forKey:@"username" service:@"com.kishikawakatsumi"];
[UICKeyChainStore setString:@"password1234" forKey:@"password" service:@"com.kishikawakatsumi"];


値をキーチェーンから削除するにはキーを指定します。

[UICKeyChainStore removeItemForKey:@"username"];
[UICKeyChainStore removeItemForKey:@"password"];

サービス名を指定していた場合は、サービス名も含めて指定します。

[UICKeyChainStore removeItemForKey:@"username" service:@"com.kishikawakatsumi"];
[UICKeyChainStore removeItemForKey:@"password" service:@"com.kishikawakatsumi"];

KeyChainStore オブジェクトを使って値を操作する

複数の項目を追加・更新する場合は UICKeyChainStore のインスタンスを作成するほうが便利です。
NSUserDefaults のような使い勝手になります。


デフォルトのサービス名 (Bundle ID) の KeyChainStore を作成した場合。

UICKeyChainStore *store = [UICKeyChainStore keyChain];

[store setString:@"kishikawakatsumi@mac.com" forKey:@"username"];
[store setString:@"password1234" forKey:@"password"];

[store synchronize]; // Write to keychain.


サービス名を指定して KeyChainStore を作成した場合。。

UICKeyChainStore *store = [UICKeyChainStore keyChainStoreWithService:@"com.kishikawakatsumi"];

[store removeItemForKey:@"username"];
[store removeItemForKey:@"password"];

[store synchronize]; // Write to keychain.

miil 1.0.1 がリリースされました。


miil

miil miil

miil.me


miil 1.0.1 がリリースされました。

バージョン 1.0.1 の変更点

  • 利用規約を変更しました。

機能改善

  • ユーザー一覧画面のフォローボタンのタップ領域を拡大し、押しやすくしました。
  • Twitterから友だちを検索する際のUIを改善しました。
  • 現在地以外の位置情報を投稿できるようになりました。
  • お店を新しく登録する際の手順を改善しました。

不具合の修正

  • HOTタイムラインの並び順が更新されない問題を修正しました。
  • フォローボタンが正しく動かない問題を修正しました。
  • ユーザー登録できないことがある問題を修正しました。
  • お店の情報が正しく追加されないことがある問題を修正しました。
  • iPod touchで電話をかける画面が表示されてしまう問題を修正しました。
  • 「お知らせ」を表示する際にクラッシュすることがある問題を修正しました。
  • 「続きを読みこむ…」を押した際にクラッシュすることがある問題を修正しました。
  • 新着の「お知らせ」バッジが表示されないことがある問題を修正しました。

関連リンク


東日本大震災緊急支援募金募集中。国際NGOワールド・ビジョン・ジャパン

miil 1.0.0 がリリースされました。


miil

miil miil

miil.me


私がプログラムを担当しました、miil 1.0.0 がリリースされました。


食べものの写真を通して「おいしい!」を共有するアプリケーションです。


写真を撮って投稿したり、ほかの人の写真をみたり、


ほかの人の写真をみたり、感想を書き込んだり、


食べたい写真があったら、お店を調べたりできます。


眺めているだけでも楽しい気分になるアプリケーションですので、ぜひダウンロードしてみてください。

関連リンク


東日本大震災緊急支援募金募集中。国際NGOワールド・ビジョン・ジャパン

i-Radio 1.2.0 がリリースされました。


i-Radio


i-Radio i-Radio


ネットラジオのアプリケーション i-Radio のバージョン 1.2.0 が審査を通過しました。
自動更新購読 (Auto-Renewable subscriptions) による月額プレミアム会員がスタートしました。
プレミアム会員になると、季節ごとのバックナンバーが聴取できるようになるほか、すべての画面で広告が非表示になります。

主な変更点

  • 自動更新購読 (Auto-Renewable subscriptions) によるプレミアム機能の追加

ColorChooser が便利


カラーピッカーで選択した色を自動的に UIColor や NSColor のコードに変換してくれるユーティリティです。
同様のものに Developer Color Picker がありますが、 それのメニュー常駐版のような感じです。
コードを書いているときにどこからでも呼び出せるので私はこちらのほうが便利だなと思います。

ColorChooser - Pairote Leelaphattarakij

UINavigationBar に複数の UIBarButtonItem を配置するには

UINavigationBar は基本的に左右 (leftBarButtnItem, rightarButtonItem) に一つずつしかボタンを配置することができません。
しかし、ちょっと工夫をするとその制限を突破することができます。
(まあ物理的なスペースの関係でせいぜい2つか3つがやっとなのですけどね)

方法その1. UIToolbar を UIBarButtonItem として配置し、その中に UIBarButtonItem を並べる


もっとも見た目がキレイに仕上がる方法です。

UINavigationBar の leftBarButtnItem と rightarButtonItem は UIBarButtonItem のインスタンスをそれぞれ1つずつしか代入できませんが、UIToolbar を UIBarButtonItem として作成することで、その UIToolbar に複数のボタンを配置することができるようになります。

UIBarButtonItem *sendButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Send", nil) style:UIBarButtonItemStyleBordered target:self action:@selector(send:)];

UIBarButtonItem *cameraButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera target:self action:@selector(cancel:)];
cameraButton.style = UIBarButtonItemStyleBordered;

UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];

UIToolbar *toolbar = [[MyToolbar alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 110.0f, 44.0f)];
toolbar.backgroundColor = [UIColor clearColor];
toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight;
UIBarButtonItem *toolbarBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:toolbar];
[toolbar release];

toolbar.items = [NSArray arrayWithObjects:space, cameraButton, sendButton, nil];
[space release];
[cameraButton release];
[sendButton release];

self.navigationItem.rightBarButtonItem = toolbarBarButtonItem;
[toolbarBarButtonItem release];


ただ、標準の UIToolbar の外観は UINavigationBar の外観と微妙な差異があるので、このままでは境界が目立ってしまいます。


そこで UIToolbar のサブクラスを作成し、空の drawRect: メソッドでオーバーライドし、標準の描画を無効にします。

@interface MyToolbar : UIToolbar
@end

@implementation MyToolbar

- (void)drawRect:(CGRect)rect {
    
}

@end


すると、このような状態になります。


真っ黒になってしまったので、背景色を透明に設定します。
drawRect: メソッドの処理は backgroundColor と無関係というのがミソですね。

toolbar.backgroundColor = [UIColor clearColor];


キレイになりました。


もし、横方向の画面をサポートする場合、ナビゲーションバーは横方向では 30 ピクセルの幅に変わりますので、ツールバーの autoresizingMask プロパティに UIViewAutoresizingFlexibleHeight を設定しておきましょう。

toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight;



UIViewAutoresizingFlexibleHeight を指定しない場合は下のようになります。すこしボタンの大きさがアンバランスですね。


色をつけると違いが分かりやすくなります。


tintColor を設定すれば色も変えられます。

方法その2. UISegmentedControl をボタンのように使用する

標準のメールアプリでも採用されている方法です。
メールアプリの「前へ」「次へ」のように同じような動作をするボタンを並べる場合は良い方法だと思います。


titleView プロパティに乗せると3つのボタンを並べても余裕があります。


UISegmentedControl をボタンとして使用するには、momentary プロパティを YES に、アクションは UIControlEventValueChanged を設定します。

segmentedControl = [[UISegmentedControl alloc] initWithItems:items];
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.momentary = YES;
segmentedControl.frame = CGRectMake(segmentedControl.frame.origin.x, segmentedControl.frame.origin.y, segmentedControl.frame.size.width + 16.0f * [items count], segmentedControl.frame.size.height);
[segmentedControl addTarget:self action:@selector(segmentedControlAction:) forControlEvents:UIControlEventValueChanged];
[self.navigationItem setTitleView:segmentedControl];
[segmentedControl release];

方法その3. UIToolbar を上部のナビゲーションバーの位置に配置する

簡単ですがオススメしません。
というのも UIToolbar は画面の下部に配置するようにデザインされているため、上部に配置するとどうしても外観に違和感がでてしまうからです。
(iOS 4.x までの iPhone の場合。iPad のツールバーは上下どちらに配置してもいいようにデザインされている。)


ナビゲーションバーの位置に配置したツールバー。
ステータスバーとの境界が不自然ですね。

NSNull のインスタンスは nil として振舞ってくれると嬉しいなって


とか思ってたのですが、そう考える人はやっぱりほかにもいるようです。



たとえば私がいちばん面倒だなと思うのはjson-frameworkがnullをNSNullにマッピングするので(nilはNSArrayやNSDictionaryに格納できないため)でWeb APIからのレスポンスをParseする際に、値がセットされるときとnullがセットされるときと両方あるような場合、ハンドリングがとたんに面倒になるのですね。

 NSString *results = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
 NSDictionary *dic = [results JSONValue];
 NSString *shortURL = [[[dic objectForKey:@"results"] objectForKey:longURL] objectForKey:@"shortUrl"];

上記の場合、longURL の値が NSNull だったとすると objectForKey: の呼び出しが失敗してクラッシュします。
このとき NSNull が nil だとしたら、nil へのメッセージは単に無視されるので問題ないわけなのでそうなっていたら便利なのにという話です。


というわけで、下記のようなカテゴリをどこかに書いておくと、NSNull へのメッセージはすべて無視されるので、まるで nil のように扱えて便利です。
何をしているかというと、メッセージフォワーディングの仕組みが動作するように methodSignatureForSelector: および forwardInvocation: をオーバーライドします。
そうすると存在しないメソッドを呼びだそうとするとこれらのメソッドが呼ばれるので、forwardInvocation: メソッドで NSNull に存在しないメソッドの呼び出しは無視するように処理を変更しています。

[参考]Does Objective-C use short-circuit evaluation? - Stack Overflow

@implementation NSNull(IgnoreMessages)

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([self respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:self];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig=[[NSNull class] instanceMethodSignatureForSelector:aSelector];
    // Just return some meaningless signature
    if (sig == nil) {
        sig = [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
    }
    
    return sig;
}

@end


東日本大震災緊急支援募金募集中。国際NGOワールド・ビジョン・ジャパン

MKMapView に CATransform3D を適用するときの注意



@さんがつぶやいてたので気になって調べてみました。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = 1.0f / -500.0f;
    transform = CATransform3DRotate(transform, 60.0f * M_PI / 180.0f, 1.0f, 0.0f, 0.0f);
    
    mapView.layer.transform = transform;
}

こんなふうに書いてみたところ、どうもズーム機能が正常に動作しなくなるようです。
(ズームはするけど拡大率に合わせて再描画してくれない)


で、いろいろ試した結果、どうやら mapView の親ビューのレイヤーに transform を適用した上で、mapView のレイヤーにはその逆行列を適用することで打ち消してあげると正しく動作するようです。
(単純に親ビューのレイヤーに適用するだけではダメ。親ビューのレイヤーに適用した transform は自動的にサブビューのレイヤーにも適用されるので。)
下記がコード例です。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = 1.0f / -500.0f;
    transform = CATransform3DRotate(transform, 60.0f * M_PI / 180.0f, 1.0f, 0.0f, 0.0f);
    
    contentView.layer.transform = transform;
    
    // mapView に逆行列を適用する
    CATransform3D t = CATransform3DInvert(transform);
    mapView.layer.transform = t;
}


実行した結果は下のようになります。ちゃんとズームできてますね。


ちなみに、ズームできなくなるのは行列の m34 要素を変更したときのみで、単に回転やスケールするだけなら問題ないぽいです。


東日本大震災緊急支援募金募集中。国際NGOワールド・ビジョン・ジャパン

kotobank for iPhone 1.0.0 がリリースされました。


kotobank - コトバンク

kotobank - コトバンク kotobank - コトバンク

株式会社ジェネシックス | genesix


私がプログラムを担当しました、kotobank for iPhone 1.0.0 がリリースされました。


データを内部に持ち、オフラインで使用できる辞書アプリケーションです。
このアプリケーションの特長は、複数の辞書を一度に横断検索できる点です。

初期状態では付属しているポケットプログレッシブ国語辞典だけの検索になりますが、追加の辞書を購入することでひとつの言葉をいろいろな辞書で一度に調べられます。

以下が初期状態の検索結果です。


現在、購入可能な辞書をすべてインストールした場合、同じ語でも次のような結果になります。


「企業がわかる事典」など、普段あまり使わない辞書に意外な結果が出ておもしろかったりします。


検索結果を選択すると、詳しい意味を見ることができます。
この画面からは、調べた単語を単語帳に登録したり、メールで送信することができます。


また、この画面からさらに気になる言葉を選択して、検索しなおすことが可能です。
この機能でどんどん言葉を検索していくと、ついつい時間をわすれてしまいそうになります。


単語帳に登録した言葉は、単語帳タブの画面から見ることができます。


アプリケーションのストアから辞書を購入して追加することができます。
現在は「プログレッシブ和英中辞典 第3版」「プログレッシブ英和中辞典 第4版」「企業がわかる事典」の3つが購入できます。
和英と英和辞書はそれぞれ 1,600 円ですが、「英和中辞典セット」という商品を購入すると、2,000 円で両方の辞書を一度に購入できるのでおトクです。


そのほか、普段あまり使用しない辞書は検索対象外にしたり、ひとつの辞書だけで検索したりといった設定ができます。


ということで、辞書を追加していくことでどんどん調べる楽しさが増えていくアプリケーションです。
ぜひダウンロードして使ってみてくださいませ。

LCD Clock HD 1.1.2 がリリースされました。


LCD Clock HD

LCD Clock HD LCD Clock HD

バージョン 1.1.2 がアップルの審査を通過しました。
iOS 4.3の動作と、日本以外の国において2011年後半の移動休日のデータを更新しました。

変更点

  • iOS 4.3の動作を確認しました。
  • 日本以外の国において2011年後半の移動休日のデータを更新しました。


東日本大震災緊急支援募金募集中。国際NGOワールド・ビジョン・ジャパン

iPhone アプリにおいて Interface Builder を使って Grouped Style の UITableView を作るときは separatorStyle の値に注意

Grouped スタイルのテーブルビューを使用した iPhone アプリケーションの中に、下の画像のように先頭セクションの区切り線が2重に表示されているものをチラホラ見かけます。
実用上の問題は無いのですが、ちょっと不恰好なので気になりますよね。



この現象は、UITableView の separatorStyle に UITableViewCellSeparatorStyleSingleLineEtched が指定されていることが原因で発生します。

コードでインスタンスを生成した場合の separatorStyle デフォルト値は UITableViewCellSeparatorStyleSingleLine なのですが、Interface Builder で UITableView を Grouped スタイルで作成した場合はデフォルト値として UITableViewCellSeparatorStyleSingleLineEtched が自動的に選択されるようになりました。
(もともとはそんなことはなかったのですが、UITableViewCellSeparatorStyleSingleLineEtched が iOS SDK 3.2 から導入され、おそらくそのあたりのタイミングで動作が変更されたようです。)


ということですので、iPhone の場合に Grouped スタイルのテーブルビューを Interface Builder を使って作成するときは、separatorStyle の値を UITableViewCellSeparatorStyleSingleLine など、UITableViewCellSeparatorStyleSingleLineEtched 以外に設定しましょう。

コードでインスタンスを生成した場合のデフォルト値は UITableViewCellSeparatorStyleSingleLine ですので問題はありません。



公式のドキュメントによる言及が見つからないのですが、UITableViewCellSeparatorStyleSingleLineEtchedは(おそらく)iPad 専用の値です。
次のように iPad で Grouped Style のテーブルに対して指定した場合はキレイにハマります。



というわけで、コードで作る場合にはデフォルト値はドキュメントに書いてあるとおりなのですが、Interface Builder を使って作る場合にはデフォルト値は (IB が空気を読んで?) ドキュメントと異なる場合が多いので注意したほうがいいですね。

Interface Builder で作った場合とコードで書いた場合のデフォルト値の違いは、ほかにも各種ビューの backgroundColor や autoresizingMask などがありますので、余計な色が付いていないかどうかなど、きちんと確認したほうがいいでしょう。


東日本大震災緊急支援募金募集中。国際NGOワールド・ビジョン・ジャパン