Category Archives: Ios

Xcodeでは自分でビルドしたアプリのapplicationDataを.xcappdata(中身はアプリのディレクトリが殆どそのまま入ってる)として取り出すことができ、
この.xcappdataを使うとXcodeからそのアプリの状況を再現してデバッグをすることが出来ます。 

詳しくは以下の記事を読んでみて下さい

自分でビルドしてiPhoneに追加したアプリはXcode の Organizer に表示されるのですが、
AppStoreから入れた場合、デバッグビルドで上書きしてもXcode の Organizerに表示されないことがあります。

今回はAppStoreで入れたものでも、 Xcode の Organizerにでるようにする方法についての話です(もちろん、自分でビルドできるものに限ります)

単純にまとめるとipaとしてビルドして、入れ替えると表示されるようになるので以下のような手順を踏みます。

  1. XcodeのArchievesをする(端末に入れられるCodeSignをつけておく)
  2. Archievesタブ の Distributeボタンを押して、Save for Enterprise Or Ad-hoc Deploymentを選択してipaファイルとして保存する
  3. Devicesタブで、入れ替えたい端末のApplicationsを選んで、Addからipaを選択してアプリを置換する
 
NewImage

このような手順を踏むと、端末のApplicationsに置換したアプリが表示されるようになるので、
Downloadボタンから.xcappdataファイルを保存できるようになります。

こうすることで、Store経由でいれて見つかったバグ等をiOSシミュレータ上で再現できるようになるのでデバッグにとても便利です。 

CoreDataを扱うライブラリであるMagicalRecordをよく使いますが、
MagicalRecord では保存するデータベースファイル(デフォルトはアプリ名.sqlite)の名前指定するメソッドしか用意されていません。

具体的には、Setting up the Core Data Stackにかかれているようにsetup*経由で初期化処理をする際に、ファイル名を指定することが出来ます。


+ (void) setupCoreDataStackWithStoreNamed:(NSString *)storeName;

等ですね。

この時、デフォルトでは

Library/Application\ Support/アプリ名/アプリ名.sqlite

にsqliteファイルが保存されます。

アプリのディレクトリ
├── Documents
├── Library
│   ├── Application\ Support
│   │   └── アプリ名
│   ├── Caches
│   └── Preferences
├── アプリ名.app
└── tmp

しかし、すでに Documents/アプリ名.sqlite というようにDocumentsディレクトリ以下にsqliteファイルが有った場合はそちらが使われます。

MagicalRecord内部ではどのようになっているのかを見てみます。

setupメソッドで渡したstoreNameは、+ (NSURL *) MR_urlForStoreName:(NSString *)storeFileNameを使いNSURLとして取得されています。
+ (NSURL *) MR_urlForStoreName:(NSString *)storeFileName では、NSDocumentDirectoryとNSApplicationSupportDirectory(Application\ Support)の両方をみています。

どちらかに該当するsqliteファイルがあったらそれを利用するようになっているため、
Document/ にsqliteがあったらそちらが使われますし、 Application\ Support/にsqliteがあった場合はこちらが使われます。

+ (NSURL *) MR_urlForStoreName:(NSString *)storeFileName はPublicメソッドなので、MagicalRecordを使っていて保存されているsqliteファイルのパスを取得したい場合は、
直接、NSApplicationSupportDirectoryなどを見ないで、+ (NSURL *) MR_urlForStoreName:(NSString *)storeFileNameを経由するのが無難でしょう。

最初に述べたように、setupメソッドには任意のNSURLを指定するメソッドは用意されていません。
(現時点では、[MagicalRecord setupCoreDataStackWithStore:storeURL]; のようにNSURLを渡すと、内部ではNSURLかを判定してる ため意図通りに動きますが、将来性は保証されてません)

そのためLibrary/Application\ Support/アプリ名/アプリ名.sqlite か Documents/アプリ名.sqlite 以外にsqliteファイルをおいている場合は、
その場所にファイルをコピーするなどの処理が必要になるでしょう。

その他

MagicalRecordの中の人がCore Data Recipes by Saul Mora [Leanpub PDF/iPad/Kindle]という書籍を書くことを検討しているみたいです。
興味がある人は書籍が完成したかどうかの通知に登録しておくといいです。 

