2011年8月9日火曜日

[UITableViewController scrollToRowAtIndexPath:atScrollPosition:animated:] の挙動まとめ

UITableViewController の scrollToRowAtIndexPath:atScrollPosition:animated: メソッドは、対象のテーブルビューのセクションにヘッダ・フッタが付いている場合挙動が変化する事がわかったので、ちょっと調査してまとめてみました。具体的には以下のような動きをするようです。

  • このメソッドは自分で呼び出すか、またはテーブルビューのセルの中に UITextField のようなフォーカスを取るコントロールを配置して、それが選択されたときに呼び出される
  • このメソッドで指定した indexPath の section に Header View or Header Text / Footer View or Footer Text が指定されているとき、このメソッドは選択された indexPath の row だけではなく、それらのヘッダやフッタも同時に表示される位置にスクロールしようとする
  • ということであんまり長い Section Header / Section Footer を作ると scrollToRowAtIndexPath:atScrollPosition:animated: の挙動がおかしくなる
  • Table View Header / Table View Footer については全然無関係なので長くしても大丈夫
画像にすると以下のような感じになります。

初期状態

section3つ、row3つ、合計9行のtable viewを作って、それぞれにsection header / section footerを追加しました。このテーブルビューを使って実験を行います。

UITableViewScrollPositionTopを指定してスクロール
section0, row0section0, row1
section0, row2
section1, row0

UITableViewScrollPositionTopを指定すると、sectionの一番上のrowが指定された場合のみ、そのsectionのsection headerの高さを考慮してスクロールするようになります。

UITableViewScrollPositionMiddleを指定してスクロール
section0, row0section0, row1section0, row2
section1, row0section1, row1section1, row2

UITableViewScrollPositionMiddleの場合は特にsection header / section footer関係なく、中央に選択された行が来るようにスクロールするようです。

UITableViewScrollPositionBottomを指定してスクロール
section0, row0section0, row1section0, row2
section1, row0section1, row1
section1, row2

UITableViewScrollPositionBottomを指定すると、sectionの一番下のrowが指定された場合のみ、そのsectionのsection footerの高さを考慮してスクロールするようになります。

2011年8月5日金曜日

自分流 View Controllerの作り方 その2



その1はこちら

ぼくのかんがえたさいきょうのせっけいです
主に以下の書籍に影響受けまくりであります
0321127420Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))
Martin Fowler
Addison-Wesley Professional 2002-11-05

by G-Tools
4798116831レガシーコード改善ガイド (Object Oriented SELECTION)
マイケル・C・フェザーズ ウルシステムズ株式会社
翔泳社 2009-07-14

by G-Tools


図を適当に補足
ViewWrapperは既存のすでにあるどうしようもない設計のViewを何とか救いたいときに非常に便利、Wraper / Decoratorパターンを適用してボタンのタップを奪い取ってViewHelperに流すみたいな役目をする
ViewHelperは簡単に言うならUITableViewControllerのdelegate, datasourceだけを担うオブジェクトみたいな感じ。要するにView専用のドメインロジックを書くオブジェクト
Viewの表示を制御するドメインロジックが途方もなく大きくてViewControllerに納めるのが不可能になってしまったときに超便利

Serviceは準ドメインロジックだと思っている、たいていの場合セミシングルトンみたいにする(通信が絡むので複数画面をまたいで使うことがほとんど)
Androidの場合はここ、普通にServiceクラスでいいんじゃないでしょうかね

Managerはドメインロジックというよりもプロセス外へのリソースアクセスを行うためのクラスというイメージ、個人的にはゲートウェイみたいな感じ
  • NSFileManager
  • NSUserDefaults
  • KeychainAccessManager
  • InAppPurchaseManager
  • APIConnectionManager
Modelはドメインモデルです、ほとんどの場合はCoreDataのNSManagedObject。用がないときでもCoreData使っておけ(超便利)
と思ってましたがCoreDataのモデルは特にN:N関係を正しく扱わないと簡単に問題が発生してしまいますので、安易に採用すると危険かも。

Task, Operationってのは非同期で実行されていく特別なドメインロジックのイメージ。要するにAPI通信みたいなもんです
API通信を複数束ねて使ったりとか並列実行したりとかの制御が絶対必要になるのでそういうときに使う via @monjudoh
// MyTaskが終わったらMySuperTaskを実行して、それが終わったらさらにMySuperDuperTaskを連続して実行したい
// 終わったらselfに通知させたい
Task *root = [[[MyTask alloc] init] autorelease];
root.nextTask = [[[MySuperTask alloc] init] autorelease];
root.nextTask.nextTask = [[[MySuperDuperTask alloc] initWithId:100] autorelease];
root.delegate = self;
[root start];

自分流 View Controllerの作り方 その1



その2はこちら

以前勉強会の際に発表した View Controller の作り方のメモをまとめてみました。あくまでメモなので中身はうまくまとまっていませんが、何かのご参考になればと思います。




通信が絡んでくると、たいていの人がやりがちな問題(実例)
  • API通信のレスポンスを処理するコードがViewControllerの中に入っている
  • API通信が3種類必要で、Aを実行したあとにBとCを実行しなければならないとか
  • ABCのレスポンスJSONのパースまでViewControllerでやっている
  • というかAPIの呼び出しの組み立てだとかURLの指定だとか自体がIBActionの中に入っていたりする
API通信だけじゃなくてIn App Purchaseなどでも同様の事例が見られる

それに対する対応策。そもそもなぜこのような問題が発生するのか?
  1. Outletの生成・更新・レイアウトが分離されていない
    • そのため複数回画面が更新されるタイミングが発生するととたんに破綻する
    • 大変よく見かける初心者コードが"drawXXX"という名前のOutletを生成してデータをセットしてframeまでセットして画面に配置するコード
    • Outletを描画コードと勘違いしている。Outletはペンやブラシに相当するものであって、実際に線を書くコードではない
    • この初心者コードでも動く唯一の理由は画面が一回(viewWillAppear時)しか更新されないから
  2. 通信という(比較的大きくなりがちな)ドメインロジックがViewControllerに混入している
