タオルグリップ法を支援するiPhoneアプリのプロト作って見た

Pocket

タオルを短時間握るだけで血圧が改善できるというハンドグリップ法なるものの記事を見つけ、iOSの通知機能の手習いで支援アプリを作って見た。

こんな感じで、アプリ起動してスタートしておくと、以後は他のことしてても定期的に通知が来るという簡単なもの。片手でタオルを握り、もう片方でスマフォをいじってればいい、という。どうだろ、ちゃんと作り込んだらニーズあるかしら。

13260069_10209552332352030_4460919170323275006_n

技術的にはあまり面倒な部分はなかった。実際にはアプリがフォアグラウンドにいる時はそのアプリ自身の通知は受け取れないので、スタート後はホームを押なりスリープするなりしてもらうか、きちんと実装してアプリがフォアグラウンドにいるときにはアプリ上にメッセージが出るよう同期処理をする必要がありそう。スタートした後、自身で終了するのが技術的には可能なんだけど、ガイドライン的には非推奨っぽい。

でまぁ作ってひと月ほど立つんですが、まぁほとんどの日は忘れますねw。あと防水端末でお風呂で使いたいなぁとか。そういう意味ではAndroidで作るべき?

あと一分休憩をいれることになってるけど、左右交互なら別にいらなくないのか?とか。今は、右->休憩->左->休憩->…だけど、休憩を挟まず左右交互にやるエクスプレスモードも作りたくなってる今日この頃。

KeyRemap4MacBookでSublime Text2を除外する

Pocket

今日、Facebook経由でSublime Text2というプログラマ向けテキストエディタを知り、ヨサゲなので使い始めてみました。とりあえず何がスゴいかはこちらの日本語入門動画がオススメです。

で、試していて最初に困ったのが、Ctrl-AがEmacsキーバインドの行頭移動ではなく全選択になっていた点。さっそくカスタマイズしようとしたらそんな設定はどこにもない。あぁ、そうかMacのキーバインドをカスタマイズするKeyRemap4MacBookでWindowsライクにしてたからな。Emacs、Terminal、VMWareなどでは適用除外してくれるのですっかり忘れてた。しかし、標準ではKeyRemap4MacBookがSublime Text2などという新しいエディタを知っているわけもなく、除外設定もありません。

ということで、こちらの記事を参考にさせていただき、自作設定を追加しました。まず、元となる標準設定の、Use PC Style Copy/Paste、Use PC Style Undo、Use PC Style Select All、Use PC Style Save(それぞれTerminalやEmacsを除く版の方)の4つの設定を「/Library/org.pqrs/KeyRemap4MacBook/app/KeyRemap4MacBook.app/Contents/Resources/include/checkbox/for_pc_users.xml」から引っこ抜いてきます(<item>~</item>がひとまとまりです)。次に、KeyRemap4MacBookの設定パネルから「Misc & Uninstall」タブを開き「Open private.xml」をクリック。Finderで表示されたウインドウをエディタで開き、しかるべき位置にペーストします。元のfor_pc_users.xmlを直接編集してしまうと、おそらくバージョンアップ時に上書きされてしまうので、必ずカスタム設定はprivate.xmlの方に書いておくようにしましょう。

で赤字の位置を修正します。

  • Sublimeのアプリ名を認識させる為にappdefタグを追加(ちなみにcom.sublimetext.2はアプリバンドルの中のinfo.plistを見ました)。
  • identifierタグの中身がカブると怒られるので、private.や_and_sublimeを追加
  • notタグにappdefで定義したappnameを追加

以上。保存し、KeyRmap4MacBookコントロールパネルで「Change Key」タブに戻り、「ReloadXML」ボタンを押せば項目一覧の一番上に新設定が出現するはずです。

