iOS開発のデバッグツールchiselの紹介

すっかりblog放置で、1年以上ぶりのエントリになります。(汗)

先日のyidev@恵比寿勉強会参加させて頂き、その際の@dealforestさんの発表でfacebook/chiselというツールを知りました。

これがなかなか便利そうなので、紹介します。

@dealforest さんのblogエントリ
yidev@恵比寿勉強会 で chisel について発表してきた (動画付き)

chiselは、LLDBのコマンドラインからpythosスクリプトでlldbの機能をラップして便利に使うコマンド群のようです。

そもそも私はLLDBをまったく使ってきませんでしたが(汗)、これでNSLog差し込みが減って楽になりそうです。

インストール

1. brew install chisel でインストール

2. ~/.lldbinit-Xcodeに、以下を記載(fblldb.pyのパスは環境に合わせて記載してください。)

command script import /usr/local/Cellar/chisel/1.1.0/libexec/fblldb.py</div>

(.lldbinit だと、Xcodeから起動したLLDBでは呼ばれないので注意。)

Xcodeのlldbプロンプトで、helpとたたくと、chiselで使えるコマンド群が表示されます。さらにhelp <command> で、コマンドの詳細説明が出力されます。


(lldb) help
The following is a list of built-in, permanent debugger commands:
〜略〜
pca -- Run Python function __FBPrintCommands_pca
pcells -- Run Python function __FBPrintCommands_pcells
pclass -- Run Python function __FBPrintCommands_pclass
pinternals -- Run Python function __FBPrintCommands_pinternals
pinvocation -- Run Python function __FBInvocationCommands_pinvocation
pivar -- Run Python function __FBPrintCommands_pivar
presponder -- Run Python function __FBPrintCommands_presponder
ptv -- Run Python function __FBPrintCommands_ptv
pvc -- Run Python function __FBPrintCommands_pvc
pviews -- Run Python function __FBPrintCommands_pviews
show -- Run Python function __FBDisplayCommands_show
taplog -- Run Python function __FBFindCommands_taplog
unborder -- Run Python function __FBDisplayCommands_unborder
unmask -- Run Python function __FBDisplayCommands_unmask
visualize -- Run Python function __FBVisualizationCommands_visualize
vs -- Run Python function __FBFlickerCommands_vs
wivar -- Run Python function __FBDebugCommands_wivarFor more information on any particular command, try 'help '.
(lldb) help pvc
Print the recursion description of <aViewController>.

Arguments:
  <aViewController>; Type: UIViewController*; The view controller to print the description of.

Syntax: pvc <aViewController>

This command is implemented as FBPrintViewControllerHierarchyCommand in /usr/local/Cellar/chisel/1.1.0/libexec/commands/FBPrintCommands.py.

(LLDB adds the next line, sorry...)
Syntax: pvc
(lldb)

便利なコマンドたち

pvc

ViewControllerの階層構造を表示してくれます。
pvc <viewController> で、指定したViewController配下の階層構造を表示します。


(lldb) pvc
 <UINavigationController: 0x109268650; view = <UILayoutContainerView; 0x109419100>; frame = (0, 0; 320, 568)>
 | <KJMasterViewController: 0x109410ec0; view = <UITableView; 0x10a82fc00>; frame = (0, 0; 320, 568)>

(lldb) pvc 0x109410ec0
 <KJMasterViewController: 0x109410ec0; view = <UITableView; 0x10a82fc00>; frame = (0, 0; 320, 568)>
 pviews
 <UIWindow: 0x109319480; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x109319d80>; layer = <UIWindowLayer: 0x109318560>>
 | <UILayoutContainerView: 0x109419100; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x10931bfa0>; layer = <CALayer: 0x109419320>>
 | | <UINavigationTransitionView: 0x10931b970; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x109259740>>
 | | | <UIViewControllerWrapperView: 0x109527b30; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x109530800>>
 | | | | <UITableView: 0x10a82fc00; frame = (0 0; 320 568); clipsToBounds = YES; opaque = NO; autoresize = W+H; gestureRecognizers = <NSArray: 0x109266c80>; layer = <CALayer: 0x1095310c0>; contentOffset: {0, -64}>
 〜略〜

pviews

viewの階層構造を表示してくれます。引数でviewを指定すると、指定したview配下の階層構造が表示されます。


(lldb) pviews
<UIWindow: 0x109319480; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x109319d80>; layer = <UIWindowLayer: 0x109318560>>
| <UILayoutContainerView: 0x109419100; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x10931bfa0>; layer = <CALayer: 0x109419320>>
|    | <UINavigationTransitionView: 0x10931b970; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x109259740>>
|    |    | <UIViewControllerWrapperView: 0x109527b30; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x109530800>>
|    |    |    | <UITableView: 0x10a82fc00; frame = (0 0; 320 568); clipsToBounds = YES; opaque = NO; autoresize = W+H; gestureRecognizers = <NSArray: 0x109266c80>; layer = <CALayer: 0x1095310c0>; contentOffset: {0, -64}>
〜略〜

pcells

tableViewdで表示されているセルの一覧を表示

presponder

指定したクラスのレスポンダーチェインを表示