デザイナーさんと一緒に仕事する時に、iOSアプリで使う画像のファイル名について悩むことがあります。

例えば、button.png や arrow.png といった、ユニークじゃない名前がファイル名に使われると、
画像が少ないうちはいいですが、画像が多くなると、どこに/いつ使われるべき画像なのかがファイル名から読み取ることが難しくなってしまいます。

また、button01.png のような連番になると、その順番が崩れた時に問題が起きたり、何か命名ルールについて共通の認識が欲しいなーと思っていました。

画像ファイル名の命名規則

New York Times Mobile Team の Objective-C(iOSアプリを書く言語)についてのコーディングルール(コードを書く作法)が公開されていて、

この中に、画像のファイル名の命名規則についてのImage Namingという項目があります。

この作法の中では次のような命名をするべきであると書いてあります。

画像のファイル名は、キャメルケースでそれらの目的について説明する名前であるべきである。
特別な接頭辞は付けないで、
<クラス名 or プロパティ名><色 and/or 場所><状態>.png
というような命名をするべきである。

という感じで書かれています(訳ではないので、原文は Image Namingを見て下さい)

<クラス名 or プロパティ名>がピンと来にくいですが、
要は、<キーワード + 要素名> になると思います。

キーワードは画面の名前や役割などを考えるといいかもしれません。
要素名とは、iOSのUI要素の名前のことなので、ButtonやSwitch、BarButton(ツールバーのボタン)等です。 

色や場所は常にあるわけではないかもしれないので、省いてもいい時もあると思います。 

<状態>は常にあるわけじゃないので、これも省略していい場合も多いと思います。
(主に、selected, highlighted,  disabled があり、normalの場合は省略するのが一般的になると思います)

具体例

ルールだけだと分かりにくいので、具体例で見て行きましょう。
(面倒なので、@2x等のretina表記は省いています) 

カレンダーのセル(ボタン)の選択状態の画像なら

  • CalendarCellSelected.png

逆に触っても反応しない灰色のカレンダーのセル(ボタン)の画像なら

  • CalendarCellDisabled.png
    iOSでは触っても反応しないボタンは灰色になるので、わざわざGray等の色は入れなくていいかもしれません。
両方共、カレンダーのセル(ボタン)の画像であることは共通しているので、ファイル名も似ている感じです。 

次はボタン画像について、

色んなところで使うような、赤い共通のボタン画像なら、

  • CommonButtonRed.png

メインタブメニューの設定タブのアイコン画像なら 

  • MainTabMenuSettingIcon.png

—–

一応、補足としてiOSのUITabBar(タブメニュー)を画像で装飾する場合は、
タブバー(MainTabMenuBackground.png) と 選択状態の時の個別のタブの画像 と 個別のタブのアイコン画像から作られます。

そのため、厳密にするなら、MainTabbaritemSetting.png のような名前になり気がしますが、かえって分かりにくくなるので、意味がつかみやすい感じするといい気がします。

—-

ナビゲーションの戻るボタンなら

  • BackNavigationButton.png

のようなファイル名が考えられます。

原文の方にも Image Naming 、他の具体例が載っているのでそちらも参考にするといいかもしれません。
また、画像をグループで分けられるなら、それらはフォルダにまとめると画像が整理しやすいと思います。

自分の経験上では、最低でも起動画像(Defaults-xxx)とアイコンの画像はそれぞれ別のフォルダにまとめた方がいいです(他の画像と混ぜるのは危険)

おわりに

キャメルケース に馴染みがない場合は全部小文字で_(アンダーバー)区切りでいいと思います。

また、 については、デザインが変わった時にファイル名も変えてしまわないといけなくなってしまうので、自分は含めなくてもいいかなーと思います。

このルールで大事なのは、画像のファイル名から少なくてもそれが何なのかを読み取れるようにすることで、その画像を扱う人達にとってより良くなるという事です。
この命名規則じゃなくて、ボタン画像は Button_xxx みたいにButtonでまとめた方がわかりやすいという場合もあると思います。
この場合でも、Button.png ではなく、button_todo_chek_on.png (Todoのチェックがオンの画像) という感じで説明や状態を入れるといいと思います。