<?xml version="1.0"?>
<root>
   <appdef>
        <appname>SUBLIME2</appname>
        <equal>com.sublimetext.2</equal>
    </appdef>

    <item>
      <name>Use PC Style Copy/Paste</name>
      <appendix>(Control+C to Command_L+C)</appendix>
      <appendix>(Control+V to Command_L+V)</appendix>
      <appendix>(Control+X to Command_L+X)</appendix>
      <appendix>(Except in Terminal, VM, RDC, Emacs, X11, Eclipse, SublimeText2)</appendix>
      <identifier>private.remap.copy_paste_winstyle_no_term_and_sublime</identifier>
      <not>EMACS, TERMINAL, VIRTUALMACHINE, REMOTEDESKTOPCONNECTION, X11, ECLIPSE, SUBLIME2</not>
      <autogen>–KeyToKey– KeyCode::C, VK_CONTROL, KeyCode::C, ModifierFlag::COMMAND_L</autogen>
      <autogen>–KeyToKey– KeyCode::V, VK_CONTROL, KeyCode::V, ModifierFlag::COMMAND_L</autogen>
      <autogen>–KeyToKey– KeyCode::X, VK_CONTROL, KeyCode::X, ModifierFlag::COMMAND_L</autogen>
    </item>

    <item>
      <name>Use PC Style Undo</name>
      <appendix>(Control+Z to Command_L+Z)</appendix>
      <appendix>(Except in Terminal, VM, RDC, Emacs, X11, Eclipse, SublimeText2)</appendix>
      <identifier>private.remap.undo_winstyle_no_term_and_sublime</identifier>
      <not>EMACS, TERMINAL, VIRTUALMACHINE, REMOTEDESKTOPCONNECTION, X11, ECLIPSE, SUBLIME2</not>
      <autogen>–KeyToKey– KeyCode::Z, VK_CONTROL, KeyCode::Z, ModifierFlag::COMMAND_L</autogen>
    </item>

    <item>
      <name>Use PC Style Select All</name>
      <appendix>(Control+A to Command_L+A)</appendix>
      <appendix>(Except in Terminal, VM, RDC, Emacs, X11, Eclipse, SublimeText2)</appendix>
      <identifier>private.remap.select_all_winstyle_no_term_and_sublime</identifier>
      <not>EMACS, TERMINAL, VIRTUALMACHINE, REMOTEDESKTOPCONNECTION, X11, ECLIPSE, SUBLIME2</not>
      <autogen>–KeyToKey– KeyCode::A, VK_CONTROL, KeyCode::A, ModifierFlag::COMMAND_L</autogen>
    </item>

    <item>
      <name>Use PC Style Save</name>
      <appendix>(Control+S to Command_L+S)</appendix>
      <appendix>(Except in Terminal, VM, RDC, Emacs, X11, Eclipse, SublimeText2)</appendix>
      <identifier>private.remap.save_winstyle_no_term_and_sublime</identifier>
      <not>EMACS, TERMINAL, VIRTUALMACHINE, REMOTEDESKTOPCONNECTION, X11, ECLIPSE, SUBLIME2</not>
      <autogen>–KeyToKey– KeyCode::S, VK_CONTROL, KeyCode::S, ModifierFlag::COMMAND_L</autogen>
    </item>

</root>

dgKeyframe2Chapeter 1.3をリリースしました

Pocket

TMPGEncシリーズが書き出すチャプター情報ファイル.keyframeをmp4box等でmp4ファイルに埋め込める.chapters.txtに変換する拙作ツールdgKeyframe2Chapterを1.3にバージョンアップしました。ダウンロードリンクはこちら。リンク先記事のコメント欄でいただいた要望、

  • 24fps(厳密には23.976fps)のソースに対応

という更新内容です。keyframeファイルは通し番号のフレームナンバーなので、対象ファイルが30fpsか24fpsかで変換後の時分秒の値が全然違うものになってしまうわけですね。指摘されるまで考えもしてなかったw。

まずは単純にプルダウン形式で選べるようにしておきました。ファイル名に_24とかついてたら自動判別、、とかも考えたけど、たぶんdgシリーズを使ってる人はファイル名は専用フォーマットを守っているだろうとか(^^;)。