(lldb) pcells
 <__NSArrayI 0x109422590>(
 <UITableViewCell: 0x10928e800; frame = (0 0; 320 44); text = '2014-04-27 14:13:37 +0000'; autoresize = W; layer = <CALayer: 0x10928b300>>,
 <UITableViewCell: 0x10933ada0; frame = (0 44; 320 44); text = '2014-04-27 14:13:35 +0000'; autoresize = W; layer = <CALayer: 0x10933b090>>,
 <UITableViewCell: 0x10932fd10; frame = (0 88; 320 44); text = '2014-04-27 14:12:38 +0000'; autoresize = W; layer = <CALayer: 0x109322c00>>,
 <UITableViewCell: 0x109273a10; frame = (0 132; 320 44); text = '2014-04-27 14:11:32 +0000'; autoresize = W; layer = <CALayer: 0x10925d540>>
 )

(lldb) presponder 0x10933ada0
 <UITableViewCell: 0x10933ada0; frame = (0 44; 320 44); text = '2014-04-27 14:13:35 +0000'; autoresize = W; layer = <CALayer: 0x10933b090>>
 | <UITableViewWrapperView: 0x10926ae50; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x10926b050>>
 | | <UITableView: 0x10a82fc00; frame = (0 0; 320 568); clipsToBounds = YES; opaque = NO; autoresize = W+H; gestureRecognizers = <NSArray: 0x109266c80>; layer = <CALayer: 0x1095310c0>; contentOffset: {0, -64}>
 | | | <KJMasterViewController: 0x109410ec0>
 | | | | <UIViewControllerWrapperView: 0x109527b30; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x109530800>>
 | | | | | <UINavigationTransitionView: 0x10931b970; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x109259740>>
 | | | | | | <UILayoutContainerView: 0x109419100; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x10931bfa0>; layer = <CALayer: 0x109419320>>
 | | | | | | | <UINavigationController: 0x109268650>
 | | | | | | | | <UIWindow: 0x109319480; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x109319d80>; layer = <UIWindowLayer: 0x109318560>>
 | | | | | | | | | <UIApplication: 0x109308360>
 | | | | | | | | | | <KJAppDelegate: 0x10921a1e0>


また、対象のviewをアプリ上でインタラクティブに特定するための以下のようなコマンドも用意されています。

taplog

実行すると、フォーカスがアプリ(シミュレータor実機)に戻り、画面をtapすると、tapしたviewの情報が表示される。

vs

インタラクティブに、viewの選択/サーチができる。実行すると、指定したViewが点灯表示され、以下のキー入力(+リターン)で、対象viewが遷移する。

(w) superviewへ移動
(s) 最初のsubviewへ移動
(a) 同階層の一つ前のviewへ移動
(d) 同階層の次のviewへ移動
(p) 階層を表示
(q) 終了

pclass

指定したクラスの継承を表示

 fv

viewクラス名の正規表現引数指定すると、該当するviewを表示。

 fvc

viewControllerクラス名の正規表現引数指定すると、該当するviewControllerを表示。
コレ以外にも、指定したViewをアプリ上でマーキングしたり、指定したViewをマーキングしたり非表示にしたり、osx上にイメージ出力したりと、色々と便利な機能が用意されているので、試してみてはいかがでしょうか。

@takayamaさんyidev運営ごくろうさまでした。

P.S.

qiitaに同じ内容投稿しました。
qiitaの方が書き心地がいいので、今度からは、この手のエントリはqiitaに書く事にします。

Xcodeのログを色付きで

XcodeColorsというXcodeのログ出力をカラーにできるPlug-inを導入してみました。
新規プロジェクト作成時のセットアップ手順が極力シンプルになるよう、CocoaLumberjackは使わずに、元々使っていたプリプロセッサマクロをいじって以下のことができるようにしました。

  • DEBUGビルドの場合にのみログ出力する。(RELEASEビルドではログ出力しない)
  • インスタンスのクラス名+メソッド名と、コード中の行番号を表示。
  • カラーパターンについては、標準パターンと、背景色付き(赤、緑、青の3パターン)を用意。

準備

1. XcodeColorsの導入

https://github.com/robbiehanson/XcodeColorsをRELEASEビルドしてインストール。(Runではなく、ArchiveするとRELEASEビルドしてくれます。)

 

2. 以下のコードを .pchにコピペ(新規プロジェクト作成時)

(スニペット登録しておくと便利)


#ifdef DEBUG
//#if (TARGET_IPHONE_SIMULATOR && DEBUG)
#define TRACE(fmt, ...) NSLog((@"\033[fg0,118,135;(%d)\033[fg50,80,200;%s \033[fg0,0,0;" fmt @"\033[;"),__LINE__,__PRETTY_FUNCTION__, ##__VA_ARGS__);
#define TRACE_R(fmt, ...) NSLog((@"\033[bg255,180,180;\033[fg0,118,135;(%d)\033[fg50,80,200;%s \033[fg0,0,0;" fmt @"\033[;"),__LINE__,__PRETTY_FUNCTION__, ##__VA_ARGS__);
#define TRACE_G(fmt, ...) NSLog((@"\033[bg180,255,180;\033[fg0,118,135;(%d)\033[fg50,80,200;%s \033[fg0,0,0;" fmt @"\033[;"),__LINE__,__PRETTY_FUNCTION__, ##__VA_ARGS__);
#define TRACE_B(fmt, ...) NSLog((@"\033[bg180,180,255;\033[fg0,118,135;(%d)\033[fg50,80,200;%s \033[fg0,0,0;" fmt @"\033[;"),__LINE__,__PRETTY_FUNCTION__, ##__VA_ARGS__);
#else
#define TRACE(...)
#define TRACE_R(...)
#define TRACE_G(...)
#define TRACE_B(...)
#endif