そこで問題1.に対応するためにViewControllerの中でやる作業を以下のように分割する
  • Outletを生成する
    • preload(一度に生成する方法)
    • lazy load(必要になったら生成し、必要でなくなったら捨てる方法)
  • Outletのプロパティを更新する
  • Outletをレイアウトする
これらはそれぞれ(基本的に)以下のようなUIViewControllerのメソッドが対応する
  • loadView
    • Outletをpreloadする場合はコレで全く問題ない。このとき、self.viewとここで作られたOutletの生存期間は等しくなる
    • Outletをlazy loadする場合はOutletを生成するコードと削除するコードを用意しておいて、必要なタイミングで呼び出すとかする
  • なし
    • プロパティを更新するために、たとえばupdateOutletsみたいなメソッドを自分で用意してやる
  • 各種willRotate...系メソッド
    • willRotateほげほげの中にレイアウトコードを入れておくと自動的に画面の回転にも対応できて超便利
    • 自動的に必要なタイミングで適切に呼び出ししてくれて超便利
    • そういうのが嫌いな人はlayoutOutletsForInterfaceOrientation:みたいなメソッドでも作ればいいんじゃないでしょうか
次に問題2.に対応するためにAPIの呼び出しやファイルアクセスなどはService, Managerなどの層を作ってそちらに任せる
決してViewControllerの中にドメインロジックを混入させないのが大事
→混入するとドメインロジックとビューナビゲーションロジックが混ざって大爆発する
→さらにドメインロジック自身も複雑な通信が必要になると大爆発する

それとは別にイベントを受け取ってViewの状態を制御する大事なお仕事をする必要がある
  • ボタンタップしたり
  • 画面をタップしたりパンしたり
  • スクロールが発生したり
  • APIコールが完了したり
  • In App Purchaseが完了したり
ここまでがView Controllerのお仕事。決してドメインロジックを混ぜないのがポイント

2011年7月31日日曜日

Objective-C で文字列リテラルに \0 を含めたいときの作戦

Xcode 4.0 から LLVM が標準のコンパイラとなり、各種警告が非常に厳しくなっています。その中でも特に今回は文字列リテラルに \0 が含まれているときの警告について回避策を発見したのでご紹介したいと思います。

Objective-C では文字列リテラルは @"abesi" のように @"" で囲んで表現します。このリテラルは(あくまで推測で確定ではないのですが)コンパイラによってコンパイル時に CFSTR("abesi") に置換され、 CFStringRef 型としてプログラム中に定義されているようです。さて問題はここからで、 Xcode 4.0 が内部的に構文解析のために使っている LLVM がこのリテラル中に \0 、要するにNULL文字が含まれていると以下のような警告を出すようになってしまったのです
CFString literal contains NUL character
普通はNULL文字をリテラル中に含めたいということはまず無いのですが、SMTPの通信部分を書いたりしていると通信プロトコル自体が命令を \0 で区切れみたいな要件を求めてくるため、どうしてもリテラルの中に \0 を含めたいときが出てきます。 まぁ、具体的には以下のライブラリなんですけどね。

http://code.google.com/p/skpsmtpmessage/source/browse/trunk/SMTPSender/Classes/SKPSMTPMessage.m
sendState = kSKPSMTPWaitingAuthSuccess;
NSString *loginString = [NSString stringWithFormat:@"\000%@\000%@", login, pass];
NSString *authString = [NSString stringWithFormat:@"AUTH PLAIN %@\r\n", [[loginString dataUsingEncoding:NSUTF8StringEncoding] encodeBase64ForData]];
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
そして私と同じライブラリの同じ箇所でぶつかった人を発見。

http://stackoverflow.com/questions/5814520/how-do-i-create-a-login-string-that-contains-2-embedded-nulls-and-the-login-an

さて回避方法はないものかと探してみたところ、ちょうど良い回避策が発見できました。

http://stackoverflow.com/questions/6211908/nsstring-with-0

NSStringのstringWithFormat:に %C を埋め込んで、そこに 0 を渡せばうまくいくみたいです!
int main() {
NSString *string = [NSString stringWithFormat: @"Hello%CWorld!", 0];
NSData *bytes = [string dataUsingEncoding: NSUTF8StringEncoding];
NSLog(@"string: %@", string);
NSLog(@"bytes: %@", bytes);
return 0;
}
さっきの例だと、
sendState = kSKPSMTPWaitingAuthSuccess;
NSString *loginString = [NSString stringWithFormat:@"%C%@%C%@", 0, login, 0, pass];
NSString *authString = [NSString stringWithFormat:@"AUTH PLAIN %@\r\n", [[loginString dataUsingEncoding:NSUTF8StringEncoding] encodeBase64ForData]];
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
このようにすればばっちり上手く回避できました!

2011年7月18日月曜日

iPad の UIWebView で twitter.com を表示したときに出てくるアプリの宣伝広告を出さない方法

iOS アプリ内で Twitter のタイムラインを表示したり、投稿させたり、 Tweet Button を置きたいみたいな要求は結構あると思うのですが、ここで問題になるのが iPad の UIWebViewで twitter.com を表示したときです。以下の画像のように、 Twitter for iPad をオススメする広告が最初に表示されてしまうのです。



一度 Continue on mobile.twitter.com ボタンを押せば次の画面に遷移して二度と表示されなくなるのですが、大抵のお客さんはこの画面を見た瞬間意味不明になってしまうと思うので、表示されないようにしなければなりません。

最初に思いついた方法は以下の



http://mobile.twitter.com/settings/dismiss?d=2