一応とあるタイトルで変換後の数字とTMSR4上での表示を見比べてみましたが大丈夫な気がしてます。もし不具合などありましたらお知らせ下さい。

AppStore売り上げ監視アプリAppSalesの微修正

Pocket

AppStoreのアプリ売り上げをiPhoneやiPadでチェックできるAppSales-Mobileというオープンソースアプリがあります。Appleが提供している開発者向けWebサービスiTunes Connectにログインして最新データを取得し、グラフなどで売り上げ状況を確認できます。

IMG_2411オープンソースで配布されており、自分でXcodeでビルドして実機にインストールする必要がありますが、そもそもが開発者向けなのでそこら辺は楽勝でしょう。

さて、そのAppSalesでデータ取得後に「Downloading reports from iTunes Connect failed. Please try again later or check the iTunes Connect website for anything unusual. Could not login. Please check your username and password.」というエラーが出るようになりました。iTunes Connect側のHTMLが変わったり、なにかエラーが出ている時には起きがちなことだし、データ取得自体はちゃんと出来てたのでしばらく放置していたんですが、σ(^^)のプログラミング師匠の田中さんが原因を突き止めて教えてくれたので、許可をいただきシェアしておきたいと思います。

原因としては、iTunes Connectにログインできているかをチェックする判定がNGになっているせい。それをしているのが、ReportDownloadOperation.mの252行目辺りのここ。

NSString *signoutSentinel = @"Sign Out";

画面内に「Sign Out」というテキストリンクがある=ログインしている、とロジックみたいです。ところが、現在のiTunes Connectが(日本語環境の人に対して)実際に返している文字列は“サインアウト”。ということで、

NSString *signoutSentinel = @"サインアウト";

とすることでエラーが出なくなりました。もしくは、田中さんのオススメでは、

NSString *signoutSentinel = @"sign-out";

とclass属性に使われている文字列を使うことで、将来更に文言や言語判定ロジックがかわった時にも対応できるのではとのことです(全HTMLレスポンス中から検索してるからタグや属性を指定しても良いそうです)。

実害は少なかったとは言えやはり気になっていたので、スッキリしました。ありがとう田中さん!お礼に最新アプリ、iPad向けの(MicrosoftのVisioみたいな)ダイアグラム図作成ツールViDiaの宣伝リンク貼っときます

iOS Developer Programの期限内更新覚え書き

Pocket

iOS Developer Progaramの某社名義のライセンスが期限切れ間近だったので更新手続きを行いました。ネット上の掲示板のやりとりとかだと期限切れちゃったけどどうしたらいい?的なものが多く、普通に切れる前に更新する手順が示されておらずちと手探りでした。夏には自分名義のものをしなければならないので覚え書き。

まずAppleから「Don’t let your iOS Developer Program membership expire.」というHTMLメールが来ます(この時点で残り15日と表示。実際は一月前から手続きできるっぽい?)。文中最後に「Renew today」というボタンがあるのでクリック。ここは某社側でやってもらったので細かい手順はわかりませんが、基本は初回同様AppleStoreにリダイレクトされカートに更新ライセンスが入っているので決済すればいいようです。

AppStoreからの「ご注文成立のお知らせ」というメールに少し遅れて「Thank you for renewing your Apple Developer Program」というのが届きます。とりあえずこれで更新は完了しており、iTunes Connect側では有効期限表示が更新されていました。

あわせてdeveloper.apple.comのiOS Probisioning Portalでプロビジョニング周りの更新が必要になります。まずは年間100台まで登録できるテスト用端末のUDIDについて。更新前の注意書きには、更新後の初回ログイン時に限って不要なものを削除できる、と書かれてたので、そういう画面に強制的にとばされるのかなと想像してたんですが、それらしい表示はなにも出ず。削除自体は通常通り「Devices」画面でできたんですが、残り台数は増えませんでした。ちょっと損した感じ。まぁ3台分だからいいんですが。確実なのは更新手続き前に削除をしてしまっておくことだなと。