使い方

NSLogの代わりに、TRACEとします。


NSLog(@"hoge: %@",obj);
// ↓
TRACE(@"hoge: %@",obj);


背景色付きにしたい場合は、


TRACE_R(@"hoge: %@",obj); // 背景を赤く


です。

こんな感じになります。

UICollectionViewLayoutの実装方法

iOS6で追加されたUICollectionView、色々と夢が広がる感じです。

https://twitter.com/olebegemann/status/248380531690045440

(数ヶ月後には、UICollectionViewLayoutのサブクラスがGitHubにあふれているだろう!)

#まだほとんど無いようですが。。。

色々とカスタマイズしようと思うと、まだ情報が少なく、よくわからない部分が多いです。

DataSourceまわりの扱い(UITableViewでおなじみな感じ)、UICollectionViewFlowLayoutを使った基本的な使い方については、こちらが参考になります。
Natsu's Note
 [iOS6] Collection View 基本的な使い方
今回は、カスタムなレイアウトを実現するために、UICollectionViewLayout(UICollectionViewFlowLayoutではない)をサブクラス化して使う場合について、WWDC2012のサンプルコード(CircleLayout)をもとに解説してみます。

ただし、Appleが配布しているWWDC2012のサンプルコードは、iOS6ベータ時代のもののようで、そのままではまともに動きません。期待通りに動くように修正されたコードが以下にあります。)

元ネタは以下。

http://markpospesel.wordpress.com/2012/10/25/fixing-circlelayout/

コードはこちら。

https://github.com/mpospese/CircleLayout

 

Collection View で利用するクラス

サンプルコードを見るまえに、ざっとCollectionViewについて触れておきます。

Collection View を使う際の主なクラスは、
  • UICollectionView
  • UICollectionViewController
  • UICollectionViewCell
といったあたりですが、UITableViewを扱ったことがある方なら、名前から大体使い方はイメージできるでしょう。Collection View では、そのレイアウトの自由度を高めるために専用のクラスが用意されており、ViewControllerと合わせて使うこととなります。
  • UICollectionViewFlowLayout
  • UICollectionViewLayout
UICollectionViewFlowLayoutは、UICollectionViewLayoutのサブクラスであり、UICollectionViewLayoutは抽象クラスです。UICollectionViewFlowLayoutでは表現できないようはカスタムレイアウトを行ないたい場合は、UICollectionViewLayoutをサブクラス化してレイアウト制御を実装することとなります。このレイアウトクラスが、UITableViewとの最大の差異となっている箇所です。

Collection Viewは、割り当てたLayoutオブジェクトにレイアウト情報を問い合わせ、Layoutオブジェクトがレイアウト情報を必要に応じて返答することで、レイアウトが制御されます。

そのほかに、
  • UICollectionViewLayoutAttributes
  • UICollectionViewUpdateItem
あたりのクラスも押さえておく必要があるかと思います。UICollectionViewLayoutAttributesクラスは、Collection View上の各Cellのレイアウト情報(大きさ、座標、α値などの属性情報)をCellに紐付けて取り扱いやすいようにまとめたクラスです。
UICollectionViewUpdateItemについては、後ほど説明します。

 

UICollectionViewLayout のサブクラス化

サンプルコードを見る前の前準備としてもう一つ、UICollectionViewLayoutで使われるメソッド群について触れておきます。

UICollectionViewLayoutは抽象クラスのため、カスタムなレイアウトを実装する際には、このクラスをサブクラス化し、必要なメソッドを実装していくこととなります。

Appleのドキュメントは、オーバーライドすべき(should)メソッドとして以下が記載されています。

 

collectionViewContentSize

Collection View のコンテンツのサイズを返す。

 

layoutAttributesForElementsInRect:

Rectで指定されたエリアに存在するCell群のレイアウト情報(UICollectionViewLayoutAttributesクラスのオブジェクト)をNSArrayで返す。

 

layoutAttributesForItemAtIndexPath:

NSIndexPathで指定されたCellのレイアウト情報(UICollectionViewLayoutAttributesクラスのオブジェクト)を返す

 

layoutAttributesForSupplementaryViewOfKind:atIndexPath:

supplementary view(今回は説明省略)を利用する場合にそのレイアウト情報を生成する。

 

layoutAttributesForDecorationViewOfKind:atIndexPath:

decoration view(今回は説明省略)を利用する場合にそのレイアウト情報を生成する。

 

shouldInvalidateLayoutForBoundsChange:

Collection Viewのboundsが変更された際に、レイアウトし直すかをBOOLで返す。

 

上記がオーバーライドすべき(should)メソッドとして指定されていますが、上記以外にも、よく使いそうなメソッドとして
  • prepareLayout
  • prepareForCollectionViewUpdates:
  • finalizeCollectionViewUpdates
  • initialLayoutAttributesForAppearingItemAtIndexPath:
  • finalLayoutAttributesForDisappearingItemAtIndexPath:
  • invalidateLayout
といったものがあります。

 

レイアウト情報生成に呼び出されるメソッドの流れ