このリンクを NSHTTPConnection なんかで踏ませればいいんじゃないかと思っていたのですが、調査してみた結果もっと簡単に何とかできることがわかりました。このアプリの宣伝広告を消すのに一番簡単な方法は、以下のように Cookie をセットしてやることです。
// mobile.twitter.comにUIWebViewからアクセスしたときに、"Get Twitter for iPad NOW"とかなんとか表示されるのを防ぐため、
// UIWebViewが使うcookieにd=2をセットしておく
// NSHTTPCookieExpiresはセットしなくても大丈夫です(その場合起動ごとにCookieがけされてしまうので、起動時に毎回セットしてください)
NSHTTPCookie *twitterForIPadAdBlockCookie = [NSHTTPCookie cookieWithProperties:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                                @"d", NSHTTPCookieName,
                                                                                @"2", NSHTTPCookieValue,
                                                                                @".twitter.com", NSHTTPCookieDomain,
                                                                                @"/", NSHTTPCookiePath,
                                                                                [NSDate distantFuture], NSHTTPCookieExpires,
                                                                                nil]];
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:twitterForIPadAdBlockCookie];
これだけでご覧の通り!嘘のように広告が出なくなります。




■おまけ: javascript:スキームを使ってみよう

今回のように UIWebView をアプリ内で使って特定のWebアプリを表示する場合にWebアプリの挙動が知りたくなったときは、iPadのSafariブラウザから javascript: スキームを使って JavaScript のコードを流し込んでデバッグするとなかなかはかどると言うことがわかりました。たとえばクッキーを表示させてやるだけなら、以下のように超簡単にできます:
javascript:alert(document.cookie);


クッキーを流し込むのも、流し込むだけならこれまた簡単:
javascript:document.cookie="foo=bar";
より詳細な調査はPC側のブラウザでゆっくりやって、最後にちょっとだけ試したいところを実機でやってみることができるのがいい感じです。多分この用途のためのブラウザアプリなんかも App Store をあされば出てくると思うので、そういうのを使えばさらにはかどると思います。

BPStudy #46 での発表資料 ViewController の作り方を公開してます

かなり旧聞になってしまいますが、 BPStudy #46 で話したときの資料をpreziにアップしてますので、ご覧いただけます。
http://prezi.com/7b_joy2lcfil/bpstudy-46/

発表時にお見せしたソースコードはちょっと公開出来なかったのでこの資料だけだとイマイチよくわからないのですが、要するに
  • View Controllerにサービスを書かないようにしてサービスレイヤを分離しろ
  • View Controllerの描画コードは、アウトレットの生成、アウトレットのデータ更新、アウトレットのレイアウトの3つに分離しろ
って言う感じのお話をしました。

すげー当たり前のことしか書いてなくて恐縮なのですが><
実際、人様のコードをメンテしたりバージョンアップしたりするお仕事をいただいていると、ほとんど全てのコードがこの問題に引っかかっていて非同期通信化すると動かないとかちょっとView Controllerの呼び出され方を変えると動かないとかあって、iOS開発者の皆さんがこういうところに気を配ってコードを書いていただければなぁそして私が楽できればなぁという思いで書いてみた次第であります。

2011年6月21日火曜日

Netlogがハックしたgmailアカウントの情報を用いてspamを送っている?

やや旧聞になるのですが、お恥ずかしながらgmailのアカウントが何らかの手段によってハックされてしまったようで、私のメールアドレスから大量のspamが連絡先に飛んでしまう事件がありました。関係者の皆様には大変ご迷惑をおかけいたしまして誠にすみません><

パスワードは標準以上に強固だったはず(ハックされたアカウントが使っていたパスワードは10桁で大文字小文字数字記号全てランダムに混ざったもの)なのですが、マルウェアも見つからないし心当たりのあるフィッシング被害もないしPSNには登録してないし・・・ということでまったくどこからやられたのかわかりません。恐ろしいです。パスワードだけではもはや不十分ということかも知れません。