またプロビジョニングについてはXcodeのOrganizerで一覧を出すと期限が近いものに「Renew」ボタンがついていました。どうも普通はこれをクリックすれば更新できるらしいのですが、何故か上手くいかず。PortalでEditして作り直しても有効期限は延びません。結局、まずは開発者自身のCertificatesを更新ということです。OSXの「キーチェーンアクセス」を起動し、左上で「ログイン」、左下で「自分の証明書」を選び、右ペインのリストから目的の証明書を探します。通常、「iPhone Develpoer: (名前)」と「iPhone Distribution: (名前)」があるはずです。どちらかの左の三角ビュレットをクリックすると、下に鍵アイコンと名前がついた秘密鍵が現れます。これを右クリックし「”(名前)”を使って認証局に証明書を要求…」を選択します。「証明書アシスタント」が開くので、開発者登録に使ったAppleIDのメールアドレスと通称(自分の名前)を入れ、「ディスクに保存」を選択して「続ける」をクリック。これで新しい証明書の発行依頼書(=CSR)が出来ます。あるいは初回申請時に使ったCSRファイル自体が残っていればそちらを再利用もできると思われます。

次にPortalのCertificatesを開き、Development、Distributionそれぞれのタブから現行の証明書を「Revoke」し、再作成します。その時の画面で上記CSRファイルを指定します。プロビジョニング同様、数秒で新しい証明書ができあがるので少し間を置いてリロードするなりします。「Download」ボタンが出現したらクリックしてダウンロードし、「キーチェーンアクセス」にドラッグ&ドロップして追加。古いものは消してしまった方が無用のトラブルにならないと思います。心配なら右クリックから書き出しもできます。

この状態ならXcodeの「Renew」ボタンでプロビジョニング更新できるかな?と思って試してみたんですがダメ。ちぇ~。

仕方ないのでまたもやPortalに行き、Provisioningを開きます。今まで紐付いていた証明書がRevokeされてるので各Provisioningも無効化されています。EditしてSubmitしなおします(適当にdevicesのチェックをOFF/ONするなどして更新を発生させないとSubmitボタンが押せないかも知れません)。出来上がったプロビジョンファイルをダウンロードしてXcodeのOrganizer上にドロップ。古いものを削除しれば一応完了です。

試しにad-hoc配布用のアーカイブを再度書き出して配布サーバー上のipaファイルを上書きし、iPadで上書きインストールしてみたところ、ちゃんと新しいプロビジョニングプロファイルもインストールされました。古いものが残っていると期限切れ警告が出てウザいので、「設定」->「一般」->「プロファイル」から削除しておきます。

Xcode上のRenewボタンからできなかったのが悔しく、何か他の方法があるのかも知れませんが、まぁとりあえず上手く言ったパターンのログです。

iOSアプリの外部ディスプレイで全画面表示できない時の覚え書き

Pocket

おやゆびでおをApple Digital AVアダプターなどを使ってテレビに映せるようにしたいと年末年始に取り組んでました。iPad2やiPhone4Sのようにミラーリング対応機種なら一応映すことは可能でしたが、再生画面がフルスクリーンにならなかったりしましたし、それ以外の機種のことも考えるとアプリ側できっちり対応する必要がありました。

■アプリをサブディスプレイに対応させる

本体デバイス用のベースUIViewとしてViewForMain、外部ディスプレイ用としてViewForSubというインスタンスがあるとします。

if ([[UIScreen screens] count] >1) {
        //スクリーンが2つ以上存在する=外部ディスプレイ有り
       UIScreen *secondScreen = [[UIScreen screens] objectAtIndex:1];       
       UIWindow *secondWindow = [[UIWindow alloc] initWithFrame:[secondScreen bounds]];       
       secondWindow.screen = secondScreen;
       ViewForSub = [[UIView alloc] initWithFrame:[secondScreen bounds]];
       ViewForSub.backgroundColor = [UIColor blackColor];

       //以下、ViewForSubに必要なUI部品をaddSubviewしていく
       //必要に応じて、ViewForMainにUILabel等を置いて「テレビ画面出力中」などと表示する
   } else {   
       //外部ディスプレイ無し
       (以下、ViewForMainに必要なUI部品をaddSubviewしていく)

   }