上記を踏まえ、サンプルコードのCircleLayoutプロジェクトを追いかけていきます。

CircleLayoutプロジェクトは、丸いCellを円状にレイアウトし、かつ、Cellをタップした際にはアニメーションしながらCellを削除、背景をタップした際にはアニメーションしながらCellを挿入するデモコードです。

サンプルコードでは、CircleLayout.mがUICollectionViewLayoutのサブクラス実装です。今回の説明はこの部分にフォーカスして説明していきます。

このコードを見ながら、先に紹介したLayoutクラスの各メソッドが、UICollectionViewがレイアウト情報を取得する際に、主要なメソッド群がどのように呼び出されるかの流れを、呼び出される順に見て行きます。

 

 

1. prepareLayout

レイアウトの計算に必要となるような、前処理を行う。

-(void)prepareLayout
{
    [super prepareLayout];

    CGSize size = self.collectionView.frame.size;
    _cellCount = [[self collectionView] numberOfItemsInSection:0];
    _center = CGPointMake(size.width / 2.0, size.height / 2.0);
    _radius = MIN(size.width, size.height) / 2.5;
}

ここで何をすべきというお約束は特にないので、実装するレイアウト計算に必要な前処理を行なう部分となります。

 

 

2. collectionViewContentSize

コンテンツサイズを返します。

-(CGSize)collectionViewContentSize
{
    return [self collectionView].frame.size;
}

まあ、そのままですね。

 

 

3. prepareForCollectionViewUpdates

これは、Cellの削除、追加、移動処理が行なわれた際にのみ呼ばれます。
削除、追加、移動処理のための前処理を定義します。

- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
    // Keep track of insert and delete index paths
    [super prepareForCollectionViewUpdates:updateItems];

    self.deleteIndexPaths = [NSMutableArray array];
    self.insertIndexPaths = [NSMutableArray array];

    for (UICollectionViewUpdateItem *update in updateItems)
    {
        if (update.updateAction == UICollectionUpdateActionDelete)
        {
            [self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
        }
        else if (update.updateAction == UICollectionUpdateActionInsert)
        {
            [self.insertIndexPaths addObject:update.indexPathAfterUpdate];
        }
    }
}

このメソッドでは、お約束の実装パターンが必要となります。

渡されるupdateItemsのArrayは、UICollectionViewUpdateItemオブジェクトからなるArrayで、UICollectionViewUpdateItemオブジェクトを利用し、削除、挿入、移動などの更新処理がなされたCellを特定することができます。
Cellの削除、挿入、移動のアニメーション処理のために、更新処理が行なわれた対象Cellの情報(indexPath)をここで保持しておく処理を行なっています。

また、

    [super prepareForCollectionViewUpdates:updateItems];

の行は、お約束として必ず入れておいてください。この行がないと、アニメーションされません。

 

 

4. layoutAttributesForElementsInRect + layoutAttributesForItemAtIndexPath

rectで指定された領域に存在するCell群のUICollectionViewLayoutAttributesオブジェクト(以降、attributesと略記)をArrayで返す実装をここでします。

-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray* attributes = [NSMutableArray array];
    for (NSInteger i=0 ; i < self.cellCount; i++) {
        NSIndexPath* indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
    }
    return attributes;
}

CircleLayoutプロジェクトでは、全てのCellが表示領域に存在しているため、このメソッドの中で、存在する全てのCell数分のattributesを生成しています。attributes生成そのものは、layoutAttributesForItemAtIndexPathメソッドで行なう構造となっています。

この構造だけ見ると、layoutAttributesForItemAtIndexPath メソッド必要なの?と思いますが、後に出る、initialLayoutAttributesForAppearingItemAtIndexPath,finalLayoutAttributesForDisappearingItemAtIndexPathなど、Cell単体でのattributes取得が必要な場面が出てくるので、お約束として実装しておきます。

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
{
    UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
    attributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE);
    attributes.center = CGPointMake(_center.x + _radius * cosf(2 * path.item * M_PI / _cellCount),
                                    _center.y + _radius * sinf(2 * path.item * M_PI / _cellCount));
    return attributes;
}

cellのindexPath情報に紐付けてUICollectionViewLayoutAttributesオブジェクトを生成し、Cellのサイズや位置を定義しています。

 

 

5. initialLayoutAttributesForAppearingItemAtIndexPath:

Cellを挿入した際に、挿入アニメーションを実現するために存在するメソッドです。
挿入初期状態のCellのattributesを返すようにします。
ここで生成したattributesの状態から、最終的な状態(挿入完了状態)のattributes (layoutAttributesForItemAtIndexPathで生成されるもの)へとアニメーションされることとなります。

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    // Must call super
    UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];

    if ([self.insertIndexPaths containsObject:itemIndexPath])
    {
        // only change attributes on inserted cells
        if (!attributes)
            attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];

        // Configure attributes ...
        attributes.alpha = 0.0;
        attributes.center = CGPointMake(_center.x, _center.y);
    }

    return attributes;
}

このメソッドがくせ者で、実装すべき内容を誤解しやすいのですが、、、、
このメソッドは、挿入されたCell分だけでなく、もともと存在するCellを含め表示する全てのCell毎に呼び出されます。(もともと9個のCellがある状態から1個のCellを追加した場合には、このメソッドは10回呼ばれます。)

