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すべし。

2011年3月8日火曜日

lipo を使って簡単に Universal Binary を作成する方法

iOS 向けのライブラリやフレームワークは、よく static library (.aファイル) の形式で配布されています。これは iOS がユーザーが作成した dynamic library (.dylibファイル) や framework バンドルをサポートしていないからなのですが、ときどきこの static library がシミュレーターとデバイス両方で使える形式、いわゆる Universal Binary になっていない場合があります。

たとえばこんな感じですね。



この状態でビルドを行うと、シミュレーター向けビルドを行えばデバイス用のバイナリが、デバイス向けビルドを行えばシミュレーター用のバイナリが、それぞれ対応していないアーキテクチャであると警告を出してしまいます。警告ですからコンパイルは通るのですが、私は几帳面で気になってしまうので、これを解消したいと考えます。


■lipoの出番

そこで lipo を使います。 lipo は Xcode に付属されているコマンドラインツールですので、 iOS SDK をインストールしている人でしたら誰でも使えます。詳しい使い方は man を見ていただければわかりますが、普通使うのは以下のパターンだけです:
lipo -create ライブラリ1.a ライブラリ2.a -output 出力するライブラリ.a
たったのこれだけで二つのライブラリを組み合わせて Universal Binary を生成してくれます。さっきの画像の例ですと、
lipo -create libAbesi_dev.a libAbesi_sim.a -output libAbesi.a
これでOKです。


■本当に Universal なの?

それでは本当に Universal Build になったのか見てみましょう。 static library の中身を見るには、 nm コマンドを使います。



どうやらうまくいったみたいですね。一つのライブラリファイルの中に、3種類のアーキテクチャに対応したオブジェクトが格納されています。

2011年2月28日月曜日

UIWebView のスクロールを制御するためのプロパティを書いてみた

UIWebView にどうして scrollEnabled プロパティがついてねえんだ Apple のチンパンジー野郎!とお嘆きの全国1000万の iOS 開発者の皆様、こんばんわ。もちろん私もその一人であります。

嘆いていてもしょうがないので何とかスクロールを制御する方法を・・・と思って探していたら、すでに2009年の地点で @nakamura001 さんがこんなブログを書いてらっしゃいました。

http://d.hatena.ne.jp/nakamura001/20090520/1242837408

が、遷移先で詳解されている

http://praveenmatanam.wordpress.com/2009/04/03/how-to-disable-uiwebview-from-user-scrolling/

のコードが正直いまいちなのです。何がいまいちって、せっかくのCocoa環境であるにも関わらず、わざわざobjc/runtime.hなんていう低レベルなC言語の関数を使っています。別にパフォーマンスがタイトな場所でもないですし、かっこよくCocoaっぽく書き直してみました。ということで書き直したコードがこちら↓

https://gist.github.com/846258

で、このAdditionを導入するとですね、
UIWebView *webView = [[[UIWebView alloc] initWithFrame:frame] autorelease];
webView.webViewScrollEnabled = NO;
みたいな書き方ができてハッピーになれます。

内部的には NSInvocation を使っています。 Mac OS X 10.5 (iOS 2.0) から存在するこのクラス、本当に便利で、ぶっちゃけ呼び出し対象のシグネチャさえわかれば何でも呼び出せるスグレモノです。フレームワークを作るときなど、呼び出し先のシグネチャしかわからない状況下で対象のメソッドを呼び出す時などに便利な感じかもです。その上 NSInvocationOperation を使ってそのまま並列化もできたりして。
NSMethodSignature *sig = [subview methodSignatureForSelector:selector];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setTarget:subview];
[invocation setSelector:selector];
[invocation invoke];
BOOL result = YES;
[invocation getReturnValue:&result];
return result;

2011年2月20日日曜日

github で pull request をされたとき・するときの手順

github に自分のリポジトリを公開していると、たまに pull request をされることがあります。また逆に、他人のリポジトリのコードを使っていて、どうしても気になるバグを見つけて修正したときなど、相手に pull request を送りたいことがあります。こんなときにどうすればよいかをまとめてみました。