iOSアプリの開発者はObjective-Cで多少長い名前には免疫があると思うので、画像のファイル名も多少長くてもいいので簡潔な説明が入ってるとより良くなると思います。 

iOSアプリで初回起動時にアプリケーションの説明などのチュートリアルを設ける事がありますが、 よく見かけるパターンが何個かあると思います

Chardin.js のように実際の画面で、説明したい部分に説明を載せる感じの方法。

workshirt/WSCoachMarksView のようなスポットライトあてて説明するのも似たようなパターンです。

Chardin js 2013 07 11 16 21 34

もう一つは、これが実装もしやすいのでよく見かける気がしますが、
チュートリアル用の画面を用意して表示するタイプ。

evernoteのアプリなどもそうですが、PageControlを使ってスワイプして進めるタイプが多いです。

2013 06 19 15 56 12

これは、StoryBoardで実際にそれぞれの画面を作って、
NavigationControllerで遷移するような仕組みを作ればいいだけなので、作りやすいのです。 

後は、CMPopTipView等のツールチップを表示するものを使って必要な要素にツールチップで説明を表示するパターンです。

最初の方法と似たような感じですが、ツールチップを無視して進めたりする場合が多いので緩い感じのチュートリアルです。

NewImage

今回は、最後のツールチップを使ったチュートリアルをどういう感じで実装すればキレイにできるかを少し考えたメモです。
ツールチップのUI自体はCMPopTipViewのライブラリを利用します。

実装

サンプルは azu/tooltip-navigation-app においてあります。

動作は以下のような感じです

先に、CMPopTipViewの簡単な説明をしておきます。

    self.popTipView = [[CMPopTipView alloc] initWithMessage:tutorialData.message];
    self.popTipView.delegate = self;
    [self.popTipView presentPointingAtView:表示箇所にしたいView inView:self.view animated:YES];

表示箇所にしたいView に、あるボタンのViewを渡してあげれば、そのボタンに対してself.popTipViewのツールチップが表示されるという感じです。
つまり、表示するにはViewの参照を示してあげる必要があります。 

プロジェクトの関係してるファイルは以下のような感じです

├── Config
│   ├── UserDefaults.h // UserDefaultを管理
│   └── UserDefaults.m
├── Main
│   ├── MainModel.h // 結局使わなかった…
│   ├── MainModel.m
│   ├── MainViewController.h // メイン画面
│   └── MainViewController.m
├── Tutorial
│   ├── TutorialData.h // チュートリアルデータのモデル
│   ├── TutorialData.m
│   ├── TutorialDataManager.h // チュートリアルの管理
│   └── TutorialDataManager.m

これを作る際に気をつけていた事は、

  • 表示するツールチップの内容と場所は一箇所で管理したい
  • MainViewControllerがチュートリアルの内容に関するデータを持たない事
  • TutorialDataManagerでツールチップの内容を管理したい
  • しかし、TutorialDataManagerがViewの参照を直接持たない事

MainViewControllerで全部持ってしまうが一番単純ですが、コントローラーが肥大化するので避けたいと思います。
しかし、TutorialDataManagerでツールチップの表示内容と場所(Viewの参照)を管理したいのですが、
TutorialDataManagerがViewの参照を直接持つのは良くない感じがしたので、代わりに以下のようなenumを定義して参照の代わりにこちらを管理するようにしました。

typedef NS_ENUM(NSUInteger, MainViewOutletType){
    MainView_InformationButton,
    MainView_FirstButton,
    MainView_SecondButton,
    MainView_ThirdButton,
};

TutorialDataManagerでは以下のようにツールチップの内容と場所を配列にして管理するようにしました。 (この並び順にツールチップを出していって、tutorialTypeは一度出たツールチップは再度出ないようにするための識別子)

@[
        [TutorialData dataWithMessage:@"ここをタップする再度ヘルプを見られます" outletType:MainView_InformationButton tutorialType:TUTORIAL_STEP1],
        [TutorialData dataWithMessage:@"一番目のボタンです" outletType:MainView_FirstButton tutorialType:TUTORIAL_STEP2],
        [TutorialData dataWithMessage:@"二番目のボタンです" outletType:MainView_SecondButton tutorialType:TUTORIAL_STEP3],
        [TutorialData dataWithMessage:@"三番目のボタンです" outletType:MainView_ThirdButton tutorialType:TUTORIAL_STEP4],
];