ついつい、挿入対象のCellの初期状態のattributesのことだけ考えてしまいそうになりますが、元々存在しているCellの挿入処理前のattributesも、それぞれのCellに対して応答する必要があるわけです。

以下の行で、全てのCellに対し、挿入前状態のattributesを取得しています。

    UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];

さらに、

    if ([self.insertIndexPaths containsObject:itemIndexPath])

の部分で、指定されたindexPathが新たに挿入されたCellか、元々存在しているCellかを判定しています。
ここで判定に利用している、self.insertIndexPath は、3. prepareForCollectionViewUpdates で用意したものを利用しています。

 

 

6. finalLayoutAttributesForDisappearingItemAtIndexPath:

Cellを削除した際に、削除アニメーションを実現するために存在するメソッドです。
削除完了状態のCellのattributesを返すようにします。

- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    // So far, calling super hasn't been strictly necessary here, but leaving it in
    // for good measure
    UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];

    if ([self.deleteIndexPaths containsObject:itemIndexPath])
    {
        // only change attributes on deleted cells
       if (!attributes)
            attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];

        // Configure attributes ...
        attributes.alpha = 0.0;
        attributes.center = CGPointMake(_center.x, _center.y);
        attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);
    }

    return attributes;
}

実装の考え方は、5.の挿入と同様なので、説明は省略。

 

 

7. finalizeCollectionViewUpdates

一連のレイアウト情報生成が完了後、最後に呼ばれるメソッドです。

- (void)finalizeCollectionViewUpdates
{
    [super finalizeCollectionViewUpdates];
    // release the insert and delete index paths
    self.deleteIndexPaths = nil;
    self.insertIndexPaths = nil;
}

レイアウト情報生成に利用したデータをクリアしています。

長くなりましたが、以上です。

もし、記載内容に誤り等あれば、ご指摘頂けますと幸いです。

iOS6から導入されたUIActivityViewControllerでShareKitいらず

iOS6から使えるようになった、UIActivity/UIActivityViewControllerをご存知でしょうか。(WWDC2012の資料で見かけた記憶が無く、最近存在に気付きました。)
iOS6の標準Appで見かけるようになった以下のようなアイコンベースのアクションシート画面を表示して、イメージやテキストなどをShareすることができるviewControllerです。



Share先は、Facebook,Twitter,Weibo, Message, Mail, Print, Pasteboard, AssignToContact, SaveToCameraRollに標準で対応しています。

渡せるデータは、各サービスによって異なりますが、おおよそ、NSString, UIImage, NSURLあたり。渡すデータ種別に応じて、対象となるShare先が自動的に選択されて表示されます。(対象データがテキストの場合は、SaveToCameraRollは表示されないなど。)

使い方は超簡単。

- (IBAction)shareButtonPressed:(id)sender {

 // 1. shareする素材を用意して、NSArrayにする。(複数種類のデータを一度に渡せる)
 NSString *shareText = @"hogehoge";
 NSURL *shareURL = [NSURL URLWithString:@"http://www.apple.com"];
 NSArray *activityItems = @[self.imageView.image, shareText, shareURL];

 // 2. Controller生成
 UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
 // 第一引数にShareするデータをNSArrayで
 // 第二引数には、標準以外のサービスを利用したい場合に指定。
 // (Share対象サービスを自作できるようで、自作したサービスを利用する場合に指定。標準サービスのみ利用の場合は、nilでOK)

 // 3. shareボタンを表示したくないサービスを NSArrayで用意して、指定
 NSArray *excludeActivities = @[UIActivityTypePostToWeibo];
 activityController.excludedActivityTypes = excludeActivities;

 // 4. activity処理完了時の動作
 void (^completionHandler)(NSString *activityType, BOOL completed) = ^(NSString *activityType, BOOL completed){
 NSLog(@"completetion handler");
 // ただし、キャンセルした場合は、completedがNOでかえってくる。(Activity処理そのものの成否は解らない?
 };
 activityController.completionHandler = completionHandler;

 // modalで表示
 [self presentViewController:activityController animated:YES completion:^{
  NSLog(@"Activity complete!!");
 }];
}
デモコードを以下におきました。
https://github.com/kenhama/UIActivityDemo

従来であれば、ShareKitを使うところですが、アプリのサポート対象がiOS6以上でよいならばこちらが簡単でよさげです。

追記:WWDC2012のPDFあらためてチェクしたら、ちゃんと紹介されてたorz
追記2: completion handlerを追記

Expedited App Review Request してみた話

Expedited App Review Request ってご存知でしょうか?

 

昨日リリースされた.Schedのver.3.20で,iOS6対応にBUGがあり予定が全く参照できないという致命的状況となってしまいましたが、本日、修正版のver.3.21がリリースされました。(利用者の方、大変ご迷惑おかけしました。m(_ _)m )

 

通常、iOSアプリのリリースには、Appleの審査に約1週間程待たされるのですが、この「Expedited App Review Request」が通ると優先的にレビューしてもらうことが出来ます。

 

やり方は、

1. iTunes Connectを開く

2. Contact us を選択

3. “App Review” -> “Request Expedited Review” を選択

4. contact us のリンクをクリックすると、申請フォームが現れるので入力 (緊急対応が必要な理由を英作文)



 

 

