【Xcode】設定しておくと便利なカスタマイズいろいろ

Posted by: daichi  /  Category: 開発補助

いろいろカスタマイズして便利にします。

外観

デバッグコンソールなどを1つのウィンドウに統合する

シミュレータでアプリを起動すると、デバッガコンソールがXcodeの後ろに隠れてしまい、いちいちフォーカスを切り替えるのが面倒。そんなときはこの設定を。
Xcode→環境設定→全般→レイアウト→オールインワン

アプリ実行時にデバッガを自動的に表示する

Xcode→環境設定→デバッグ→開始時→コンソールとデバッガを表示

__MY_COMPANYNAME__を変更する

ファイルを新規に作成する時にファイル作成者の情報等が自動生成されるが__MY_COMPANYNAME__が気持ち悪い。そんな時は、コンソールから以下のコマンドを叩く。YourNameHereを表示したい名前に置き換えて。

defaults write com.apple.Xcode PBXCustomTemplateMacroDefinitions
   '{ORGANIZATIONNAME="YourNameHere";}'



プリプロセッサ

デバッグ用マクロ

デバッグ時はNSLogメソッドをよく使うが、リリースビルドにはNSLogを使いたくない。そんな時はプリプロセッサでDebugモードの時だけ、NSLogを吐くマクロを定義すればいい。
Global.h

?View Code OBJECTIVE-C
#ifdef DEBUG 
#define LOG(...) NSLog(__VA_ARGS__)
#else
#define LOG(...) 
#endif



info.plistでDebug構成時のみ、GCC_PREPROCESSOR_DEFINITIONS項目にDEBUG文字列を設定する。

すると、LOG(@”hogehoge”)でDebug構成時のみ、NSLogを吐けるようになる。

テンプレート

IBを使わないテンプレートを作成する

Interface Builderは最近は使わなくなったので、新しいプロジェクトを作る場合に、いちいち削除したりするのが面倒。そんな時はプロジェクトテンプレートをカスタマイズする。

Xcodeのプロジェクトテンプレートは、以下の場所においてある。
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application

ユーザ定義のテンプレートは以下の場所に置くとXcodeに認識される。
~/Library/Application Support/Developer/Shared/Xcode/Project Templates/Application/

Windowベースのアプリケーションをベースにカスタマイズする。オリジナルのWindow-based Applicationをユーザ定義の方にコピーしてWindow-based-non-IB Applicationにリネーム。

ディレクトリ内の___PROJECTNAME___はすべてプロジェクト名に置き換えられる。

変更するのは以下。
  • info.plistのMain nib file base name→空に
  • main.m内のUIApplicationMainメソッドの第4匹数に@”___PROJECTNAME___AppDelegateを”
  • MainWindow.xib→削除
  • ___PROJECTNAME___.xcodeproj/project.pbxproj→MainWindow.xibに関する部分をすべて削除
これでxibファイル抜きのテンプレートができあがり。

個人的に___PROJECTNAME___AppDelegateという名前が長ったらしくてあまり好きではないので、これをAppDelegateにした。
この時の変更箇所は以下。
  • main.m→@”___PROJECT_NAME___AppDelegate”を@”AppDelegate”へ
  • ___PROJECT_NAME___AppDelegate.h→ファイル名をAppDelegate.hへ。中身も該当箇所を修正
  • ___PROJECT_NAME___AppDelegate.m→ファイル名をAppDelegate.mへ。中身も該当箇所を修正
  • ___PROJECT_NAME___.xcodeproj/project.pbxproj→___PROJECT_NAME___AppDelegate部分をすべてAppDelegateへ修正

.gitignore、.gitattributeをテンプレートに含める

gitを使いだすとgitの設定ファイルである.gitignoreでXcode用ファイル達を除外したくなるが、これを毎回プロジェクトを作る度にコピーしてくるのは面倒なので、テンプレートに含めてしまう。やり方は上のテンプレートディレクトリに.gitignore、.gitattributeファイルを入れるだけ。
.gitignoreは