■pull request をしたいとき

pull request をしたいときは、まず相手のリポジトリを fork する必要があります。


このボタンをぽちっとな。

fork したら、 fork して自分の管理下に入ったリポジトリを clone して、コードを修正します。
git clone https://akisute@github.com/akisute/asi-http-request.git
コードの修正が終わったら、自分の fork したリポジトリに push しておきます。
git push origin master
ではコードの修正が終わったので pull request をします。


このボタンをぽちっとな。


するとこうなります。デフォルトでは相手の master に対して自分の master を pull request することになりますが、もちろんこの画面で変更可能です。 request をする先のリポジトリ・ブランチと、リクエストされるブランチをそれぞれ指定できます。それからコメントをつけて送れます。ここでつけたコメントは相手側のリポジトリの Pull Requests タブに記載されるので、それなりにかっこいい内容を書いておくと良いかと思います。あとは相手が受け入れてくれるのを待つばかりです。自分のコメントにコメントがついたらメールとかで通知が来ますんで、適当に返事して相手と連絡を取りながらやる感じになります。


■pull request をされたとき

逆に自分が pull request をされることもありますので、そのときの対処法も。


pull request されると、こんな風に Inbox の Notifications の中に通知が来ます。


対象のリポジトリの Pull Requests タブにもこんな具合でリクエストが入ります。


クリックしてリクエストの詳細を開きます。ファイルの diff 一覧とかどっからどこに対してリクエストが飛んでいるのよとか全部ここで見られます。相手のコメントに対してコメントを返したり、 diff に対してコメントをつけたりできます。 diff につけたコメントは相手にも公表されます。

ここで相手とコメントやりとりとかして、じゃあ取り込もうか、となりましたら、実際に pull request を取り込む作業を行います。自分のリポジトリにマージ用のブランチを用意して、 pull して確認し、問題なければマージ用のブランチと本線をマージして終了。コマンドがよくわからなくても github が全てのコマンドを表示してくれるのでそれを見れば一発です。本当に親切。以下、 master ブランチに対して pull request があったとすると、
git checkout -b pull-request-master master
git pull http://github.com/applicant/his-repository
# 相手のコミットが自分のブランチに pull されてくるので、この状態で動作を確認する
# merge 作業が発生するならここで行う
# 問題がなさそうなら
git commit
git merge master
git push origin master
push まで完了したら先ほどの pull request のページから取り込み完了したよボタンを押しておきましょう。


■おまけ: 自分が fork しているコードの fork 元リポジトリが更新されたので、こちらも最新にしたい

実はこれは pull request をされたときとあんまり変わりません。対象の相手のブランチを自分の fork しているコードに pull すればよいのです。
git checkout -b merge-master master
git pull http://github.com/parent/parent-repository
# 相手のコミットが自分のブランチに pull されてくるので、この状態で動作を確認する
# merge 作業が発生するならここで行う
# 問題がなさそうなら
git commit
git merge master
git push origin master

2011年2月12日土曜日

Redmine を Debian 5.0 にゼロからインストールしてみた

あらかじめ注意: 下記のメモは、ruby, gem, rails, rack, redmineのいずれかのバージョンが0.0.1でも違うと動作しない可能性が高いので、当てにしないでください。


■顧客が説明した要件
  • 1つのサーバーに複数のプロジェクトのリポジトリとかリソースをおいているので、1プロジェクトにつき1個インスタンスが必要なtracは都合が悪い!
  • っていうかtrac飽きた!!古い!
  • 複数プロジェクト扱えるBTSが欲しい
  • 見た目クールだといいね、カスタマイズとかできるとすごくいい!
  • Wikiは絶対必須
  • Google Appsと連携できると素敵だな!
  • 簡単にインストールしたい、時間かけたくない