さてここからが本題。
Netlog (http://ja.netlog.com/) というSNSがあるのですが、今週の月曜日に知人から続々Netlogへの勧誘メールが私から飛んできたという報告が。

手口はどうも私のgmailの連絡先に入っていたメールアドレスに対して、私がin-reply-toになるようなspamを次々送りつけるというもののようです。他の経路からspamされているのではと思って調べてみたのですが、一番怪しいFacebookについてもセキュリティが破られた形跡はないし、そもそも一度もNetlogなるサービスを聞いたこともないしページを開いたことも友達に勧められたこともありません。さらに私のgmailの連絡先にしか入っていないメールアドレスにもspamが飛んでいるようで、どうもこの盗まれたアカウント情報を元にspamを送っているような気がしてなりません。

あくまで状況証拠なのですが、ともかく皆さんもgmailのセキュリティには万全を期してください><

2011年5月29日日曜日

iPhone iPad 向けスタイラスで書き味勝負をしてみた 2回戦


写真は左から順に、 Pogo SketchoStylus 初期限定生産型パワーサポート スマートペン PBJ-9XシリーズBamboo Stylus

iPhone/iPad向けのスタイラスがいくつも発売されていますが、実際にスタイラスを使ったらどんな具合なの?指でいいんじゃないの?と疑問をお持ちの方と、 私のようなスタイラスマニアの方 のために、iPhone/iPad向けのスタイラスの書き味を実際に書いてみて試してみるという企画です。

前回の記事はこちら: http://akisute.com/2011/02/iphone-ipad.html
おすすめスタイラスまとめ記事はこちら: http://akisute.com/2010/06/ipad.html
Bamboo Stylusのレビューはこちら: http://akisute.com/2011/05/ipad-bamboo-stylus.html
oStylusのレビューはこちら: http://akisute.com/2010/10/ipad-ostylus.html


■比較方法

iPad上で実際にスタイラスでいろいろ書いてみて、書き味を見て比較します。
  • 使用するiPadは iPad 2 Black 16GB (WiFi+3G GSM)
    • 表面に保護フィルムは貼っていません
    • 保護フィルムがある場合、保護フィルムのためにペン先の滑りおよび感度が変化するため、結果が異なってくる可能性があります。
    • 一般的に、保護フィルムがある方が感度が悪くなります。滑りはフィルムの材質により変化します。
  • 使用するアプリは Noteshelf
    • 無地ノート
    • 黒インク
    • ペン先10pt
    • 拡大は使用しない
  • すべて一発勝負
    • undoなし
    • 書き損じたり、ペン先の感度が落ちて線が飛んでもやり直さない。それは感度が実際に悪いという証拠になるため。

■結果

書いてみたノートをpdfにして出力してみました。以下のURLからご覧になれます。
https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxha2lzdXRlc2FtYXxneDo0OTI3MWU0MjRlMzI2ZjNm

複数同じ文言が並んでいるページは、全て上から順に、
pogo sketch, oStylus, スマートペン, Bamboo Stylus
の順に並んでいます。


■考察

以下、いくつかのページをピックアップして考察。


pogo sketchは第一世代のスポンジ式ペン先であるため、やはりペン先感度が悪く線が飛びまくります。線自体は自然に書けているのですが。


oStylusは感度はよいものの、いかんせん書きづらい。曲線が特に不自然です。しかも書くのに時間がかかります。多分一番時間がかかりました。


スマートペンはさすがの高性能。最高の感度と軽い軸の恩恵で、一番楽に書けます。ただし線自体は細かいところが弱いです。



今回新顔のBamboo Stylus。ペンが重く、スマートペンより筆圧が必要なため疲れますが、スピードのある細かい線が魅力。一番速く書けたのもこのペンです。


並べてみるとこんな感じです。上からpogo sketch, oStylus, スマートペン, Bamboo Stylus。良いと思ったところには緑○を、悪いと思ったところには赤×を引いています。
pogoは線が飛びさえしなければ・・・
oStylusはとにかく線をゆっくり引いてしまう。 Noteshelf アプリは Penultimate 同様、線を高速に引けば美しく細くなるように出来ているのですが、見てのとおり全ての線が太くてゆっくり引いてしまっている。Zとかへろへろですよ、もう。
スマートペンとBamboo Stylusは安定して良いですね。高速に美しく書けています。


■まとめ

やはり第二世代のシリコンペン先を装備したスタイラスが圧倒的に良いです。その中でも自分の好みや用途で選べる次代になってきたのがすごくいいですね。次回はプリンストンテクノロジーの専用タッチペンやAcase Stylusなんかも加えてレビューしてみたいです。

iPad 用スタイラス Bamboo Stylus を試してみた



つい先日いよいよ発売された Bamboo Stylus, 早速買って試してみました。


■Bamboo Stylus とは?

http://wacom.jp/jp/company/news_detail.php?id=355
http://japanese.engadget.com/2011/04/19/bamboo-stylus/

B004XF0FQWWacom iPad/IPad2/iPhone4対応 描画、ポインティングに最適なタッチペン Bamboo Stylus CS-100/K0
ワコム 2011-05-27

by G-Tools

PC向けペンタブレットではおそらく業界最大手の Wacom が、満を持して投入してきた静電誘導式タッチディスプレイ用のスタイラスペンです。ペンタブレットの開発でおそらくこの手のスタイラスペンに関しては相当なノウハウがあるのではないかと思われ、非常に期待が持てます。プレスリリースで、特に以下のような点が既存のスタイラスとは異なると強調されています。
  • 手に持った時の心地よさと書きやすさを両立する重さと重心のバランス。
  • 上質なサテン調のブラックとシルバー色を採用し、高い質感を実現。
  • ペン先は主な他社製品よりも直径が約25%小さく、より自然で直感的な描画が可能。
  • スムーズで快適な書き心地を実現するペン先の実現。(磨耗した際には交換が可能)
  • iPadカバーに留めたり、ポケットに留めたりするなど、携帯や保管にも便利なクリップ。(脱着可能)

■最初に結論

良い点
  • 第二世代のシリコンペン先を採用している他のスタイラスペンと比べ、細くて滑りが良くしっかりとしたペン先。
  • サイズの割に軸がやや太めで、しっかりと握ることができる。
  • 上記の理由により、実物のペンと非常に近い筆書感覚が得られる。
  • クリップが丈夫で脱着可能。取り外してストラップを代わりにつけることもできる。
悪い点
  • 同サイズのスタイラスペンの中では圧倒的に重く、書く際に手が疲れる。
  • 第二世代のシリコンペン先を採用している他のスタイラスペンと比べ、感度がやや悪い。
  • 3000円という、やや高価な値段設定。

■外見、持った感触

まずは外見から。値段設定がやや高めで、かつBambooブランドの名前を冠していることからもわかるとおり、iPad向けスタイラスとしてはハイエンドを狙っている物と思われます。そのためデザインにも力が入っており、表面がiPadやMacのように梨地加工されています。単なる金属の棒という体の パワーサポート スマートペンプリンストンテクノロジー 専用タッチペン に比べ断然高級感があり美しく感じられます。

長さは12cmということで、他社のスタイラスと比べても数ミリ長い程度で標準的だと思います。軸は他社の製品に比べてやや太く、良くコンビニなどで売っているボールペンよりわずかに細い程度になっています。そのため手に持った際の感覚が非常にペンらしいです。また、重さが非常に特徴的で、他のスタイラスと比べて明らかに密度のある重さがあります。体感だと パワーサポート スマートペン より二倍近く重いように感じます。

上部とペン先はねじになっていて、それぞれクリップとペン先が取り外せるようになっており、メンテナンスのことを考えた作りになっているのが心憎いです。消耗品ではなく、長期間使わせる物という印象を受けます。また、取り外した上部のクリップの部分にストラップをつけて持ち歩くことも出来そうです。


長さ比較。左から右に短い物順。pogo sketch, スマートペン, Bamboo Stylus, oStylus


太さ比較。先ほどと同じ順序。pogo sketch, スマートペン, Bamboo Stylus, oStylus


■動作、書き味

買って最初に一筆書いてみた時にすぐ気づいたのが、まず感度が悪いです。ほとんど載せるだけで反応する超感度の パワーサポート スマートペン に対して、ほんの少しだけ力を込めないと反応しません。しかしながらペン先が細くてしっかりしており、iPadの表面を非常になめらかに滑ります。滑りがよいので実際に紙で書いているよりちょっと違う感じがしますが、ペンタブレットに慣れている人にはちょうどいいぐらいだと思います。先端がふにゃふにゃしてやや抵抗がある滑りの パワーサポート スマートペン とは対照的な印象です。

個人的には長時間書いていると疲れます。感度が悪く抵抗が弱いのと重いペンが相まって、腕の力を使うためだと思います。


実際に Noteshelf アプリ上で書いてみました。実際のペン同様、非常になめらかに書けています。


■まとめ

WacomとしてはiPad向けスタイラスのハイエンドモデルとして位置づけているこの商品ですが、実際に使ってみるとむしろ パワーサポート スマートペン と対になるような感じの印象を受けました。すなわち、以下のように棲み分けられます。

Bamboo Stylus をオススメする人
  • ペンに近い持ち味、書き味が欲しい
  • 細くてよく滑るペン先が何より欲しい!
  • 値段は高くても気にしない
パワーサポート スマートペン をオススメする人
  • 軽くて、書いていて疲れないペンが欲しい
  • 感度の良くて、実際のペンみたいに少し抵抗がある滑りのペン先が何より欲しい!
  • 値段は安い方がいい、または初めてスタイラスを試してみるので失敗しても後悔しない値段の方がいい
私自身はどっちを選ぼうか悩んでますが、せっかく買ったし、しばらくはBamboo Stylusを使ってみようかと考えてます。

-ObjC とか -all_load って何をやってるのか調べてみた

よく外部のライブラリやFrameworkをiOSのプロジェクトに取り込むときに、つけないと動かないからつけてねと言われる、 Other Linker Flag = -ObjC -all_load のフラグ。これって何をやっているのか今までずっと気になっていたので、ちょっと調べてみました。

こういうことらしいです。
http://developer.apple.com/library/mac/#qa/qa1490/_index.html
This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.
このフラグは、Objective-Cのクラスまたはカテゴリを定義しているライブラリに含まれる、全てのオブジェクトファイルをロードするようリンカに促します。これによってたいていの場合実行ファイルのサイズが大きくなってしまいますが(アプリに追加のオブジェクトコードが読み込まれるので)、既存のクラスに対するカテゴリを含むObjective-C静的ライブラリを正しく使えるようになります。
へぇぇ!全く知らなかったです。ちなみに -all_load はというと、
-all_load forces the linker to load all object files from every archive it sees, even those without Objective-C code. -force_load is available in Xcode 3.2 and later. It allows finer grain control of archive loading. Each -force_load option must be followed by a path to an archive, and every object file in that archive will be loaded.
-all_load は全てのオブジェクトファイルを全ての認識できる静的ライブラリ(アーカイブ)から全部ロードしてくる(意訳)
とかそういうことらしいです。なるほどねー。

ということは -all_load はより強力な -ObjC に見えますが、実際には -ObjC は -all_load と違って上記に記載されているオブジェクトファイルのロードの挙動変化以外にも何かやっているようで、実際 -all_load -ObjC 両方指定してあるとリンクできるのに、 -all_load だけではリンクが通らない場合がありました。なのでやっぱり必要なときはまず -ObjC だけを試してみて、それでも駄目なら両方設定するのがいいみたいですね。


余談ですが、上記の内容を調べていたとき、リンカとかローダについて詳しく学ぶには以下の本がよいとオススメしていただきました。

Linkers and Loaders (日本語訳版もありますが、訳がひどいので英語版の方がよいとのことです。ここでは日本語版のリンクはあえて紹介しません)
http://linker.iecc.com/

4789838072リンカ・ローダ実践開発テクニック―実行ファイルを作成するために必須の技術 (COMPUTER TECHNOLOGY)
坂井 弘亮
CQ出版 2010-08

by G-Tools


KindleかiBooksで欲しかったのですが、残念ながら紙の本しかないですね・・・><

2011年5月5日木曜日

atos を使ってアプリのクラッシュログを symbolicate する方法

iOSアプリがクラッシュするとクラッシュログがデバイスに残され、 Xcode のオーガナイザーからログを取得してバグの原因を解析できるのは皆さんご存知の通りだと思います。このとき、基本的には Xcode がクラッシュログがの中のシンボルを自動的に読める状態にしてくれる (symbolicate) のですが、どうも Xcode 4 になってからこの symbolicate がいまいちよく動いてくれないので、手動で symbolicate をする方法を調べてみました。

参考にしたページは以下の通り。
http://stackoverflow.com/questions/1460892/symbolicating-iphone-app-crash-reports


■atosの使い方

symbolicate をするには Xcode に付属している atos というコマンドラインツールと、ビルドの際に生成される dSYM と呼ばれるファイルを使います。

まずは dSYM ファイルを探します。 Xcode 4 の場合は、デフォルトで *~/Library/Developer/Xcode/DerivedData/プロジェクト名-ランダムな文字列/Build/Products/ビルド設定名/アプリ名.app.dSYM* に生成されるようになっているはずです。

dSYM ファイルが見つかったら、コンソールから atos を実行します。 atos の使い方は以下のとおりです。
atos -arch アプリがクラッシュしたデバイスのアーキテクチャ -o アプリ名.app.dSYM/Contents/Resources/DWARF/アプリ名 クラッシュした関数またはメソッドのアドレス
実際にやってみるとこんな感じです。
cd ~/Library/Developer/Xcode/DerivedData/MyProject-xxxxxxxxxxxxxxxxxxxxxxxxxxxx/Build/Products/Debug-iphoneos/
atos -arch armv7 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp 0x00072ce6
これで指定されたアドレスが symbolicate され、シンボル名および元のソースコード上の行数まで表示してくれます。超便利です。


■注意点

何回か試してみましたが、ときどき実際にクラッシュしたシンボルと違うシンボルが表示されることがあるみたいです。上記で紹介したやり方が間違っているのか、それともそういうものなのかわからないのですが・・・すんません>< しかしその場合でもだいたい表示されたシンボルの近くが問題の原因だったため、いまのところ参考ぐらいには使えています。

2011年5月3日火曜日

Xcode のビルド設定で $(inherited) を使って設定を継承する



AppleのXcode 2のころのドキュメントによるとXcodeのビルド設定は以下の順に設定内容が解決されていきます。
  1. ユーザーのデフォルトの環境変数設定(たいていは ~/.MacOSX/environment.plist で指定されている)
  2. Xcodeのビルトインデフォルト設定
  3. Xcodeのアプリケーション設定(これはXcode 4では廃止されている気がします)
  4. ベースとなるビルド設定ファイル(元のドキュメントには書いてありませんが、.xcconfigファイルを使ってビルド設定を指定可能です)
  5. プロジェクトのビルド設定
  6. ターゲットのビルド設定
  7. コマンドラインから起動した場合は、コマンドラインのビルド設定フラグの値
このとき、下位の設定は上位の設定を上書きして使用するようになっているのですが、ここで上位の設定をそのまま受け継いで使いたい場合には、設定項目に$(inherited)という特殊な値を指定することが可能です。

2011年4月23日土曜日

KeychainItemWrapper を改造して、複数の Keychain Item に同時にアクセス出来るようにしてみた

※2011/04/23現在での情報です。以下の問題は全て Apple に対して報告いたしましたので、ひょっとしたら将来的に修正されるかもしれません。


現在の仕事にて課金情報を安全にアプリケーション内に保存したいということになり、それならばということで iOS に最初から用意されている Security.framework を使って Keychain 領域に情報を保存するアプリを作ることにしました。この Security.framework は全てC言語にて実装されており、そのまま使うと非常に取っつきづらいです。そこで、 Apple がサンプルコードとして提供している GenericKeychain プロジェクトKeychainItemWrapper クラスを使って Keychain 領域にアクセスすることにしました。

最初は問題ないように見えたのですが、実はこの KeychainItemWrapper クラス、Version 1.2には深刻なバグがあります。それは、複数の KeychainItemWrapper クラスのインスタンスを使って複数の識別子を持った情報を保存しようとすると、同時に一種類しか保存できなくなる(二つめを保存しようとするとステータスコード25299が返却されて保存できなくなる)というものです。

原因を調査した結果、以下のページに記載されている内容が問題だとわかりました。
http://useyourloaf.com/blog/2010/3/29/simple-iphone-keychain-access.html
The combination of the final two attributes kSecAttrAccount and kSecAttrService should be set to something unique for this keychain. In this example I set the service name to a static string and reuse the identifier as the account name.
サンプルで提供されている KeychainItemWrapper クラスにはこれらの一意となるキーが指定されておらず、単に検索用の kSecAttrGeneric だけが指定されていたため、一意キーの指定がおかしくなって二つ目の Keychain アイテムが保存できなくなっていた、というわけです。

そこでこちらの問題を修正した KeychainItemWrapper クラスをご用意しました。
https://gist.github.com/938375

ライセンスは元となるAppleのオープンソースライセンスに準拠します。改変・再配布・利用すべて自由ですが、その際ソースコードの最上部にあるAppleのライセンス条項コードは絶対に改変しないでください。Apple先生に何か言われても私は知りませんよ><


■修正版 KeychainItemWrapper の使い方

基本的には通常の KeychainItemWrapper と何ら変わりません。
// インスタンスを作る
KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"com.akisute.keychain.KeychainTestApp.item1"
serviceName:@"com.akisute.KeychainTestApp"
accessGroup:nil];

