Xcode - カーソル位置の履歴を行き来する

2010年8月12日木曜日 | Published in | 2 コメント

このエントリーをはてなブックマークに追加

option + command + ← or → でカーソル位置の履歴を行き来することができる。

例えば下のファイルを見ているとする。

その後ファイルを切り替える。
ここで option + command + ← を押すと再び元の位置が表示される。
option + command + → を押すとまた2番目のファイル内へ移動する。


履歴はカーソルの移動毎に記録されるので同じファイル内での移動にも使える。

拡張子から MIME Type を取得する

2010年8月11日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

MobileCoreServices.framework に 拡張子とMIME Type の相互変換用の関数が用意されている。

UTType Reference

変換方法


変換はどちらの場合も一旦 UTIへの変換を経由する。
MIME Type (例:image/png)
 ↓
UTI(例:public.png)
 ↓
拡張子(例:png)
UTI への変換には UTTypeCreatePreferredIdentifierForTag() を使う。
逆に UTIからの変換には UTTypeCopyPreferredTagWithClass() を使う。

MIME Type から 拡張子を取得する場合はこんなコードになる。
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(
 kUTTagClassMIMEType, (CFStringRef)mimeTYpe, NULL);
 
CFStringRef filenamExtension =
 UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);
 :
CFRelease(uti);
CFRelease(filenameExtension);

どちらの関数も引数に UTTagClass を取る。これは次の2つが定義されている。
const CFStringRef kUTTagClassFilenameExtension;
const CFStringRef kUTTagClassMIMEType;
また取得した文字列は CFReleaseで開放してやる必要がある。


サンプル


こんな感じ。

UTType系関数をラップするユーティリティクラスを用意してみた。
@interface UTTypeUtility : NSObject {
}

+ (NSString*)filenameExtensionFromMimeType:(NSString*)mimeType;
+ (NSString*)mimeTypeFromFilename:(NSString*)filename;
+ (NSString*)mimeTypeFromFilenameExtension:(NSString*)filenameExtension;

+ (NSString*)UTIfromMimeType:(NSString*)mimeType;
+ (NSString*)UTIfromFilename:(NSString*)filename;
+ (NSString*)UTIfromFilenameExtension:(NSString*)filenameExtension;

@end

中身はこんな感じ。
+ (NSString*)filenameExtensionFromMimeType:(NSString*)mimeTYpe
{
 CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(
  kUTTagClassMIMEType, (CFStringRef)mimeTYpe, NULL);
 
 NSString* filenamExtension =
  (NSString*)UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);
 
 CFRelease(uti);
 
 return [filenamExtension autorelease];
}

なお UTType系関数の利用には下記のフレームワークを追加する必要がある。

その上で MobileCoreServicesをインポートする。
#import <MobileCoreServices/MobileCoreServices.h>


ソースコード


GitHubからどうぞ。
UUTypeSample at 2010-08-10 from xcatsan's iOS-Sample-Code - GitHub
ソースコードはGitHubからどうぞ。


参考情報

Able Pear Software: How to get the file extension for a mime type on iOS
UTType関連の関数の使い方はここで知った。

共通の背景画像を使う

2010年8月10日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

以前、UITableView の背景に画像を表示するブログを書いた。今回は1つの UITableViewだけでなく、全画面共通の背景を表示する方法について調べてみた。

UIWindow に UIImageView を貼り付ける


通常 iPhoneアプリは1つの UIWindow を持つが、ここへ UIImageViewを配置し共通で使う画像を表示させる。こんなイメージ。

MainWindow.xib を開き、Windowに UIImageViewを配置し、あとは表示させたい画像を指定してやる。その上で他のビューの背景色を透明にするとこの画像が背景に表示されるようになる。


サンプル


サンプルを作ってみた。UINavigationController を使い、2つの UITableViewController を行き来する。

初期画面はこう。


行を選択すると次のビューがスライドしてきて

次のビューが表示される。スライド中に背景が動いていないのがポイント(あたりまえだが)。


ソースコードは GitHub からどうぞ。
BackgroundImage at 2010-08-07 from xcatsan's iOS-Sample-Code - GitHub


UITableView でのポイント


UITableView の背景色(backgroundColor)を透明色にする必要があるが、これを InterfaceBuilder上で設定すると角の部分が黒くなってしまう。

拡大図。

これを防ぐにはコードで透明色の指定を行う。
- (void)viewDidLoad {
 [super viewDidLoad];
  :
  :
 self.tableView.backgroundColor = [UIColor clearColor];
}

するとこうなる。

※理由はわかりません。


参考情報


Cocoaの日々: UITableView の背景に画像を表示する

UITableView - 編集モードで左側のアイコンを消す

2010年8月9日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

UITableView を編集モードにした時に左側に削除用のアイコンが表示される。