・フォームに入力〜送信後、1時間もせずにメール連絡(定型フォーマット自動返信っぽい)

超訳

「超忙しいんで、対応可能かどうかの返事は、営業日1日か2日待って」

 

・15時間後にメール連絡(定型フォーマットっぽい)

超訳

「緊急状況は解ったんで1回だけ例外でexpdited reviewするよ。」

「expedited reviewは保障するものでないし、限定的なものなんで、次は対応できるかわからんよ。」

・5時間後にInReview

・1時間後にProcessing for App Store

 

ということで、iTunes ConnectにアプリをSubmitしてからリリースまで通常1週間はかかるところを、24H以内でリリースできました。

 

やっちゃった時のための、緊急修正手段として覚えておいて損はないかと思います。

 

追記@9/22 13:00

上記を書いてる間に、ver.3.21でiOS5だと起動できないという、さらに致命的なBUGを埋め込んでいたことが判明(汗)。

利用者の方(iOS5の環境でver.3.21を導入された方)へ、お詫び申し上げます。修正版がリリースされるまで今しばらくお待ち願います。

現状は、一旦AppStoreから削除した上で、修正版のver.3.22をsubmit、再度Expedited review requestしています。(多用はダメと言われてるそばから、再度expedited review依頼にチャレンジ中)

 

追記@9/25 AM

二回目のExpedited review requestが無事受け入れられ、先ほど、Rady fo saleになりました。二回目のExpedited review requestを行った日が土曜日であったため、土日明けで即座に対応してくれた感じです。(ダメもとの2連続Expedited review requestでしたが、通りました。ちなみに、メール内容は1回目と完全に同一内容でした。)

 

 

PureData書籍紹介: “Making Musical Apps”と”Pd Recipe Book ―Pure Dataではじめるサウンドプログラミング”

以前に作りかけとなっていた音楽系アプリの開発を再開しようと、いろいろと調べていたところ、PureData(pd)がiOSで動くようになってるではないですか。

しかも、つい最近、書籍も出てるじゃないですか!

ということで、入手した書籍2つご紹介。

 

Making Musical Apps

O’REILLY本です。英語版のみ。



 

目次
Chapter 1 Introduction
- A Great Investment
- Resources
- Prerequisites
Chapter 2 Making Noise with Pure Data
- Installing Pure Data
- A First Patch
- Adding Audio Input
- Sample Rates and Audio Channels
- Control Rate Objects and Messages
- Sending and Receiving Messages
- More About Messages
- Using MIDI with Pd
- Further Topics
Chapter 3 When Not to Make Musical Apps
- Creating RjDj Scenes
- Anatomy of an RjDj Scene
- Patching for RjDj
- Deploying an RjDj Scene
- Receiving Sensor Input
- Controlling Graphics and Text
- Using rjlib
Chapter 4 Embedding Pure Data with libpd
- Introducing libpd
- API Overview
- Opening Patches
- Finding Resources
- Sending Messages to Pd
- Receiving Messages from Pd
- Reading and Writing Arrays in Pd
- MIDI Support in libpd
- Odds and Ends
- Externals in libpd
- Audio Glue
- Launch Sequence
Chapter 5 Pd for Android
- Setting Up the Development Environment
- Creating a Musical App: Part I
- Creating a Musical App: Part II
- Improving the User Interface
- Building Externals for Android
Chapter 6 Pd for iOS
- Setting Up the Development Environment
- Creating a Musical App: Part I
- Creating a Musical App: Part II
- Improving the User Interface
Chapter 7 Outlook

PureDataの簡単な説明から始まり、RjDjのパッチの作成方法、iOSでPureDataを動かすためのlibpdの導入方法から、実際の使い方の簡単な説明まで記載されています。(Android向けの解説もあるが読んでない)

PureDataそのものの説明は、簡単なものなので、PureDataも初めてという人向けではないですが、iOSへの導入は、記載の通りに勧めて行けば実際に動かすことができる内容となっています。

 

PureDataそのものをマスターするには、こちらがオススメ。

“Pd Recipe Book ―Pure Dataではじめるサウンドプログラミング”

