24/7 twenty-four seven

iOS/OS X application programing topics.

ビュー (UIView) の階層構造をダンプする非公開の便利メソッド

標準 SDK で提供されているクラスがどういう構造になってるか参考にしたいとか、ちょっとしたカスタマイズをしたいとか、そういうときにビュー構造をダンプしたりすることはよくあると思います。

下記のようなメソッドを書いてもいいのですが、実は UIView には便利なメソッドが提供されています。

- (void)explode:(id)aView level:(int)level {
    doLog(level, @"%@", [[aView class] description]);
    doLog(level, @"%@", NSStringFromCGRect([aView frame]));
    for (UIView *subview in [aView subviews]) {
        [self explode:subview level:(level + 1)];
    }
}


それが次の2つです。recursiveDescription メソッドが使用できるのは iPhone OS 3.0 以降です。

- (NSString *)recursiveDescription;
- (NSDictionary *)scriptingInfoWithChildren;


試しに UIWebView について出力してみました。

<UIWebView: 0x4411240; frame = (0 0; 320 480); layer = <CALayer: 0x441f300>>
    <UIScroller: 0x442ac60; frame = (0 0; 320 480); clipsToBounds = YES; autoresize = H; layer = <CALayer: 0x442b000>>
        <UIImageView: 0x442c0c0; frame = (0 0; 54 54); transform = [-1, 0, -0, -1, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x442c0f0>>
        <UIImageView: 0x442c060; frame = (0 0; 54 54); transform = [0, 1, -1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x442c090>>
        <UIImageView: 0x442bb30; frame = (0 0; 54 54); transform = [0, -1, 1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x442c030>>
        <UIImageView: 0x442b090; frame = (0 0; 54 54); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x442b0c0>>
        <UIImageView: 0x442b030; frame = (-14.5 14.5; 30 1); transform = [0, 1, -1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x442b060>>
        <UIImageView: 0x4429850; frame = (-14.5 14.5; 30 1); transform = [0, -1, 1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x4423c80>>
        <UIImageView: 0x4429540; frame = (0 0; 1 30); transform = [-1, 0, -0, -1, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x4428800>>
        <UIImageView: 0x4427c10; frame = (0 0; 1 30); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x4426e50>>
        <UIImageView: 0x4426d80; frame = (0 450; 320 30); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x441e530>>
        <UIImageView: 0x442b190; frame = (0 0; 320 30); transform = [-1, 0, -0, -1, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x442ba70>>
        <UIWebDocumentView: 0x4823600; frame = (0 0; 320 480); layer = <UIWebLayer: 0x4420ce0>>

UIWebView は謎が多いクラスですが、こうしてみると UIWebView は単なるコンテナで、その下の UIScroller がスクロール機構を提供していて、実際の HTML やリッチテキスト、PDF の表示は UIWebDocumentView か担っているという構造はよくわかりますね。


iPhone OS 3.2 以降では内部構造に少し変更が入っています。
下記は OS 3.2 上で実行した場合の出力です(出力形式も若干変更が入ってますね)。

<UIWebView: 0x551a6a0; frame = (0 0; 320 480); layer = <CALayer: 0x5539ee0>>
   | <UIScrollView: 0x55416b0; frame = (0 0; 320 480); clipsToBounds = YES; autoresize = H; layer = <CALayer: 0x5541a20>>
   |    | <UIImageView: 0x5542770; frame = (0 0; 54 54); transform = [-1, 0, -0, -1, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x55427a0>>
   |    | <UIImageView: 0x5542710; frame = (0 0; 54 54); transform = [0, 1, -1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x5542740>>
   |    | <UIImageView: 0x55426b0; frame = (0 0; 54 54); transform = [0, -1, 1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x55426e0>>
   |    | <UIImageView: 0x5542550; frame = (0 0; 54 54); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x5542580>>
   |    | <UIImageView: 0x55424f0; frame = (-14.5 14.5; 30 1); transform = [0, 1, -1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x5542520>>
   |    | <UIImageView: 0x5542490; frame = (-14.5 14.5; 30 1); transform = [0, -1, 1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x55424c0>>
   |    | <UIImageView: 0x5542430; frame = (0 0; 1 30); transform = [-1, 0, -0, -1, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x5542460>>
   |    | <UIImageView: 0x55423d0; frame = (0 0; 1 30); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x5542400>>
   |    | <UIImageView: 0x5542370; frame = (0 450; 320 30); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x55423a0>>
   |    | <UIImageView: 0x5542210; frame = (0 0; 320 30); transform = [-1, 0, -0, -1, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x5542240>>
   |    | <UIWebBrowserView: 0x6035000; frame = (0 0; 320 480); layer = <UIWebLayer: 0x5539ac0>>

UIScroller が UIScrollView に、UIWebDocumentView が UIWebBrowserView に変わっています。
このように UIWebView は内部構造がころころ変わるので、普通に使うのが一番なのですが、どうしても見た目や振る舞いを変更したいこともあると思いますので、たまに見ておくといいですね。


ちなみに、UIWebView は 3.2 から UIScrollViewDelegate プロトコルに準拠するようになったのですが、UIScrollViewDelegate 関連のコールバックは呼ばれませんでした。
単に UIWebView が UIScrollView を使用するようになったので、UIWebView クラスにその宣言が増えただけのようです。
スクロールのタイミングとか簡単に取れるかと期待したのですが、残念です。


下記は同じく謎の多い MKMapView についての出力です。
上が 3.1.3、下が 3.2 での出力です。こちらも若干ですが内部構造が変化しています。

<MKMapView: 0x4613ad0; frame = (0 0; 320 480); clipsToBounds = YES; layer = <CALayer: 0x4613d00>>
    <UIView: 0x4617220; frame = (0 0; 320 480); autoresizesSubviews = NO; layer = <CALayer: 0x4617250>>
        <MKScrollView: 0x461a8c0; baseClass = UIScrollView; frame = (0 0; 320 480); clipsToBounds = YES; autoresizesSubviews = NO; layer = <CALayer: 0x461ae80>>
            <MKMapLevelView: 0x461bdd0; baseClass = UITiledView; frame = (0 0; 512 512); layer = <CALayer: 0x46151d0>> mapType:0 offset:(0.000000,0.000000) zoomlevel:2
            <MKOverlayView: 0x46172a0; frame = (0 0; 512 512); autoresizesSubviews = NO; layer = <CALayer: 0x46172f0>>
    <UIImageView: 0x461c2a0; frame = (9 448; 69 23); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x461c440>>
<MKMapView: 0x5349ef0; frame = (0 0; 320 480); clipsToBounds = YES; layer = <CALayer: 0x534a1b0>>
   | <UIView: 0x534ed10; frame = (0 0; 320 480); autoresizesSubviews = NO; layer = <CALayer: 0x534ed40>>
   |    | <MKScrollView: 0x5352310; baseClass = UIScrollView; frame = (0 0; 320 480); clipsToBounds = YES; autoresizesSubviews = NO; layer = <CALayer: 0x53525f0>>
   |    |    | <MKMapTileView: 0x534de30; frame = (0 0; 512 512); transform = [1.90735e-06, 0, 0, 1.90735e-06, 0, 0]; layer = <MKTiledLayer: 0x534df80>>
   |    |    | <MKAnnotationContainerView: 0x534ed90; frame = (0 0; 512 512); autoresizesSubviews = NO; layer = <CALayer: 0x534ede0>>
   | <UIImageView: 0x534c060; frame = (9 448; 69 23); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x5352940>>