// 値を保存する
// いったんresetKeychainItemしているのはmyValueがnilのとき保存されているデータを削除したいから
// 基本的に値(kSecValueData)として保存できるのはUTF-8のNSStringのみです。内部的にNSDataに変換されてKeychainに保存されます。
// それ以外の情報を保存したい場合はすみませんが未対応です>< Base64文字列にするとかJSON文字列にするとか工夫して回避してください><
NSString *myValue = @"abesi";
[wrapper resetKeychainItem];
[wrapper setObject:myValue forKey:(id)kSecValueData];

// 値を取り出す
NSString *resultValue = [wrapper objectForKey:(id)kSecValueData];
NSLog(@"resultvalue = %@", resultValue);

iOS 3の UIScrollView はスクロールが発生しない状態では内部の UIView のタッチが取れない

iOS 3以前と iOS 4以降で大きく内部実装が変わったクラスがいくつかあります。 UIScrollView はその中の一つですが、ここではその中でもかなり厄介なバグを紹介します。

iOS 3の UIScrollView は、スクロールが発生しない状態(すなわち UIScrollView.bounds.size >= UIScrollView.contentSize のとき)では内部の UIView のタッチが取れません。タッチが取れないというのは、要するに内部の UIView に実装してある touchesBegan, touchesMoved, touchesEnded などが呼び出されないということです。情報元はこちら。
http://www.iphonedevsdk.com/forum/iphone-sdk-development/1345-uiscrollview-subview-touch-problem.html
Looks like as if the scrollview blocks any touches when the content rectangle is smaller or of same size as its bounds. Really strange behaviour.
ほんと Really strange behavior ですよ Apple センセ。