著者:松村 誠一郎(www.low-tech-ism.com



 

目次:
CHAPTER 1 Introduction
INTERVIEW Frank Barknecht
CHAPTER 2 Basic
2.1 Pdのパッチを作る
2.2 Putメニュー/ヘルプファイル
2.3 オブジェクトとヘルプファイル
2.4 各種メニューの詳細
INTERVIEW Gunter Geiger
CHAPTER 3 Rhythm Machine
3.1 リズムマシンの概要
3.2 step 1 音ファイルを読み込む
3.3 step 2 Arrayに読み込んだ音データを再生する
3.4 step 3 シーケンスを記録する
3.5 step 4 シーケンスを再生する
3.6 step 5 音の再生パートとシーケンスのループ再生パートを合体する
3.7 step 6 見やすいようにサブパッチ化する
3.8 step 7 音の状態を見るオシロスコープをつける
3.9 step 8 パッチを開いた時に動作する
3.10 step 9 トラック数を増やす
INTERVIEW Chun Lee
CHAPTER 4 Synthesizer
4.1 シンセサイザーの概要
4.2 step 1 シンプルなサイン波シンセサイザーを作る
4.3 step 2 音の立ち上がりと消える部分を作る
4.4 step 3 音の立ち上がりと消える時間を自由に設定する
4.5 step 4 音の立ち上がりと消える間の音量の変化
4.6 step 5 エンベロープジェネレータ(EG)の改良
4.7 step 6 スライダーを配置する
4.8 step 7 エンベロープジェネレータ(EG)パートをAbstractionにする
4.9 step 8 音のON/OFF、Pitch、Velocityのシーケンサーを作る
4.10 step 9 サイン波シンセサイザーとシーケンサーを合体する
4.11 step 10 音量にモジュレーションをかける
4.12 step 11 エフェクトをかける
4.13 step 12 オシレータの波形を変更する
4.14 step 13 いろいろなシンセサイザー音源を作る
4.15 step 14 フィルタをかける
INTERVIEW Aymeric Mansoux
CHAPTER 5 Interactive System
5.1 フリールーパー(Free Looper)
5.2 時間経過とランダムな音
5.3 声で楽器音をコントロール
5.4 声の高さで映像をコントロール
5.5 Webカメラでエアドラミング
5.6 音の高さと長さを自由自在にチェンジして再生
5.7 フレーズスライサー(Phrase Slicer)
REPORT Pd-conレポート
APPENDIX:Pdオブジェクトリファレンス

まだ最後までいっていないですが、かなり丁寧というか、音楽系のプログラミングが初めての人もスコープにした記載となっています。

パッチの構成方法を一つづつ丁寧に解説してくれており、順にすすめると最終的には、目次の通り、リズムマシンやシンセサイザー、シーケンサが完成するという構成。

 

という事で、PureData + iOSで夢が広がるかも!?

 

.Sched 3 で予定をタップした時の背景をぼかすアニメーションの実装方法

.Sched 3 で予定をタップした時に、背景をぼかすアニメーションを実装してるのですが、今回はこの実装方法について紹介します。

紹介ビデオの 0:12,0:35,0:45あたりのやつです。


.Sched 2 の時も、メニュー表示時にこのアニメーションを利用してました。

 

どうやっているかというと、CALayerのCABasicAnimationで、rasterizationScaleの値を変化させています。

ボカシたいタイミング、ボカシをもどしたいタイミングで以下を実行します。

ボカシ処理開始時。

layer.shouldRasterize = YES;
CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"rasterizationScale"];
myAnimation.fromValue = [NSNumber numberWithFloat:1.0];
myAnimation.toValue = [NSNumber numberWithFloat:0.07];
myAnimation.duration = 0.3;
//アニメーション終了時にエフェクトが元に戻らないようにしておく
myAnimation.removedOnCompletion = NO;
myAnimation.fillMode = kCAFillModeForwards;
[layer addAnimation:myAnimation forKey:@"myAnimation"];

 

ボカシを終了して戻す時

CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"rasterizationScale"];
myAnimation.fromValue = [NSNumber numberWithFloat:0.07];
myAnimation.toValue = [NSNumber numberWithFloat:1.0];
myAnimation.duration = 0.3;
myAnimation.removedOnCompletion = NO;
myAnimation.fillMode = kCAFillModeForwards;
[layer addAnimation:myAnimation forKey:@"myAnimation"];

これだけです。

アニメーション適用対象レイヤーのレイヤーツリー配下の全てレイヤーがアニメーション対象となるため、フォーカスしたい部分は、指定レイヤーのレイヤーツリー配下にないようにしておく必要があります。

当初、予定詳細表示時のアニメーションとして、画面がパカーンと割れてスライドするアニメーション(iPhoneのフォルダを開いた時のアニメ)を実装したんですが、上下に割れる形にすると、パフォーマンス的にいまいちだし、思ったほどマッチしなかったので、今回のアニメーションに変更しました。

 

.Sched3の紹介動画作成 Reflection +QuickTime +QuartzComposer +iMovie +GarageBand

3/18 に.Sched3をiTunes ConnectにSubmitしました。ずぅっと”Waiting for Review”状態ですが、一週間たったので、そろそろIn Reviewになっていいころでしょうか。。。

とりあえず、.Sched3の紹介動画を作成しましたのでアップ。



せっかくですので、この動画の作成方法について簡単に紹介します。

はっきり言ってこの様な作業はまったくの素人ですので、完全に試行錯誤しながらの作業です。

初めは、実際にカメラで撮影しようかと思ったのですが、思い描く絵にまったたくならず、早々にあきらめました。iPhoneの操作画面撮りは、ライティングが難しいです。

で、どうしたかというと、CGで頑張ってみることにしました。3DCGって、どうやって作るの?という調査から開始。ソフトの値段もピンキリのようで、いきなり高価なものに手を出すわけにもいかず、極力コストをかけずにやるという方針に決定。最終的には以下のような形となりました。

  1. ReflectionというアプリでiPhoneの画面をMacで表示
  2. Reflectionで表示された画面をQuick Time X でキャプチャ
  3. Quartz Composer に、iPhone4の3Dモデルを読み込ませ、画面部分にキャプチャした動画をはりつけ
  4. Quartz Composerでカメラアングルやライティングをコントロールして動画書き出し。
  5. 最後に、iMovieで体裁整え。音楽がGarageBandで。

ReflectionでiPhoneの画面をMacで表示