■プロジェクトリーダーの理解
  • https://www.gravitydev.com/ はすごくいいんだけれど許可下りなさそうだなぁ。そもそもWikiがないから却下。
  • となるとやっぱりRedmineかな!あれすごくいいよね!
  • Redmineに移行しようかみたいな話も社内で出てたし、先んじて導入してノウハウ作るのはありだな!


■アナリストのデザイン
[11/02/10 9:24:53] akisute: redmineのインストールが超面倒くさいんだけれど
[11/02/10 9:24:54] akisute: なんとかならんのこれ
[11/02/10 9:25:02] wozozo ( ゚Д゚)y─┛~~: rails
[11/02/10 9:25:19] tokibito: rails
[11/02/10 9:25:20] 文 殊堂: まいんちゃん
[11/02/10 9:26:39] akisute: http://redmine.jp/guide/RedmineInstall/
[11/02/10 9:26:43] akisute: これ見てるとアホかと言いたくなってくる
[11/02/10 9:26:50] akisute: Ruby 1.9には対応していません。 上記の表に示したバージョンのRuby 1.8.xをインストールしてください。
RubyGems 1.3.1以上が必要です。
Rake 0.8.3以上が必要です。
Rack 1.0.1が必要です。1.0.1がインストールされていない場合、データベースマイグレーションが失敗します。
[11/02/10 9:27:25] wozozo ( ゚Д゚)y─┛~~: aptでいれたgemをupdateしようとすると、
[11/02/10 9:27:34] wozozo ( ゚Д゚)y─┛~~: おい待てって言われるし
[11/02/10 9:27:49] aodag: Redmineってライトニングなのないの?
[11/02/10 9:27:53] tokibito: rubyはaptで入れたら死亡する可能性
[11/02/10 9:28:04] aodag: rvmっしょ
[11/02/10 9:28:04] tokibito: 自分でビルドしたほうが無難とかいう
[11/02/10 9:28:11] tokibito: rvmで。