回避策として、iOS 3では UIScrollView.contentSize を調整して、常にスクロールが発生するような状態にしておけば大丈夫です。 iOS 4以降でしたらこの問題は発生しません。


2011/05/02追記: @k_katsumi さんから突っ込みいただきました。
http://twitter.com/#!/k_katsumi/status/61690186664378368

以下のように delaysContentTouches プロパティと canCancelContentTouches プロパティを NO に設定する方がどうもお行儀がよいようです。
scrollView.delaysContentTouches = NO;
scrollView.canCancelContentTouches = NO;

UIButton でアニメーションする画像を表示させたい

UIButton でアニメーションする画像を表示させたいときは、 UIButton.imageView.animationImages プロパティを使うことで簡単にアニメーションを実装させることができます。さっそくサンプルを書いてみます。

以下、 iOS 3.2 および iOS 4.0 以降にて確認しております。
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

NSArray *animationImageNames = [NSArray arrayWithObjects:@"animation1.png", @"animation2.png", @"animation3.png", nil];
NSMutableArray *animationImages = [NSMutableArray arrayWithCapacity:[animationImageNames count]];

for (NSString *animationImageName in animationImageNames) {
UIImage *image = [UIImage imageNamed:animationImageName];
[animationImages addObject:image];
}

[button setImage:[animationImages objectAtIndex:0] forState:UIControlStateNormal]; // 大事
button.imageView.animationImages = animationImages;
button.imageView.animationDuration = 2.0;
[button.imageView startAnimating]; // 大事、忘れるとアニメーションしません