無料で10分試すことができます。Macでこのソフトを起動し、iPhone側でAirPlayの設定をするだけ。非常に簡単です。画質はさほどよくないですが、反応は思ったより悪くないです。ちょっと試している間に10分の時間切れとなり、有償ライセンス購入しました。14.99USD(Paypal払い)です。

 

Reflectionで表示された画面をQuick Time X でキャプチャ

OSX付属のQuick Time X で画面の動画キャプチャができることを皆さんご存知でしょうか?

メニューの「ファイル」>「新規画面収録」で、画面の動画がキャプチャできます。全画面or任意の領域指定が可能です。

iPhone4Sでアプリを起動操作する様をReflectionでMacに表示し、それをQuickTimeXで撮影。この撮影した動画をQuartz Composer上のiPhone4の3Dモデルの画面部分に張りつけて。。。。。。。。という感じでまるまる1日使い、14.99USDの出費で何とかひとまず完成させました。

カメラアングルとか、コマ割りとか、もうちょっと色々と凝りたいところですが、やり出すと永遠に終わらない予感がしたので、ひとまず決着(完了)させました。

(動画作成後半戦のQuartz Composer以降内容については、また、後日記載するかも)

CALayerの描画チューニングとshadow表示

.Sched 3 の日表示モードでは、予定をドラッグ移動できるようになっています。

予定のドラッグ操作時にタッチポジションに合わせ、予定単体のUIViewもしくはCALayerを移動させるのであれば、どうってことはないのですが、既存の予定と重なったときに、ぐにゃぐにゃとアニメーションさせたかったので、逐次描画計算してアニメーションするようにしています。

最初に実装した時点では、パフォーマンスが全く出ずカクカクなアニメーションで実用に耐えない状況でしたが、チューニングしてなんとか使えるレベルのパフォーマンスを出せるようになりました。

 

まずは、iOSシミュレータでデバッグ

iOSシミュレータのデバッグメニューで、
  • ブレンドレイヤー
  • コピーイメージ
  • 不揃いのイメージ
  • オフスクリーンレンダリング
をチェックすると、シミュレータ上の画面が色付きで表示されます。


上のイメージのように、ブレンドレイヤーをチェックすると、シミュレータ上で、アルファチャネルがある箇所が赤く表示されます。

この赤い部分を極力減らしていくのが、チューニングステップ1です。だたし、これだけでは、体感レベルでの効果は得られませんでした。(これは、もちろんアプリの処理内容によるかと思います。)

このあたりは、フェンリルさんのブログエントリiPhone / iPad アプリのアニメーションをなめらかにするためのポイント3つを参考にさせて頂きました。

 

オフラインレンダリングを減らす(shadow描画)

上記のフェンリルさんのブログエントリにもありますが、iOSの自動オフラインレンダリングが発生するとFPSが低下するとのことで、特にCALayerのshadowプロパティを使用していると、オフラインレンダリングが発生し、激しくFPSが低下することがわかりました。

CALayerのshadowプロパティは、

layer.shadowRadius = 3;
layer.shadowColor = [UIColor blackColor].CGColor;
layer.shadowOpacity = 0.5;
layer.shadowOffset = CGSizeMake(1, 3);

とすると、CALayerに影をつけてくれる便利機能なので、できればそのまま使いたいと思い、いろいろと調べていたところ、以下のBlogのエントリにたどり着きました。

iPad drop shadow performance

shadow描画のパフォーマンスを、いろいろな描画方式で比較されてます。CALayerのshadowを利用していても、shadowPathを使うとよいとのことなので、

layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;

を追加してみたところ、劇的に早くなりました。
ということで、CALayerのshadowを使う際には、shadowPath指定をお忘れなく!
 

iPhoneのカレンダー同期その3(ExchangeとCalDAV比較)

iPhoneのカレンダー同期その1(アラート同期)iPhoneのカレンダー同期その2(繰り返し予定)につづき、iPhoneのカレンダー同期ネタ3回目、これで最後です。

前回はこちら

iPhoneのカレンダー同期その1(アラート同期)

iPhoneのカレンダー同期その2(繰り返し予定)

 

Google Calendarとの同期方式はExchange(ActiveSync)とGMail(CalDAV)の2種がありますが、この2種の同期方式の比較です。

 

Exchange(ActiveSync)

  • プッシュ同期可能(Google Calendar側の更新が即時にiPhoneに反映されます。)
  • カレンダーの色は同期されない。(iOSがかってに色を割り当てる)
  • アラームは1件しか同期されない。(しかも、iOS側で修正を行うと、Google Calendar側のアラーム情報が消える可能性あり。詳細は「iPhoneのカレンダー同期その1(アラート同期)」のエントリ参照ください。)
 

GMail(CalDAV)
  • プッシュ同期不可(フェッチ同期となります。iOS本体の設定で指定したフェッチ間隔でのポーリング同期となります。(標準カレンダーAppを起動すると同期されます。)。ポーリング処理のため、場合によってはバッテリーの消費に影響があるかも。)また、iOS->Google Calendar方向の同期もあまり早くないようです。
  • カレンダー色同期可能。
  • アラームは複数同期可能(ただし、SMSは同期されない。E-Mailは通常の「通知」としてiOSに反映されます。詳細は「iPhoneのカレンダー同期その1(アラート同期)」のエントリ参照ください。)
 

一長一短ですので、お好みでどちらか選択するしかないですね。

それぞれの設定手順はこちら を参照ください。