これを消すには UITableViewDelegate を使う。
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
 editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
 return UITableViewCellEditingStyleNone;
}
- (BOOL)tableView:(UITableView *)tableView
  shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
 return NO;
}

アイコンを消す(UITableViewCellEditingStyleNone)と共に、インデントしない設定を行っている。

するとこうなる。わかりずらいが編集モードに入ってもアイコンが非表示でインデントも起こらない。

- - - -
UITableView の editing プロパティを変更すると自動的にその上の UITableViewCell にも反映されるので、編集用のユーザインターフェイスを自前で作る時にも editing が使えると便利。その場合に既存の動作を隠す方法が今回の内容。

CoreData - awakeFromSnapshotEvents:

2010年8月8日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

NSManagedObjectContextに対して rollback を送ると、対象となる NSManagedObject の awakeFromSnapshotEvents: が呼び出される。

NSManagedObject Class Reference - awakeFromSnapshotEvents:

引数には実行された操作の種類が渡される。
enum {
   NSSnapshotEventUndoInsertion = 1 << 1,
   NSSnapshotEventUndoDeletion = 1 << 2,
   NSSnapshotEventUndoUpdate = 1 << 3,
   NSSnapshotEventRollback = 1 << 4,
   NSSnapshotEventRefresh = 1 << 5,
   NSSnapshotEventMergePolicy = 1 << 6
};
typedef NSUInteger NSSnapshotEventType;

このメソッドは操作が行われた後に呼び出される。

- - - -
rollback直前のイベントを取るにはどうしたらいいのか。

NSTimer を Blocks で処理する [2]改良版

2010年8月7日土曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: NSTimer を Blocks で処理する

前回のコードを少し改良してみる。

改良版 Blocks対応NSTimer


前回は userInfo を持回す為に NSMutableDictionary を導入したり、Blocksの引数に userInfo を付けたりしていたが、考えてみると Blocksは定義されているそのレキシカルスコープ内の self やメンバ変数、auto変数が参照できるので userInfo は無くてもいい。そこで userInfo の無い改良版を作ってみた。

まず定義
typedef void (^TIMER_BLOCK__)(NSTimer*);

@interface NSTimer (Extension)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
 block:(TIMER_BLOCK__)block repeats:(BOOL)repeats;

@end

実装
+ (void)executeBlock__:(NSTimer*)timer
{
 if (![timer isValid]) {
  return;
  // do nothing
 }
 
 TIMER_BLOCK__ block = [timer userInfo];
 block(timer);
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
 block:(TIMER_BLOCK__)block repeats:(BOOL)repeats
{
 NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:seconds
     target:self
   selector:@selector(executeBlock__:)
   userInfo:[[block copy] autorelease]
    repeats:repeats];
 return timer;
 
}
userInfo の持回しが無くなったので、ものすごいシンプルになった。


サンプルによる検証


前回のサンプルを書き換えて上記メソッドを使うようにした。

※ソースコードは GitHubからどうぞ。
NSTimerBlocks at 2010-08-07.2 from xcatsan's iOS-Sample-Code - GitHub

タイマー利用部分はこんな感じ。
- (void)viewDidLoad {
    [super viewDidLoad];
 
 NSArray* array = [NSArray arrayWithObjects:@"DOG", @"CAT", @"MONKEY", nil];

 timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
      block:^(NSTimer* timer) {
       self.counterLabel1.text =
     [NSString stringWithFormat:@"%d", counter1++];
       NSLog(@"%@", array);
      }
    repeats:YES];

 timer2 = [NSTimer scheduledTimerWithTimeInterval:2.0
      block:^(NSTimer* timer) {
       self.counterLabel2.text =
     [NSString stringWithFormat:@"%d", counter2];
       counter2 += 10;
       NSLog(@"%@", array);
      }
    repeats:YES];

 timer3 = [NSTimer scheduledTimerWithTimeInterval:3.0
      block:^(NSTimer* timer) {
       self.counterLabel3.text =
     [NSString stringWithFormat:@"%d", counter3];
       counter3 += 100;
       NSLog(@"%@", array);
      }
    repeats:YES];
前回と比べると userInfo が無くなった。また auto変数 array への参照を Block内に記述してある。実行すると NSLog出力で array の内容が参照出来ていることがわかる。
NSTimerBlocks[4485:207] (
    DOG,
    CAT,
    MONKEY
)


block内でのauto変数参照について


これまで見てきた通り、block内ではその外側で定義されている auto変数への参照も可能である。blockがどのようにして auto変数を扱っているかデバッガで調べてみた。

+[NSTimer executeBlock__] が呼び出された時の引数 block の内容は次の通り。

block のメンバ変数に self と array が存在することがわかる。恐らくコンパイラが blockクラスを定義するときに参照している変数を、blockのメンバ変数として定義するのだと思われる。ただ以前の調査では、blockがスタック上にある場合はスコープを外れるとデバッガ上でこれらのメンバ変数が消えていたことが確認できているので、追加・除去は動的に行われるようだ。