[self.view addSubview:button];
UIButton.imageView.animationImages プロパティを使う際に注意する点がいくつかあります。
  • 必ず最初に setImage:forState: メソッドを使って何らかの画像を表示させるようにすること。通常の画像を表示させるようにしないと、 UIButton.imageView が画面に表示されません。
  • iOS 4.0以降ではこのプロパティでセットした画像は UIButton の frame に合わせてリサイズされて表示されますが、 iOS 3.2 以前ではリサイズされずそのままの大きさで表示されますので、注意が必要です。

2011年4月17日日曜日

Xcode 4 で scheme が My Mac 64bit になって iPhone 向けのビルドが出来なくなった時の対処法



最近会社のプロジェクトで使う Xcode を Xcode 4 に乗り換えたのですが、全く新しく作り直された バグだらけでまともにビルドすら出来ない上に無料じゃなくなったどうしようもない出来映えの IDE ということで、何かとトラブルが多いようです。今回はその中でももっとも頻発したものをご紹介します。

たまに iOS 向けのプロジェクトを Xcode 4 で開いたときに、左上の scheme 選択欄に My Mac 64bit と表示されてしまって iPhone シミュレータや実機でビルドが出来なくなることがあるようです。

対処法はこちら。
http://stackoverflow.com/questions/5319251/xcode-4-the-selected-run-destination-is-not-valid-for-this-action

Build Settings を開き、 Base SDK の設定をいったん Mac OS X SDK に変更してから、もとの iOS SDK に戻すと問題が解決されるようです。これで一安心ですね。

Xcode 4.0 と Xcode 4.0.1 で再現することを確認しました。 Xcode 4.0.2 では未確認です。

Xcode 4 で build target 名にスペースが含まれているとビルド時に 100% エラーになる



タイトルからしてひどい出落ちですが、 Xcode 4 で build target 名にスペースが含まれているとビルドを行った際に 100% ビルドに失敗します。

原因がまたお粗末で、 Xcode 4 は build target 名をビルド成果物の中間配置ディレクトリの名前に使っているのですが、スペースをエスケープしていないためコンパイラが正しくパスを解釈できずに落ちているようです。がっくし。



Xcode 4.0 での調査結果です。 4.0.1 または 4.0.2 では修正されている可能性がありますが、いずれにせよスペースはさけたほうが良さそうです。

UINavigationController に管理されている UIViewController の view のサイズを変更したい

たとえばアプリ内の固定の位置に広告を突っ込みたいときなど、 UINavigationController に管理されている UIViewController の view 構造を操作したい場合があると思いますので、調べてみました。


■UINavigationController.viewの中身

iOS 4 ですと以下のようになっています。



最上位の UINavigationController.view に相当するのが UILayoutContainerView で、その下に UINavigationTransitionView があり、そのまた下に UIViewControllerWrapperView というのがあって、こいつが表示される UIViewController.view をラップして表示しているという仕組みになっているようです。

おそらく iOS 3.2 でも同じだと思います。 iOS 3.1.3 以前についてはわかりません。


■実際にやってみる

いろいろ試してみた結果、もっともうまくいきそうなのは以下のように、 viewDidAppear のタイミングで UIViewController.view.superview に対して操作を行う方法であることがわかりました。
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.headerView.frame = CGRectMake(0, 0, 320, 100);
self.view.frame = CGRectMake(0, 100, 320, self.view.superview.frame.size.height - 100);
[self.view.superview addSubview:self.headerView];
}

■問題発生

ところがこの方法では画面遷移時のアニメーションが発生すると上記の例で追加した headerView の表示が無茶苦茶に壊れてしまいます。またタイミングを誤ると headerView のサイズが self.view.superview.bounds の大きさに補正されてしまいます。恐らくは UINavigationTransitionView とかいう private な view がなにやらやっているのでしょう。アニメーションが発生しない範囲であれば問題なさそうですが、いまいちです。


■結論

横着しないで表示される UIViewController の view で何とかするしかないみたいですね><

2011年3月17日木曜日

Xcode 4 で昔の Three20 フレームワークを使っているプロジェクトをビルドする方法

主に自分用のメモ。
Xcode 3 時代に作った Three20 フレームワークを使っているプロジェクトが動かない場合の対処法です。


動かない原因は簡単で、Xcode 4からはビルドした生成物が用意される場所が、各プロジェクトのbuildディレクトリ以下から、
~/Library/Developer/Xcode/DerivedData/プロジェクト名/Build/Products
以下に移動しているからです。なので、たいていの場合ヘッダ検索パスが間違っているので動かなくなります。

対処法はThree20公式ブログに載っているので転載します。
http://three20.info/article/2011-03-10-Xcode4-Support

以下のようにヘッダ検索パスを追記すると良いようです。
"$(BUILT_PRODUCTS_DIR)/../three20"
"$(BUILT_PRODUCTS_DIR)/../../three20"


2011/04/03追記: はじめに載せていた対処法は間違いでしたorz 大変申し訳ありません!
以下、はじめに掲載していた間違っている対処法です。この方法では通常のビルドはうまくいきますが、アーカイブ時のビルドに失敗します!どうもアーカイブ時と通常のビルド時でもビルド先のディレクトリが指し示す先が異なるようです、

対処法は、以下のようにヘッダ検索パスを書き直します。
$SYMROOT/three20


$SYMROOTという環境変数がキモです。



こいつはドキュメントには書いてませんが、Xcode 4のビルド設定ですと、
~/Library/Developer/Xcode/DerivedData/プロジェクト名/Build/Products
に対応するパスを返してくれるようです。

2011/04/03追記: 上にも書きましたが、アーカイブ時には上記に対応するパスではなく、異なるパスが使われます。そのためこの方法ではうまくいきません。Three20.infoに掲載されている方法を使うのが一番良いようです。

2011年3月16日水曜日

Python で Xcode のビルドスクリプトを書く方法

以前こんな記事を書きましたが、今回はもっと実践的なお話。PythonでXcodeのビルドスクリプトを書いてハッピーになろうというお話です。