[UIScreen screens] cound]でOSが認識しているモニタの数がわかるので、それが1より多い場合はサブモニタがあるということで、UIScreenクラスのsecondScreenオブジェクトに2番目(objectAtIndex:1)を入れ、更にUIWindowsを入れ、UIViewを入れてやります。サイズは全てsecondScreenのものを引き継いでおきます。これで後は普通にUI部品を配置して利用できます(もちろんUIButton等を置いても押せませんが)。

とりあえずこんな感じで外部ディスプレイ側に表示できました。実際には、アプリ起動中にアダプターが挿抜されたのを検知して切換処理をする等の仕組みが必要になりますがここでは割愛。というかまだ未着手…

■全画面で表示されない?

さてそこまではAppleのドキュメントやブログ記事などを参考に辿り着いたのですが、なぜかフルHDモニタに接続した時に全画面表示されずに4辺に黒縁が出てしまう現象が発生。iOSシミュレーター上で仮想1280×720ディスプレイを使っても発生しません。

SDIM0068

MPMoviePlayerはもとより、UIWindowsやUISreenインスタンスのbound.sizeを取ってみてもちゃんと1920×1080になっているにも関わらず、です。写真の様にポーズアイコンを座標0,0で置いてみると、動画部分の左上隅に来ます。アプリ的には1920×1080で動画を表示しているつもりっぽい。モニタ側で入力信号情報を表示するとこれまた1920×1080。試しに手動でboundを2000×1200とかにすると黒縁部分が減ります。この数字をつきつめればピッタリ全画面にはできるでしょうがどう考えても拡大->縮小とリサイズが2回起きて無駄だし画質も損なわれるでしょう。MPMoviePlayerの全画面表示系のプロパティをあれこれいじっても解決せず。UIScreenより上の階層で何かが画面を1割くらいスケーリングしてそれを1080pでアダプターに送出しています。

でまぁ足かけ二日ほど悩んだ挙げ句、

secondScreen.overscanCompensation = UIScreenOverscanCompensationInsetApplicationFrame;

で解決できました。オーバースキャンという言葉にもっと早く思い至っていれば検索もヒットさせられたかも知れません。もうアナログ時代の言葉だと思って記憶の底に沈んでましたw。overscanCompensationプロパティには3つの定数があるんですが、なんだか一番全画面っぽくないコイツが勝利の鍵でした。公式ドキュメントはこちら。iOS5からの対応みたいですが、とりあえずiOS4.3シミュレーターではクラッシュなどはしないようですが、iOS4.3の実機だともしかすると全画面化されないままかも知れません。

SDIM0067

見事、フルHD画面いっぱいに動画を再生できるようになりました。

 

今のところ日本語でoverscanCompensationに関して触れている記事はヒットしなそげので、参考に紹介しておきます。

パスワード入力欄をブルブルする

Pocket

iOS用アプリでパスワード入力UIを作ってるのですが、無効なパスワードを入力した時のフィードバックをどうしようか考えて、Mac OSX Lionのログイン画面のように、パスワード入力フィールドが「イヤイヤ」って感じで左右に揺れる感じにしようと思いました。いっそこれくらいCocoaの標準メソッドとしてあってくれても良さそうなものでしたが、UITextfieldのリファレンスを見る限りはなさそうだったので自分で実装してみました。さして高度なことしてるワケでもないですし、あんまりエレガントなソースでないかも知れないですが晒しておきます。もっとこうすりゃいいんじゃね?的なコメントも歓迎。

2011.10.27追記:

s1tnkさんにアドバイスいただいて大幅な簡略化に成功したので、更新版を貼ります。

//パスワードが不適切だった場合に入力欄を揺らす
-(void)shakePasscodeField {

    [UIView setAnimationRepeatAutoreverses:TRUE]; //最初の位置に戻る
    [UIView animateWithDuration:.05
         animations:^(void) {
             passcode.center = CGPointMake(passcode.center.x – 10, passcode.center.y);
         }
         completion:^(BOOL finished) {
             [UIView animateWithDuration:.05 animations:^(void) {
                  passcode.center = CGPointMake(passcode.center.x + 10, passcode.center.y);                                             
                  }
                  completion:^(BOOL finished) {
                      [UIView animateWithDuration:.05 animations:^(void) {
                           passcode.center = CGPointMake(passcode.center.x – 10, passcode.center.y);
                           }
                           completion:^(BOOL finish) {
                               [UIView animateWithDuration:.05 animations:^(void) {
                                    passcode.center = CGPointMake(passcode.center.x + 10, passcode.center.y);
                                    }];
                           }];
                  }];
         }];

1メソッドで完結できたのでほとんど説明は不要だと思います。passcodeというのがテキスト欄の名前です。

旧バージョンとの違いは、

  • setAnimationRpeatAutoreversesの存在を教わり、左に行って戻る、右に行って戻るの2フェーズで表現できた
  • completion引数に次の処理をブロックで丸ごと角ことでアニメーションを連鎖記述できるようになった
  • 結果として2フェーズx2回を1つのメソッドに完結させ、状態変数を一切なくせた
  • durationは往復なので時間を倍の0.05にした

という辺りです。振動回数のカスタマイズはやりにくくなっちゃいましたが。

 

実際の動作はこんな感じ(IE9かiOSのSafari位しかインラインで再生できないかも)。

動画がインラインで再生されない人は、こちらからMP4をダウンロードして再生して下さい(44KB)。

■以下旧バージョン

前提として、.hファイル当たりで、

IBOutlet UITextField *passcode;
INT gakuburuCounter;
BOOL isFirstLoopForShakePasscodeField;

という3つのオブジェクト、変数を宣言してあるとします。

んで、.mファイルでメソッドを2つ。

//パスワードが不適切だった場合に入力欄を揺らす
-(void)shakePasscodeField {

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDuration:.1];     
    [UIView setAnimationDidStopSelector:@selector(shakePasscodeFieldLoop)];
    switch (gakuburuCounter % 3) {
        case 0:
            //左へ動くフェーズ 
            [UIView setAnimationDuration:.025];     
            passcode.center = CGPointMake(passcode.center.x -10, passcode.center.y);
            break;
        case 1:
            //左位置から右へ動くフェーズ 
            [UIView setAnimationDuration:.05];     
            passcode.center = CGPointMake(passcode.center.x +20, passcode.center.y);
            break;
        case 2:
            //右位置から初期位置へ戻るフェーズ 
            [UIView setAnimationDuration:.025];     
            passcode.center = CGPointMake(pa
sscode.center.x -10, passcode.center.y);
            break;
        default: 
            break;
    }
    [UIView commitAnimations];
   
}

-(void)shakePasscodeFieldLoop { 
    if (isFirstLoopForShakePasscodeField == TRUE) {
        gakuburuCounter = -1;
        isFirstLoopForShakePasscodeField = FALSE;
    }

    if (gakuburuCounter < 5) {
        gakuburuCounter++;
        [self shakePasscodeField];
    }
}

こんな感じ。

#つーか、そろそろコードをカラーリングしてくれるライブラリを導入した方がいいんだろうなぁ…

パスワードを検証してNGだったら

isFirstLoopForShakePasscodeField = TRUE;
[self shakePasscodeFieldLoop]

って感じで呼びます。