# xcode noise
build/*
*.pbxuser
*.mode1v3
 
# old skool
.svn
 
# osx noise
.DS_Store
profile


.gitattributesは

*.pbxproj -crlf -diff -merge


ここまでのテンプレート設定ディレクトリをいちおのせておく。これをダウンロードして、ローカルの
~/Library/Application Support/Developer/Shared/Xcode/Project Templates/Application/
に置けば、使えると思う。
Window-based-non-IB Application
これには次のログマクロテンプレートも含まれている。

ログマクロをテンプレートに含める

さきほど作ったログマクロをテンプレートに含めるのは、テンプレートディレクトリ内にログマクロを記述したファイルを追加すればOKだが、ファイルを開発時にファイルを新規作成するたびに、
#import “Global.h”
とするのは面倒だ。

なので、ファイル作成時に既に#import “Global.h”を追記されているファイルテンプレートを作成する。

オリジナルのファイルテンプレートは以下に置いてある。
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/File Templates/Cocoa Touch

このCocoa Touchディレクトリをユーザ定義用ファイルテンプレートディレクトリである
~/Library/Application Support/Developer/Shared/Xcode/File Templates/
へコピー。

あとはファイルを好きなように変更すればそれがテンプレートになる。

Cocoa Touchのすべてのファイル作成時にGlobal.hを含むように設定したファイルテンプレートはこれ。
Cocoa Touch Class
これを
~/Library/Application Support/Developer/Shared/Xcode/File Templates/
へ入れれば、ユーザ定義ファイルとして使える。


よく使うフレームワークをテンプレートに含める

例えば、Three20だったり、JSONフレームワークだったり、GTMだったり、こういうよく使うフレームワークはあらかじめ使える準備の整ったプロジェクトテンプレートが欲しくなる。これも上のやり方同様、コピーしたローカルのテンプレート周りをいじくり倒して設定できる。

マクロ

よく使うメソッドをマクロとして登録する

Xcodeでは、Ctrl+.でマクロを呼び出せる。
例えばinitと打って、Ctrl+.を押すと、NSObjectのinitメソッドが挿入される。他にもlog、deallocなどが予めマクロとして登録されている。
が、ViewController周りのメソッド達は登録されていないので、いちいちviewWillAppear〜などとよく使うメソッドを入力しなければならない。これは面倒だ。ということでマクロを登録する。

オリジナルのマクロ定義ファイルは以下にある。
/Developer/Applications/Xcode.app/Contents/PlugIns/TextMacros.xctxtmacro/Contents/Resources/ObjectiveC.xctxtmacro

これをユーザ定義のマクロファイル置き場である以下にコピーする。
~/Library/Application Support/Developer/Shared/Xcode/Specifications

マクロとして
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
を追加する場合は、ファイル内に以下のような記述を追加する。

        {
            Identifier = objc.didselect;
            BasedOn = objc;
            IsMenuItem = NO;
            Name = "didSelectRowAtIndexPath";
            TextString = "- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {¥n¥t<#statements#>¥n}";
            CompletionPrefix = didselect;
        },


内容の説明
  • Identifier→ユニークな識別ID。
  • Name→Xcodeで編集→テキストマクロを挿入→Objective Cと行った時に表示される名称
  • TextString→マクロ実行時に挿入される文字列
  • CompletionPrefix→マクロ発動文字列。例の場合、didselectと入力してCtrl+.を押すとマクロが実行される
他の項目はおおむね上の例のままで問題ない様子。

ひとまず
viewWillAppear, viewDidAppear, viewDidLoad, viewWillDisappear, viewDidDisappear, cellForRowAtIndexPath, didSelectRowAtIndexPathなどを登録してみた。なかなか悪くない。

vwaと入力してCtrl+.でviewWillAppear発動。他もvda、vdl、vwd、vdd、cellfor、didselectで発動するのでいい感じ。

この設定済みマクロはこちらから。
ObjectiveC.xctxtmacro

参考

【iPhone】アプリ内でWEBアプリ感覚で画面遷移する

Posted by: daichi  /  Category: iphone開発

iPhoneアプリで画面遷移させる場合、コントローラをalloc/initしてナビゲーションコントローラのスタックにプッシュしたり、modalしたりする流れになるわけですが、複数の画面から呼ばれる画面だったりするとインスタンスの管理が面倒だったり、決まり文句をいちいち書くのが面倒になります。

そこでThree20のTTNavigatorを使うと、すっきり画面遷移できるようです。

webアプリ感覚で”http://about”のように遷移先を指定するだけで、その画面を表示できるようになります。
httpの部分は任意の文字列を定義できます。

そのためには、まずURLとコントローラのマッピングが必要です。

簡単にサンプルを示します。

?View Code OBJECTIVE-C
#import "DCAppDelegate.h"
#import "DCTwitterController.h"
#import "DCGoogleReaderController.h"
#import "DCAboutController.h"
#import "DCTabBarController.h"
 
@implementation DCAppDelegate
 
- (void)applicationDidFinishLaunching:(UIApplication *)application {
 
	TTNavigator *navigator = [TTNavigator navigator];
	navigator.supportsShakeToReload = YES;
	navigator.persistenceMode = TTNavigatorPersistenceModeTop;
 
	TTURLMap *map = navigator.URLMap;
	[map from:@"*" toViewController:[TTWebController class]];
	[map from:@"dc://tab" toSharedViewController:[DCTabBarController class]];
	[map from:@"dc://twitter" toViewController:[DCTwitterController class]];
	[map from:@"dc://reader" toViewController:[DCGoogleReaderController class]];
	[map from:@"dc://web" toViewController:[TTWebController class]];
	[map from:@"dc://about" toViewController:[DCAboutController class]];
	[map from:@"dc://compose?title=(compose:)" toModalViewController:[DCAboutController class]];
 
    [navigator openURL:@"dc://tab" animated:NO];
 
}
 
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)URL {
	[[TTNavigator navigator] openURL:URL.absoluteString animated:NO];
	return YES;
}
@end


まずTTNavigatorクラスのインスタンスを取得します。
TTNavigator.supportsShakeToReloadをYESにすると、iPhoneを振ると、リロードをしてくれるようになります。
TTNavigator.persistenceModeはアプリ終了時にどの画面を開いていたかを記憶する設定です。persistenceModeAllを指定すると常に最後に開いていた画面を記憶し、次回起動時にその画面を表示するようになります。

その後のTTURLMapがURLとコントローラのマッピング情報を保持するクラスです。
from:toViewControllerメソッドでは、fromに指定されたURLが呼ばれるとtoViewControllerの画面を表示する、というような設定になります。
fromに”*”を指定すると、マッピングにないURLが指定された場合のコントローラを指定できます。

URLを指定したら、TTNavigatorのopenURL:animated:メソッドを呼べば画面遷移が行われます。

TTNavigatorはシングルトンなため、どの画面からでも同一インスタンスを取得でき、遷移したい時にopenURL:animated:メソッドを呼んでやればよい、という感じです。

TabBarコントローラのような全画面で共通で使いたいコントローラの場合は
from:toSharedViewController:メソッドを使うと一つのインスタンスを使い回せるようです。

また、コントローラに渡すパラメータも指定できます。

?View Code OBJECTIVE-C
[map from:@"dc://compose?subject=(compose:)" toModalViewController:[DCAboutController class]];

の部分がそれで、これはDCAboutControllerクラスのcompose:メソッドが呼ばれます。

パラメータの受け取りは以下のように行います。

?View Code OBJECTIVE-C
#import "DCAboutController.h"
 
@implementation DCAboutController
 
- (void)createModel {
	self.dataSource = [TTSectionedDataSource dataSourceWithObjects:
						@"About",
						[TTTableTextItem itemWithText:@"サイト" URL:@"http://iphone.longearth.net"],
						[TTTableTextItem itemWithText:@"ご意見ご要望" URL:@"dc://compose?subject=%5biSlot%20Pro%5d%e6%84%8f%e8%a6%8b%2f%e8%a6%81%e6%9c%9b"],
					   [TTTableTextItem itemWithText:@"友達にすすめる" URL:@"dc://compose?subject=&body=http%3a%2f%2fitunes%2eapple%2ecom%2fWebObjects%2fMZStore%2ewoa%2fwa%2fviewSoftware%3fid%3d304074830%26mt%3d8"],
						@"関連アプリ",
					   [TTTableTextItem itemWithText:@"iPachi" URL:@""],
					   [TTTableTextItem itemWithText:@"iSlot Pro" URL:@""],
					   [TTTableTextItem itemWithText:@"iTaskTimer" URL:@""],
						nil];
}
 
- (UIViewController *)compose:(NSString *)subject query:(NSDictionary *)param {
	TTMessageController* controller =
    [[[TTMessageController alloc] init] autorelease];
	controller.subject = subject;
	controller.body = [param objectForKey:@"body"];
	return controller;
}
@end

第一引数にはsubjectで指定したものが、第二引数以降にも&繋ぎで複数パラメータが指定でき、中身はDictionaryとして取り出すことができます。
上の例ではパラメータを受け取り、それらの値を使って、メール送信画面を起動しているところです。

createModelメソッドはTTTableViewControllerでテーブルデータを表示する際に呼ぶメソッドで、ここではあまりふれません。
が、少しだけ触れておくと、TTTableViewControllerのdatasourceがテーブルデータとなり、TTSectionDataSourceを入れています。TTSectionDataSourceは引数にNSStringがあればそれをセクション名とし、それ以降のものをセクション内に属するデータとして扱います。それからそのデータとしてTTTableTextItemがあり、textで表示する文字を、URLでタップした時の遷移先を指定しています。以上でこんな画面になります。
About画面

URL形式での画面遷移でもすごい画期的なのに、まだまだThree20にはすごい部分が盛りだくさんです。
マジすごい。

【AppStore】【iPhone】Cameraアプリはもうダメかも

Posted by: daichi  /  Category: アプリ

以前Appleに申請していたFakeCameraがまたもやリジェクトされた。
今度は非公開APIを使っているからダメですよ。という内容。
ちょっとシャッターボタンのビュー掘り起こして、ボタン押すイベント横取りしてシャッター音出しただけなのに、やっぱりダメなのね。

シャッター音使ったことには何も言ってこなかったんだよなぁ。
こっちはいいってことなのだろうか。
カメラに過敏になってるのだろうか。

んーこれは本当にお蔵入りの様相を呈してきた。

ちょっと一区切りついたので、一旦たまってきた旧アプリのバージョンアップを端からやりつつ、騙し騙し再申請とかしてみようかと思う。

Google Book SearchアプリはGoogleさんから止められてしまったし、なんかお蔵入りが続いている。

とはいえ、最強情報収集&発信アプリもThree20で作成中なので、それはそれで焦らずに取り組んで行こうと思う。

ひとまずtwitterのタイムラインとはてぶのほってんとりとGoogle Readerの一覧見れて、どの一覧からでもブラウザ開いてマルチポストできるアプリ。
ブラウザでタップ長押しで出てくるツールチップに「ポスト」を追加して、そこからブックマークしたり、つぶやいたり。

一旦小休止です。

Three20とJSON-FrameworkでTwitterのタイムライン画面を作る

Posted by: daichi  /  Category: iphone開発

Three20 JSON datasource implementation – revetkn.com

Three20とjson-frameworkを使って簡単にタイムライン画面を作ってみる。

それぞれセットアップは以下を参考に

【Three20】をプロジェクトで使えるようにする手順 | iphoneアプリで稼げるのか
[iPhone] JSON Framework の使い方(準備編) | Sun Limited Mt.

※JSONFramework側の不具合で上記リンクの通り設定した場合でも、OS3.0で使うと実機転送時にcode signエラーが出た。そんな時は「他のリンカフラグ」等の設定はせずに、JSONディレクトリの中身のファイル達を直接プロジェクトに追加で回避できる。6月末に修正されたらしいので最新版を使えばだいじょぶかもしれない。
json-framework – Google Code

最低限のAPI仕様は

  1. http://twitter.com/statuses/friends_timeline.jsonへ
  2. ベーシック認証つきで
  3. GETメソッドでリクエストを投げる
上記3点が満たされていればOK。

例によってTwitter APIの詳細は公式wikiに譲る。
Twitter API Wiki / Twitter REST API Method: statuses friends_timeline

Three20の中でも今回は

を使う。

コードは以下。

コントローラ

FirstViewController.h

?View Code OBJECTIVE-C
#import
#import "Three20/Three20.h"
 
@interface FirstViewController : TTTableViewController {
 
}
 
@end


FirstViewController.m

?View Code OBJECTIVE-C
#import "FirstViewController.h"
#import "JSONDataSource.h"
 
@implementation FirstViewController
- (void)loadView {
	self.view = [[[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame] autorelease];
	self.view.backgroundColor = RGBCOLOR(240, 242, 245);
	self.variableHeightRows = YES;
       // STATUS_HEIGHTはTTGlobal.hで定義されてるステータスバーの高さを表す定数
	self.tableView = [[[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480 - STATUS_HEIGHT)
												   style:UITableViewStylePlain] autorelease];
	self.tableView.autoresizingMask =
    UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
	self.tableView.sectionIndexMinimumDisplayRowCount = 2;
	[self.view addSubview:self.tableView];
}
 
// この辺はTTTableViewControllerのdelegateメソッド
// ここで返したデータソースを使ってTTTableViewControllerが描画もろもろやってくれる
- (id)createDataSource {
	JSONDataSource *dataSource = [[[JSONDataSource alloc] init] autorelease];
	[dataSource load:TTURLRequestCachePolicyNoCache nextPage:NO];
	return dataSource;
}
 
- (UIImage*)imageForError:(NSError*)error {
	return [UIImage imageNamed:@"Three20.bundle/images/error.png"];
}
 
@end


データソース

JSONDataSource.h

?View Code OBJECTIVE-C
#import
#import "Three20/Three20.h"
 
@interface JSONDataSource : TTListDataSource {
@private
        // 読み込み中フラグ
	BOOL _isLoading;
       // 読み込み完了フラグ
	BOOL _isLoaded;
       // ここいらのフラグを見て読み込み中Activityとか出し分けしてる様子
}
 
@end


JSONDataSource.m

?View Code OBJECTIVE-C
#import "JSONDataSource.h"
 
static	NSString *username = @"username";
static	NSString *password = @"password";
 
@implementation JSONDataSource
 
- (id)init {
	if (self = [super init]) {
        	_isLoading = YES;
		_isLoaded = NO;
	}
	return self;
}
 
#pragma mark TTTableViewDataSource
 
- (void)load:(TTURLRequestCachePolicy)cachePolicy nextPage:(BOOL)nextPage {
       // この辺はTwitterポスト時とほぼ同じ
	static NSString *jsonUrl = @"http://%@:%@@twitter.com/statuses/friends_timeline.json";
	NSString *url = [NSString stringWithFormat:jsonUrl, username, password];
 
	TTURLRequest *request =
    [TTURLRequest requestWithURL:url delegate:self];
	request.cachePolicy = cachePolicy;
	request.response = [[[TTURLDataResponse alloc] init] autorelease];
	request.httpMethod = @"GET";
 
	BOOL cacheHit = [request send];
	NSLog((cacheHit ? @"Cache hit for %@" : @"Cache miss for %@"), jsonUrl);
}
 
#pragma mark TTLoadable
 
- (BOOL)isLoading {
	return _isLoading;
}
 
- (BOOL)isLoaded {
	return _isLoaded;
}
 
#pragma mark TTURLRequestDelegate
 
- (void)requestDidStartLoad:(TTURLRequest*)request {
	_isLoading = YES;
	_isLoaded = NO;
	[self dataSourceDidStartLoad];
}
 
- (void)requestDidFinishLoad:(TTURLRequest*)request {
        // 通常通りレスポンスゲット
	TTURLDataResponse *response = request.response;
	NSString *responseBody = [[NSString alloc] initWithData:response.data encoding:NSUTF8StringEncoding];
 
        // JSONFrameworkのメソッドでArrayとして受け取る。JSONの構造次第でNSDictionaryの可能性もあるのでAPI要確認
	NSArray *json =  [responseBody JSONValue];
 
	for(NSDictionary *result in json) {
                // JSON内のデータ構造はTwitter APIを参照
		NSDictionary *user = [result valueForKey:@"user"];
                // データソースの要素としてTTIconTableFieldをセット。これでアイコンとテキストがワンセットのテーブルになる。
		[self.items addObject:[[[TTIconTableField alloc]
                                                                // JSONからテキスト取得
								initWithText:[result objectForKey:@"text"]
								url:nil
                                                                // JSONから画像取得
								image:[user objectForKey:@"profile_image_url"]
                                                                // 画像読み込み前のデフォルト画像を指定。ここは自分で用意
								defaultImage:[UIImage imageNamed:@"DefaultAlbum.png"]] autorelease]];
	}
 
	_isLoading = NO;
	_isLoaded = YES;
	[self dataSourceDidFinishLoad];
}
 
// この辺はNSURLRequestでおなじみな感じ
- (void)request:(TTURLRequest*)request didFailLoadWithError:(NSError*)error {
	NSLog([error localizedDescription]);
	NSLog(@"didFailLoadWithError");
	_isLoading = NO;
	_isLoaded = YES;
	[self dataSourceDidFailLoadWithError:error];
}
 
- (void)requestDidCancelLoad:(TTURLRequest*)request {
	NSLog(@"requestDidCancelLoad");
	_isLoading = NO;
	_isLoaded = YES;
	[self dataSourceDidCancelLoad];
}
 
@end


以上で、こんな画面の出来上がり。

timeline

これだけで、ネットワーク接続エラー時の対応やデータキャッシュや読み込み中表示などをこちらが意識せずとも勝手にやってくれる。
※表示項目をよりtwitterクライアントにしたい場合は別途カスタマイズが必要。

JSONに関してはアクセス先のURLと取り出し方法の2つが外部APIに依存する部分なので、その辺をdelegateに任せるなりすればこのソースをうまいこと活用できると思う。

【iPhone】【Three20】doxygenで神ライブラリのドキュメント生成してみた

Posted by: daichi  /  Category: 開発補助

iPhoneのPhotoアプリ再現どころではない神ライブラリ見つけた | iphoneアプリで稼げるのか

UIViewから自力で作ってる

まだほとんどソースを見切れてないけど、とりあえず全体を流しみるためにも、doxygenを使ってドキュメント生成してみた。


Three20ドキュメント
(このドキュメント先、一時的に借りてるサーバなので後でURL変わるかも)


神ライブラリ。ソース見ればみるほどすごいっす。
Photoビューア部分とかUIScrollViewをうまく使っているのかなと始めは思っていたのですが、UIViewから独自のTTScrollViewを1から作り込んでいたのですね。ん〜なるほどです。

続きを読む

【Three20】をプロジェクトで使えるようにする手順

Posted by: daichi  /  Category: 開発補助

サンプル

神ライブラリThree20をプロジェクトで利用する手順。

続きを読む

iPhoneのPhotoアプリ再現どころではない神ライブラリ見つけた

Posted by: daichi  /  Category: 開発補助

以前見つけたphotoビューザサンプルコードを遥かに凌駕するオープンソースのライブラリを見つけてしまいました。

こんなことが簡単にできてしまう様子。

かっこいいラベルphotoビューアphotoライブラリタブコントローラyoutube的なあれインクリメンタルなあれ
続きを読む

Get Adobe Flash playerPlugin by wpburn.com wordpress themes