これで、TutorialDataManagerは実際のViewの参照を知らなくても済むようになりました。

MainViewControllerに、outletTypeに対する実際のViewを返すようにすればデータとViewの対応関係が取れます。

- (id)viewForMainViewOutletType:(MainViewOutletType) type {
    switch (type) {
        case MainView_InformationButton:
            return self.informationButtonItem;
        case MainView_FirstButton:
            return self.firstButton;
        case MainView_SecondButton:
            return self.secondButton;
        case MainView_ThirdButton:
            return self.thirdButton;
    }
    return nil;
}

次はツールチップの表示方法について

一度出たツールチップは表示しないようにしたいため、ツールチップをタップされた時にその情報(tutorialTypeを元にした)を保存します。 ツールチップを表示する際に、CMPopTipViewのView.tagにtutorialTypeの値を入れておきます。

    self.popTipView = [[CMPopTipView alloc] initWithMessage:tutorialData.message];
    self.popTipView.tag = tutorialData.tutorialType;
    self.popTipView.delegate = self;

CMPopTipViewはツールチップがタップされたことはdelegateで取得できるため、 以下のようにdelegateで、popTipViewからtagを取り出してその値を保存します。

// ツールチップがクリックされたら、CheckPointを更新 -> KVOで検知
- (void)popTipViewWasDismissedByUser:(CMPopTipView *) popTipView {
    self.popTipView = nil;
    TutorialType tutorialType = (TutorialType)popTipView.tag;
    [self.tutorialModel setCheckPoint:tutorialType];
}

詳しくはazu/tooltip-navigation-appのコードを見たほうがわかりやすいですが、
値を保存する際に、checkPoint  というプロパティを経由するようにして、そのプロパティをKVOで監視するようにしているので、
値が保存された際に自動で、@selector(showNextPopTip) が呼ばれるようになっていて、次のツールチップが表示されます。

そのため、初回以外は明示的に@selector(showNextPopTip)を呼ぶ必要はなく、すべてのツールチップが表示->保存された時点でツールチップは表示されなくなります。
(サンプルではリセットボタンを左上に置いてある)

サンプルプロジェクトでは 、MainViewControllerとTutorialDataManagerに分けてチュートリアルのツールチップを管理できるようにしましたが、
まだ、お互いが依存しすぎてる気がするのでもっとキレイに書けるかもしれません。(PullRequestすればいいと思います)

iPhoneとiPadの画面を見比べながら弄りたいときなどに複数のStoryBoardをポップアップウィンドウそれぞれに開きたいと思いました。
StoryBoard等はなぜか一つのものしか同時のポップアップ表示できない様になっています。(何かやり方があるのか…)

左右分割で別々のStoryBoardを開くのは一応できて、

Assistant Editorで左右分割した状態で、

MedicineNote xcworkspace  MainStoryboard iPad storyboard 2013 06 13 15 24 30

片方はファイルを選択して、もう片方をManualでStoryBoardファイルなどを手動で選べば

2013 06 13 at 15 25 06

一つのXcodeで左右に複数のStoryBoardを開くなどは可能です。

MedicineNote xcworkspace  MainStoryboard iPad storyboard 2013 06 13 15 26 23

一応これで見られますが、かなり使いにくい感じです。

もう一つの方法は、単純に複数のプロジェクトをファイルを開けばいいという発想です。

プロジェクトのコピーそのものを作るのは微妙なので、git-new-workdirを使います。 

$ git-new-workdir . ../copy_project

とやれば、copy_projectが別のディレクトリにそのまま作られるのでcopy_projectと本体を一緒に開けばいいだけです。

こうすれば、Xcodeのプロセスが2つなりますが、それぞれポップアップウィンドウを持てるので複数のStoryBoardを並べて見るのに便利です。

MedicineNote xcworkspace  MainStoryboard iPad storyboard 2013 06 13 15 31 30

MedicineNote xcworkspace  MainStoryboard iPad storyboard 2013 06 13 15 33 49

 

ポップアップウィンドウは設定の Uses Speparate Windowを設定しておけば、別のウィンドウとしてStoryBoard等のファイルを開けます

General 2013 06 13 15 34 41