■なぜXcodeのビルドスクリプトを書くのか

Xcodeのビルド機能だけでは出来ないことをやりたいからです。たとえば、
  • 特定のディレクトリの中に入っているリソースを、ビルド時にアプリにパッケージングしたい。
  • ビルドする前に、特定のリソースを暗号化して、アプリにパッケージングしたい。
といった要望が結構ありますが、これらはビルドスクリプトを使えば簡単に可能になります。
手でいちいちやるより楽で安全ですね。


■なぜPythonか

理由はいくつかあります。
  1. Windows, Mac, Linux, 全ての環境で動く。したがって、万が一のときにはビルドスクリプトだけを移植できる。
  2. sh とか csh とか非力すぎてやってらんない。 zsh もつかえるけど Python よりはやはり弱いと思う。
  3. スクリプトを Xcode 内部のエディタで書いて、そこに閉じ込められてしまうため、可搬性が無くなってしまう。
  4. 外部スクリプトにしておくと、引数としてオプションを渡せるので、ビルド設定に応じてオプションを切り替えたり、テストと本番でオプションを切り替えて動作を変更する、とかできる
たとえばクライアント・サーバーアプリで、サーバー側がPythonで出来ていたりする場合、
サーバー側の処理を一部ビルド時にやりたいとかあったりするわけですよ。・・・たまに。・・・ごくまれに。
そういうときに便利です。


■例:

長々と語るより例を示した方がよいと思うので、さっそくビルドスクリプトの例を示します。
ここでご紹介するのは、プロジェクトの/Resources/MyResourceディレクトリ以下にあるファイルを全てアプリバンドル内の/MyResourcesディレクトリ以下にコピーするだけの簡単なビルドスクリプトです。オプションとして平文/暗号化の有無を選択できるようにしてみました(実装はしてないです><)

Xcodeがビルドスクリプトを実行する際に、環境変数にたくさんの情報をセットしてくれます。なので、Pythonの os.environ を使ってそれらの情報を拾っていきます。Xcodeがセットしてくれる環境変数についてはhttp://developer.apple.com/library/ios/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/0-Introduction/introduction.htmlにまとめがあります。

#! /usr/bin/env python
# coding: utf-8

import os
import shutil
from optparse import OptionParser

def main():
# Option parser settings
#
description = """Sample build script"""
package_type_choices = (
'plain',
'crypted',
)
parser = OptionParser(description=description)
parser.add_option('-t', '--type',
action='store',
type='choice',
choices=package_type_choices,
default=package_type_choices[0],
dest='package_type',
help='Type of the destination package',
metavar='TYPE')
(options, args) = parser.parse_args()
print "*** Begin packaging resources. Package type is %s. ***" % options.package_type
# Env var settings
#
src_resources_path = "%s/Resources/MyResources" % (
os.environ['SRCROOT'],
)
destination_resources_path = "%s/%s/MyResources" % (
os.environ['TARGET_BUILD_DIR'],
os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH']
)
# Create target dir
#
print "*** Creating the destination MyResources directory at %s ***" % destination_resources_path
if os.path.isdir(destination_resources_path):
shutil.rmtree(destination_resources_path)
os.mkdir(destination_resources_path)
# Copy each resources in src_resources_path to the destination
#
for root, dirs, files in os.walk(src_resources_path):
for dir in dirs:
# TODO: This implementation is only for 'plain' packaging. Implement the 'crypted' packaging later
print "*** Copying the resource %s to the target build location ***" % dir
fromdir_path = os.path.join(root, dir)
todir_path = os.path.join(destination_resources_path, dir)
shutil.copytree(fromdir_path, todir_path)
print "*** Removing garbage files from the copied resource ***"
for r, ds, fs in os.walk(todir_path):
for f in fs:
# .から始まるファイルをゴミファイルと見なしてパッケージに加えないようにします
if f.startswith('.'):
os.remove(os.path.join(r,f))
print os.path.join(r,f)
# Make sure not to traverse into the subdirectories
del dirs[0:len(dirs)]
# Completed
#
print "*** Packaging resources is successfully completed! ***"

if __name__ == "__main__":
main()
はい、できました。Pythonを使ったメリットとして、 optparse モジュールのおかげでオプションを扱うのがすごく楽にできるとか、リストを扱うのが強力とかが見て取れます。ファイル名の文字列加工も shやcsh の中で sed を使うより安全でらくちんです。


■Xcodeから呼び出す

あとはこれをXcodeのビルド時に呼び出すようにしてやれば良いだけです。

Xcode 3の場合には、画面左のナビゲーションバーからターゲットを選択して、「新規ビルドフェーズを追加」→「スクリプトの実行」とかで出来たと思います。
Xcode 4の場合には、以下の画像が示すとおりにすればOKです。



はい、出来ました。あとはビルドするたびに毎回このスクリプトが実行されてくれるわけです。

画像では紹介してないですが、もちろん呼び出し時にオプションをつけたりできますよ。
/usr/bin/env python $SRCROOT/bin/mybuildscript.py --myoption=1 -v --enable_my_secret

LLVM compiler 2.0 (clang 2.9) で linker にパスを出力させる方法

Xcode 4 から使えるようになった LLVM compiler 2.0 (clang 2.9) の覚え書きです。リンカにパスを出力させる方法を調べてみました。

※ clang 2.8 じゃなくて 2.9 がベースになってるみたいです。ごめんなさい><



方法は簡単。ビルド設定中のOther Linker Flags (OTHER_LDFLAGS) に、
-v -Xlinker -v
または
-v -Wl,-v
を与えればOKです。


ほいごらんのとおり。

gcc 4.2 または LLVM gcc 4.2 を使っているときは、 gcc がコンパイルして ld でリンクするようになっていましたが、 clang を使うときは clang が一人でコンパイルして一人でリンクまでやってしまいます。そのため、これまで通り普通に-vを渡しただけではうまくいきません。これだと、単に clang が verbose モードになるだけですからね。そこで -Xlinker とか -Wl を使ってリンカを verbose モードにすればOKということです。詳細はman clangすべし。