簡単に補足しておくと、テキストフィールドが定位置から左に10ピクセル、右に20ピクセル、左に10ピクセルと移動するのを1つのセットとします。ただしこれを単純に連続して呼ぶと、前のアニメーションが終わらないうちに次が実行されてしまい、正しく移動してくれないので、1つのアニメーションが終わる度に次のステップのアニメーションを実行する仕組みが必要でした。そこでそれを管理するメタメソッド(そんな言葉あるのか?)としてshakePasscodeFieldLoopを作りました。isFirstLoopForShakePasscodeFieldフラグがTRUEの状態で呼ばれると、ループカウンタgakuburuCounterをリセット(ここでは-1)して、フラグをFALSEにします。そしてgakuburuCounterをカウントアップしながらshakePasscodeFieldを呼び出します。呼ばれたshakePasscodeFieldではgakuburuCounterを3で割った答えを使って順に3つの動作パターンを実行し、アニメーションが完了した後でshakePasscodeFieldLoopを呼びます。このルートで呼ばれたshakePasscodeFieldLoopはisFirstLoopForShakePasscodeFieldがFALSEなのでカウンターはリセットせず、カウントアップだけして再度isFirstLoopForShakePasscodeを呼びます。これで、gakuburuCounterが-1~5にカウントアップする過程で、各フェーズが順に2ループ実行されます。

カスタマイズですが、-10とか20ってのが移動ピクセル数です。左に10、右に20、左に10って感じで辻褄を合わせます。つまり、振幅を倍にするなら、-20、40、-20とかするワケです。

 

shakePasscodeFieldLoop内の5を8にするとブルブルが3回になります。以降3を足すと1セット増加です。

shakePasscodeField内のUIView setAnimationDurationの値がブルブルの速度です。case 1のところは移動距離が倍なので所用時間も倍にしておくのがポイントです。

Xcodeで幽霊.xibに困った時の覚え書き

Pocket

Xcodeで開発をしていて、あるタイミングからInterface Builderから.xibファイルに対して行った変更がビルドに反映されなくなってしまいました。

  • Clean
  • Clean Build Folder (Option押しながらClean)
  • シミュレーター上のアプリを削除
  • Xcodeの再起動
  • OSの再起動

なども効果無し。挙げ句、Xcode上から当該.xibファイルを削除(他フォルダに移動)してもビルドしてくれやがります。Xcode上でもFinder上にも存在しないゴーストがどこかに残ってる!

結果として解決したのは、海外の掲示板stack overflowのこのエントリの一番下(記事執筆時)のコメント。よりによってusefulカウンタが0(笑)。そろそろσ(^^)もIDとって評価記入するようにしないとなぁ。

で、具体的にはXcodeのもってるキャッシュを削除。その場所は、

/Users/(ユーザ名)/Library/Developer/Xcode/DerivedData/

ここに今までビルドしたプロジェクト毎のフォルダがあるので、それを丸ごと削除。Lionだとユーザのホーム下のLibraryは不可視になってるので、ターミナルを使うが良いでしょう。

どうもここのキャッシュはXcode上で元ファイルが消されても消されずに使われてしまうっぽいです。では元々何故起きたかについてですが、多分、.xibをローカライズして、実体ファイルがen.lprojとja.lprojに移動/コピーされた時の参照が正しく書き換わらなかったっぽい気がしています。なんかやり方が悪かったんですかね?これによってXcode上では実体ファイルである各言語別のファイルを更新し続けたものの、ビルドには親フォルダにある(あった)ファイルのキャッシュが使われ続けてしまった、ということではないかと思っています。

同日追記:

@s1tnkさんより、Xcode4から同じことをする方法を教わりました。こっちの方が簡単そげ。この作業自体はエラーのハイライティングが壊れた時とかにもする一般的なものみたいです。特定プロジェクトがなんとなく調子悪いぜって時のお試し作業の1つとして覚えておくと良さそうです。

  1. Organizerを開く
  2. 目的のプロジェクトを選択する
  3. Delived Dataという見出しの右にある「Delete…」をクリック

 

delete_derivedData