■プログラマのコード
  1. redmine:redmine ユーザーを新規に作成する
    • 以下の作業はすべてredmineユーザーで行う
  2. rvmの最新をインストールする
    • http://rvm.beginrescueend.com/rvm/install/
    • curl と git-core のインストールが必要。aptitude経由で実施
    • /home/redmine/.bashrcの追記を忘れないように
    • source ~/.rvm/scripts/rvm も忘れないように
  3. rvmに必要なパッケージをインストールする
    • こんなんデフォルトで用意しろよ・・・いいかげんにしろ・・・
    • rvm package install readline
    • rvm package install iconv
    • rvm package install zlib
    • rvm package install openssl
    • この順番で入れないと依存関係の問題で怒られます (opensslがzlibを使うとか)
  4. ruby 1.8.7 をrvm経由でインストールする
    • 参考は http://d.hatena.ne.jp/masasuz/20100720/1279635055
    • 先に rvm package install しないとgemが使えない, zlibがないので
    • ビルドオプションを付けることでopensslとzlibを有効にすること
    • rvm install 1.8.7 -C --with-openssl-dir=$HOME/.rvm/usr -C --with-zlib-dir=$HOME/.rvm/usr
    • rvm use 1.8.7
    • ruby --version
    • gem --version
  5. gemのバージョンを 1.3.7 に戻す
  6. 本当にopensslとかzlibとかインストールされているかをチェックする
    • ruby -r openssl -e ""
    • エラーになった場合はopensslの再ビルドを行う。opensslを使えるようにしないとのちのちrakeを実行するところでエラーが発生して先に進めなくなる
    • cd ~/.rvm/src/ruby-1.8.7-p330/ext/openssl
    • ruby extconf.rb --with-opt-dir=$HOME/.rvm/usr/
    • make
    • make install
    • ruby -r openssl -e "" を再度実行してエラーにならない事を確認
  7. rails 2.3.5 をgem経由でインストールする
    • gem install rails -v=2.3.5
    • rails 2.3.5をインストールすると自動的にrack 1.0.1が入るが、先にrack 1.0.1をインストールしておいたほうが良いかも
  8. rack 1.0.1 をgem経由でインストールする
    • gem install rack -v=1.0.1
  9. sqlite3-ruby 1.2.3 をgem経由でインストールする
    • 参考は http://d.hatena.ne.jp/modka/20101208
    • sqlite3-devel (sqlite3.h) が見つからないとインストール出来ないので注意
    • 最新のsqlite3-ruby は sqlite3 3.6.3 が必要で、Debianのaptitudeでは3.5.9までしか無いので、バージョンを指定して古いsqlite3-rubyを入れる
    • sudo aptitude install sqlite3
    • sudo aptitude install libsqlite3-dev
    • gem install sqlite3-ruby -v=1.2.3
  10. i18n 0.4.2 をgem経由でインストールする
    • rakeの実行に必要
    • rakeお前依存関係に含めろや・・・アホか・・・><
    • gem install i18n -v=0.4.2
  11. redmine のリリース版の最新をダウンロードする
  12. データベースの設定を行う
    • 参考は http://redmine.jp/guide/RedmineInstall/
    • 今回はsqlite3
    • cd ~/redmine-1.1.1/config
    • cp database.yml.example database.yml
    • vim database.yml
    • cd ~/redmine-1.1.1
    • rake generate_session_store
    • rake db:migrate RAILS_ENV="production"
    • rake redmine:load_default_data RAILS_ENV="production"
  13. メールの送信設定を行う
    • 不要と思われる。メールが飛ばないだけで済む
  14. 起動する
    • cd ~/redmine-1.1.1/
    • ruby script/server webrick -e production
    • http://localhost:3000/ が立ち上がるので、 admin/admin でログインする
    • 適切にadminのパスワードを変更すること
  15. ログの設定を行う
    • やっとかないとログが溜まりまくって死ぬらしいので
    • cd ~/redmine-1.1.1/config
    • cp additional_environment.rb.example additional_environment.rb
    • vim additional_environment.rb
    • http://redmine.jp/guide/RedmineInstall/ のログの設定セクションのとおりに設定しておく
  16. Passengerをインストールする
    • 参考は http://d.hatena.ne.jp/Umeyashiki/20100131/1264948069
    • gem install passenger
    • passenger-install-nginx-module
    • ここまでの手順どおりに進めていると、大体以下のものが足りないはず
      • Curl development headers with SSL support... not found
      • OpenSSL development headers... not found
      • Zlib development headers... not found
    • 一旦abortしてそれぞれインストールすること
      • sudo aptitude install zlib1g-dev
      • sudo aptitude install libssl-dev
      • sudo aptitude install libcurl4-openssl-dev
    • nginxの再ビルド(ないしソースコード)が必要になるので、すでにapt-getしたnginxがある場合には使えない。その場合はpassengerをstandaloneで起動して、そこに対してnginxからproxy-passなりなんなりする。
    • 以下、passengerスタンドアロンで起動し、redmineはすべてpassengerにやらせる場合を考えます。ただしpassengerのスタンドアロンは内部的にはnginxを使っているので、いずれにせよ上記のインストールは必要になります。
    • cd ~/
    • mkdir log run
    • passenger start redmine1.1.1 -d -a 0.0.0.0 -p 3000 -e production --log-file=log/passenger.log --pid-file=run/passenger.pid
    • ってやると何故か/tmp以下にlogファイルをつくろうとしていみわからないエラー吐いて死ぬので中止
    • cd ~/redmine1.1.1
    • passenger start -d -e production
    • 最低限のオプションだけで起動すれば起動できた