NSTimer を Blocks で処理する

2010年8月6日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

調子に乗って今度は NSTimer に Blocks を導入してみた。

利用イメージはこんな感じ。
[NSTimer scheduledTimerWithTimeInterval:1.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel.text =
   [NSString stringWithFormat:@"%d", counter++];
   }
 userInfo:nil
 repeats:YES];


実装


NSTimer のカテゴリとして実装した。NSTimer 発火時に呼ばれるメソッドを受け取るインスタンスが必要になるが、今回は NSTimerクラスとした(つまりクラスメソッドを呼び出させる)。このあたりは少々違和感はあるが、一番簡単だと思われるのでそうした。

まずはヘッダから。
typedef void (^TIMER_BLOCK__)(NSTimer*, id);

@interface NSTimer (Extension)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
 block:(TIMER_BLOCK__)block userInfo:(id)userInfo repeats:(BOOL)repeats;
@end
ブロックの定義は何箇所かで使うので typedef で定義してみた。

実装。
#define KEY_BLOCK  @"block"
#define KEY_USERINFO @"userInfo"

+ (void)executeBlock__:(NSTimer*)timer
{
 if (![timer isValid]) {
  return;
  // do nothing
 }

 NSDictionary* context = [timer userInfo];
 TIMER_BLOCK__ block = [context objectForKey:KEY_BLOCK];
 id userInfo = [context objectForKey:KEY_USERINFO];

 block(timer, userInfo);
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
 block:(TIMER_BLOCK__)block userInfo:(id)userInfo repeats:(BOOL)repeats
{
 NSMutableDictionary* context = [NSMutableDictionary dictionary];

 [context setObject:[[block copy] autorelease]
    forKey:KEY_BLOCK];
 if (userInfo) {
  [context setObject:userInfo forKey:KEY_USERINFO];
 }

 NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:seconds
    target:self
  selector:@selector(executeBlock__:)
  userInfo:context
  repeats:repeats];
 return timer;
}
ポイントは userInfo を NSDictionary へ入れ替え、そこへ渡ってきた blockを詰めていること。これを発火時(executeBlock__:)に取り出して実行している。block の引数として NSTimer の他、userInfo を渡しているが、これは NSDictionary への入れ替えを行うので -[NSTimer userInfo] が使えないため。

block は copy すると同時に autorelease をかけている。NSTimer のインスタンスが破棄されるタイミングで (NSMutableDictionary*)contextも開放されると思うので、autorelease をかけておけば同時に block も開放されると思われる(多分)。

利用側はこんな感じ。
[NSTimer scheduledTimerWithTimeInterval:1.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel.text =
   [NSString stringWithFormat:@"%d", counter++];
   }
 userInfo:nil
 repeats:YES];

サンプル


NSTimerを使って数字をカウントアップするサンプルを作ってみた。異なる周期を持つ3つのタイマー(1秒、2秒、3秒)を作り、画面上の数字をカウントアップする。

ちゃんと動いているようだ。

コードはこんな感じ。
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel1.text =
   [NSString stringWithFormat:@"%d", counter1++];
   }
 userInfo:nil
 repeats:YES];

timer2 = [NSTimer scheduledTimerWithTimeInterval:2.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel2.text =
    [NSString stringWithFormat:@"%d", counter2];
    counter2 += 10;
   }
   userInfo:nil
 repeats:YES];

timer3 = [NSTimer scheduledTimerWithTimeInterval:3.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel3.text =
    [NSString stringWithFormat:@"%d", counter3];
    counter3 += 100;
   }
   userInfo:nil
 repeats:YES];

※ソースコードはGitHubからどうぞ。
NSTimerBlocks at 2010-08-06 from xcatsan's iOS-Sample-Code - GitHub

参考情報


自前で Blocks を実装した他の例など。
Cocoaの日々: 遅延実行を Blocksで記述する
-[NSObject performSelector:afterDelay:] に Blocksを導入してみた。
Cocoaの日々: UIActionSheet を Blocks で処理する
UIActionSheetDelegate を使わず Blocks を使ってボタン押下時の処理を書く。

- - - -
Blocks が使えると、たった一個とはいえメソッドを作らなくて楽。ましてや複数タイマーを使う場合も面倒が無くかなりいい。可読性もよくなる。Blocks便利だ。

(追記) Block内では、self の他、そのBlockのレキシカルスコープで定義された他の auto変数も参照することができる (*)。そう考えると userInfo が不要なので無くすことができる。こんな感じ。
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
     block:^(NSTimer* timer) {
      self.counterLabel1.text =
    [NSString stringWithFormat:@"%d", counter1++];
      NSLog(@"%@", array);
     }
   repeats:YES];

次回、検証してみる。


(*) 実行側で copyしてヒープへ移しておく必要はある。

人気の投稿(過去 30日間)