■営業の表現、約束
  • お客様、 redmine をお求めですか?ご安心ください! gem install redmine で一発に決まってますよ!簡単です!
    • そんなものはない
  • それなら sudo apt-get install redmine で一発です!猿でもインストールできます!!
    • Debian では無理
  • それなら最初から redmine がインストールされている vm イメージがあります!こちらを使えば一発です!
    • Ubuntu と SUSE しかない
  • Trac Lightning とかゆとり。真のエンジニアは ruby の configure & build から自分でやって当然(キリッ
    • そんな業務と関係ないところに何時間(何万円)使うつもり?
    • アップデートどうすんの?何時間(何万円)使うつもり?


■プロジェクトの書類

http://redmine.jp/guide/


■実装された運用

http://www.redmine.org/projects/redmine


■顧客への請求金額

akisute の実働5時間分 = 数万円 = https://www.gravitydev.com/pricing のスタンダードプラン数年分


■得られたサポート

http://redmine.jp/guide/RedmineInstall/


■顧客が本当に必要だったもの

http://twitter.com/shin_no_suke/status/35632371084562433
http://twitter.com/hirokinko/status/36307730025156608
http://twitter.com/hirokinko/status/36310634127695872


■元ネタ

http://ryonoda.wordpress.com/2008/09/26/


■一応補足

Redmine自体はすごく良いアプリだと思います。ええ。いま使ってますが、とっても助かってます。

bitnamiの配布しているパッケージが対話環境でもインストールできるというのは知りませんでした。っていうかbitnamiのページのどこにも書いてないんですが・・・><
wget http://bitnami.org/files/stacks/redmine/1.1.1-0/bitnami-redmine-1.1.1-0-linux-installer.bin
chmod +x bitnami-redmine-1.1.1-0-linux-installer.bin
./bitnami-redmine-1.1.1-0-linux-installer.bin --mode text
でうまくいくようです。

しかしこの方法を使うと勝手にApache, MySQL, Ruby, Subversionをインストールされてしまうみたいなので、きちんと管理したいとなるとちょっと不向きかなと。

2011年2月9日水曜日

UITableView の cell の中に UITextView を配置したとき発生する問題とその対処方法

たとえば Twitter クライアントや SMS クライアントのように、短い文章を入力させるような画面を作りたいとき, Grouped Style な UITableView の cell の中に UITextView を配置して画面を作る事が良くあるかと思います。通常、この作りでほとんど問題はありませんが、特定の条件下で問題が発生することがわかりましたので、回避策をメモしておきます。


■問題

この問題が発生するのは、以下の条件を満たしたときです。
  1. UITableView の cell の中に UITextView を配置する。
  2. UITextView に、縦スクロールが発生するぐらい長い文字列を入力する。
  3. UITextView の中ほどにカーソルを移動する。
  4. UITableView と UITextView の scrollEnabled プロパティを NO に設定する。
  5. UITextView の text プロパティを変更する。
すると, UITextView および UITableView の scrollEnabled プロパティを NO に設定しているにもかかわらず、勝手に UITableView がスクロールを起こしてしまいます。 UITextView の text プロパティを書き換えたら勝手に UITextView が一番下までスクロールしてしまうのはデフォルトの挙動であり、これは問題ありません. UITextView を単品で使っているときは, scrollEnabled プロパティを NO に設定することでスクロールを発生させずに text を書き換えることが可能なのですが, UITextView を UITableView の中に含めたときのみ, scrollEnabled プロパティが無視されてしまい、この問題が発生してしまうようです。

カスタムキーボードを用意したり、本文中のURLを短縮して差し替えたりなど、結構 UITextView の text プロパティを直接書き換えたい要件があったりするので、このままでは困ります。


■解決方法

そこで、相当な荒技ですが、以下のようにして解決を図ることが可能です。
  1. UITableView の cell の中に UITextView を配置する。
  2. UITextView をいったん removeFromSuperview する。
  3. UITextView をいったん UITableView 以外の場所に addSubview する。
    • UIWindow の直下などがおすすめ。
  4. UITextView の scrollEnabled プロパティを NO に設定する。
  5. UITextView の text プロパティを変更する。
  6. UITextView の scrollEnabled プロパティを YES に設定する。
  7. UITextView を元の view に addSubview する。
  8. UITextView を first responder にする。
コードにすると以下のようになります:
UIView *parentView = textView.superview;
[self.view.window addSubview:textView];
textView.scrollEnabled = NO;

textView.text = [currentText stringByReplacingCharactersInRange:selectedRange withString:shrunkenURLString];
textView.selectedRange = NSMakeRange(selectedRange.location + [shrunkenURLString length], 0);

textView.scrollEnabled = YES;
[parentView addSubview:textView];
[textView becomeFirstResponder];
こうすることで, UITableView が勝手にスクロールしてしまう問題を回避することが可能です。

2011年2月4日金曜日

TestFlight 経由のアプリのダウンロード速度を調べてみた




最近 iOS 界隈を賑わせている TestFlight というWebサービスがあります。詳細についてはまぁ TechCrunch さんでも読んでいただくとしまして、この TestFlight が非常に便利そうなので私のほうでも早速試してます。登録も簡単、アプリのマネジメントも良くできていますし、チームを複数用意して管理できるというのも良いです。なんで Apple がデフォルトでこれを用意してくれないのかというぐらい便利です。

が、一つだけ気になったことがあって、まぁタイトル通りなのですが、TestFlight経由だとアプリのダウンロードが遅いのではないか?ということです。ということで実際にやってみました。


■条件

対象のアプリのサイズは17.7MB。
以下の2カ所の回線で計測しました。
  • 会社の回線(確かバッファロー, IEEE 802.11g, だいたい300KB/s程度)
  • 自宅の回線(AirMac Extreme, IEEE 802.11n, だいたい1MB/s程度)

■結果

会社の回線・・・ダウンロードに12分~15分程度
自宅の回線・・・ダウンロードに1分前後、インストール完了まで2分ちょっと

それぞれ別の日に計測したので何とも言えませんし、会社の回線は自分以外の人も使うので遅いときがあるのですが、およそ

遅いとき・・・20KB/s程度
早いとき・・・300KB/s程度

とまぁずいぶん幅がある結果になってしまいました。どうやら時間帯や相手のサーバー側の混雑具合によっても速度が違ってくるようです。いずれにせよそんな猛烈に速いわけではないので注意が必要そうです。たとえば100MBぐらいあるゲームアプリをテストしたいときなどは、今まで通りの地味な配布のほうがよい・・・ということもあるかも。


■余談:TestFlightってどれぐらいのファイルサイズまでアップロードできるのか?

と思って調べてみたらありました。500MBだそうです。400MBのファイルを実際にアップした人がいるみたいです。
http://support.testflightapp.com/discussions/questions/60-max-file-size-on-build-upload

2011年2月2日水曜日

"Failed to launch simulated application: Unknown error." が発生したときの対処法



iPhone シミュレーター起動時に表題のエラーが発生したときの対処法です。


表題のエラーは文字通り原因不明の問題が発生したときに表示されるのですが、問題のうちの一つに「バンドルリソース内にMac OSが使用する予約されたリソース名が含まれていると発生する」というものがあります。

たとえば
Contents
Resources
などの名前のディレクトリやファイルをバンドルリソース内に作成してしまうと、ビルドは通るのですがシミュレーター実行時にエラーが発生するようです。これらの予約された名前は使わないようにすると良いです。

2011年2月1日火曜日

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


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


2回戦の記事はこちら: http://akisute.com/2011/05/iphone-ipad-2.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 向けスタイラスが3種類揃ったのを機会に、ちょっと書きくらべをしてみる事にしました。


■さっそく試してみた

以下、実際に手書きで字を書いてみたスクリーンショットを貼り付けます。全て一発勝負で書いています。それぞれ上からスマートペン、Pogo Sketch、oStylusです。

neu.Notes



Penultimate




■考察

スマートペンは本当にまるで普通のペンで紙に書いているように書けます。特に「返」のしんにょうあたりを見るとよくわかるのですが、このような綺麗な曲線が非常に楽に書けます。ただしやや太いためか細かい筆跡が難しいです。

Pogo Sketchはスマートペンより細く、やや滑りが悪いため細かい線に強いですし、曲線もしなやかです。しかしながらよく見ると所々線が途切れていたり短かったりすることがわかると思います。これはタッチの感度が悪いためで、タッチを認識せず線が途切れてしまっているためこのようになってしまっています。

oStylusは感度の良い金属製のペン先とペン先が見えるOリング構造のおかげで、細かい字に圧倒的な強さを誇ります。Pogo Sketchのように感度が足りず線が途切れることもありません。弱点として、全体的に長く重くペン先が平たいため、普通のペンと同じようには書けず、線が直線的になってしまっています。

一番書くのが楽だったのはスマートペン、書くのに時間がかかっているのはoStylusだったと思います。


■まとめ

それぞれ一長一短、特徴ある iPad / iPhone 向けのスタイラスが市場に出回るようになってきて、すごくわくわくする感じです!つい1年2年前まではろくな商品がないと嘆いていたのですが、いまや自分の書きたい物に応じてスタイラスを選ぶことさえできるようになりました。素敵!

2011年1月9日日曜日

Python で 5 分でちょっとした XML ファイルを生成する

なんだか西尾先生が面白そうなことをしてるので、便乗してみることにしました。


■課題

今お仕事で iOS のアプリを書いているのですが、その中で次のような plist ファイルを作成する必要が出てきました。 plist ってのをご存じない方は、要するに XML ファイルだと思ってください。
    <dict>
<key>emojiName</key>
<string>表情(嬉しい)</string>
<key>emojiCode</key>
<string>0xE415</string>
</dict>
<dict>
<key>emojiName</key>
<string>表情(にこにこ)</string>
<key>emojiCode</key>
<string>0xE056</string>
</dict>
<dict>
<key>emojiName</key>
<string>表情(笑顔)</string>
<key>emojiCode</key>
<string>0xE057</string>
</dict>
こんな感じで全部で400項目ぐらい。Webページの資料があるので、そこから転載して上記のような plist にするお仕事です。

私がやってもいいのですが、あいにく手が回りません。そこで最近弊社に入社してきました新人さんにお願いすることにしました。ところがどっこい、本当に一個ずつ手で書いているからなかなか作業が進みません。ほかのお仕事もあるのですが、二日でやっと5分の1程度。これはどげんかせんといかんですね。


■さっそくやってみよう

まずは下準備として、ExcelにWebページから情報を貼り付けて整理します。Excelを中間エディタとして使うことで、行と列の操作が簡単になりますし、そのままコピペするだけできれいに表が完成します。整理し終わったら、Excelからテキストエディタにコピペすると、以下のような TSV (Tab Separated Value) になります。
猫 0xE04F
いぬ 0xE052
ねずみ 0xE053
うさぎ 0xE52C
あとはこれを XML にするだけです。さっそくPythonでやってみましょう。
#!/usr/bin/env python

data = u"""
ここに先ほどのTSVをコピペしてください
"""
template = u"""
<dict>
<key>emojiName</key>
<string>%s</string>
<key>emojiCode</key>
<string>%s</string>
</dict>
"""

lines = data.strip().sprit('\n')
for line in lines:
t = line.sprit('\t')
print template % (t[0], t[1])

すごい原始的なコードですが、これでも十分動きますし簡単です。ってあれ、これ元ネタの西尾先生のコードとほとんど同じじゃないですか><


■その結果

まぁそんなこんなで無事 Python のコードもできたので動かしてみましょう。するとあっというまに目的のXMLが画面に出力されました!こうしてなんと残りの5分の4がたったの半日で終わってしまいました!単純な作業は工夫して楽に楽しく素早く片付けてしまいたいですね。

そうそう、ExcelやGoogle Docsのスプレッドシートを一時作業用に使うのはおすすめです。なんだかんだで表を作るならすごく操作しやすいですし、その後作った表をテキストエディタにコピペすれば TSV が簡単に作れるので、便利です。