デバッガでの解析が面倒くさい局面もあるので、自分用に書き残しておきます。
Swiftの勉強用に、非再帰版も作ってみました。
この記事は Swift4.2時点の情報に基づきます。
再帰版
普通の実装。
とにかく全てのUIViewを把握したかったので、UIApplicationから取得できる全てのWindow配下のViewを出力しています。
import Foundation
import UIKit
func dumpApplicationViewHierarchy() {
UIApplication.shared.windows.forEach { window in
print("\(window)")
window.subviews.forEach({ rootView in
dumpViewHierarchy(from: rootView, depth: 1)
})
}
}
func dumpViewHierarchy(from view: UIView, depth: Int = 0) {
let indent = String(repeating: " | ", count: depth)
print("\(indent)\(view)")
view.subviews.forEach { childView in
dumpViewHierarchy(from: childView, depth: depth + 1)
}
}
非再帰版
再帰関数のスタックオーバーフローが恐ろしい人向けの非再帰版。
func dumpApplicationViewHierarchy2() {
UIApplication.shared.windows.forEach { window in
print("\(window)")
window.subviews.forEach({ rootView in
var viewStack = [(view: rootView, depth: 1)]
while let lastNode = viewStack.popLast() {
let indent = String(repeating: " | ", count: lastNode.depth)
print("\(indent)\(lastNode.view)")
viewStack.append(contentsOf: lastNode.view.subviews.map {
(view: $0, depth: lastNode.depth + 1)
}.reversed())
}
})
}
}
自前関数の出力例
再帰版 及び 非再帰版 の出力例は、以下な感じ。subviews
の順序どおりに出力されています。
<UIWindow: 0x7fd43e424370; frame = (0 0; 375 812); gestureRecognizers = <NSArray: 0x60000117c2d0>; layer = <UIWindowLayer: 0x600001f6ca00>>
| <UILayoutContainerView: 0x7fd43e408190; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x60000117d320>; layer = <CALayer: 0x600001f6dd20>>
| | <UINavigationTransitionView: 0x7fd43e423c00; frame = (0 0; 375 812); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x600001f6e760>>
| | | <UIViewControllerWrapperView: 0x7fd43e6178f0; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x600001f60800>>
| | | | <UIView: 0x7fd43e4091a0; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x600001f64380>>
| | | | | <UIStackView: 0x7fd43e409380; frame = (0 88; 375 690); opaque = NO; autoresize = RM+BM; layer = <CATransformLayer: 0x600001f643a0>>
| | | | | | <UILabel: 0x7fd43e42b490; frame = (0 0; 375 172.667); text = 'Label Item 1'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c045f0>>
| | | | | | <UILabel: 0x7fd43e42bea0; frame = (0 172.667; 375 172.333); text = 'Label Item 2'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c040f0>>
| | | | | | <UILabel: 0x7fd43e42c190; frame = (0 345; 375 172.667); text = 'Label Item 3'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c04820>>
| | | | | | <UIButton: 0x7fd43e40c990; frame = (0 517.667; 375 172.333); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x600001f64280>>
| | | | | | | <UIButtonLabel: 0x7fd43e42c480; frame = (167 77.3333; 41 18); text = 'Dump'; alpha = 0.2; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c04b90>>
| | <UINavigationBar: 0x7fd43e41b3d0; frame = (0 44; 375 44); opaque = NO; autoresize = W; layer = <CALayer: 0x600001f6c720>>
| | | <_UIBarBackground: 0x7fd43e41bee0; frame = (0 -44; 375 88); userInteractionEnabled = NO; layer = <CALayer: 0x600001f6c780>>
| | | | <UIImageView: 0x7fd43e41cbd0; frame = (0 88; 375 0.333333); userInteractionEnabled = NO; layer = <CALayer: 0x600001f6c7c0>>
| | | | <UIVisualEffectView: 0x7fd43e41ce00; frame = (0 0; 375 88); layer = <CALayer: 0x600001f6c7e0>>
| | | | | <_UIVisualEffectBackdropView: 0x7fd43e426220; frame = (0 0; 375 88); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x600001f6e4e0>>
| | | | | <_UIVisualEffectSubview: 0x7fd43e4237e0; frame = (0 0; 375 88); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x600001f6e780>>
| | | <_UINavigationBarLargeTitleView: 0x7fd43e41deb0; frame = (0 0; 0 50); clipsToBounds = YES; hidden = YES; layer = <CALayer: 0x600001f6c9a0>>
| | | | <UILabel: 0x7fd43e41b8a0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c006e0>>
| | | <_UINavigationBarContentView: 0x7fd43e41d660; frame = (0 0; 375 44); layer = <CALayer: 0x600001f6c820>>
| | | | <UILabel: 0x7fd43e605510; frame = (164 11.6667; 47 20.3333); text = 'Demo'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c09130>>
| | | <_UINavigationBarModernPromptView: 0x7fd43e4235e0; frame = (0 0; 0 50); alpha = 0; hidden = YES; layer = <CALayer: 0x600001f6dc20>>
プライベートAPI版
この手の話題で良く紹介されるプライベートAPIを使った方法。
製品版ビルドでは、このコードを含めてはいけません。
func dumpApplicationViewHierarchy3() {
UIApplication.shared.windows.forEach { window in
print("\(window)")
window.subviews.forEach({ rootView in
let description = rootView.perform("recursiveDescription")
print("\(String(describing: description))")
})
}
}
プライベートAPI版の出力例
perform
の結果は、長い文字列として得られる。これをNSLog()
で出力すると途中で打ち切られるので注意が必要。
<UIWindow: 0x7fd43e424370; frame = (0 0; 375 812); gestureRecognizers = <NSArray: 0x60000117c2d0>; layer = <UIWindowLayer: 0x600001f6ca00>>
Optional(Swift.Unmanaged<Swift.AnyObject>(_value: <UILayoutContainerView: 0x7fd43e408190; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x60000117d320>; layer = <CALayer: 0x600001f6dd20>>
| <UINavigationTransitionView: 0x7fd43e423c00; frame = (0 0; 375 812); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x600001f6e760>>
| | <UIViewControllerWrapperView: 0x7fd43e6178f0; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x600001f60800>>
| | | <UIView: 0x7fd43e4091a0; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x600001f64380>>
| | | | <UIStackView: 0x7fd43e409380; frame = (0 88; 375 690); opaque = NO; autoresize = RM+BM; layer = <CATransformLayer: 0x600001f643a0>>
| | | | | <UILabel: 0x7fd43e42b490; frame = (0 0; 375 172.667); text = 'Label Item 1'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c045f0>>
| | | | | <UILabel: 0x7fd43e42bea0; frame = (0 172.667; 375 172.333); text = 'Label Item 2'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c040f0>>
| | | | | <UILabel: 0x7fd43e42c190; frame = (0 345; 375 172.667); text = 'Label Item 3'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c04820>>
| | | | | <UIButton: 0x7fd43e40c990; frame = (0 517.667; 375 172.333); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x600001f64280>>
| | | | | | <UIButtonLabel: 0x7fd43e42c480; frame = (167 77.3333; 41 18); text = 'Dump'; alpha = 0.2; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c04b90>>
| <UINavigationBar: 0x7fd43e41b3d0; frame = (0 44; 375 44); opaque = NO; autoresize = W; layer = <CALayer: 0x600001f6c720>>
| | <_UIBarBackground: 0x7fd43e41bee0; frame = (0 -44; 375 88); userInteractionEnabled = NO; layer = <CALayer: 0x600001f6c780>>
| | | <UIImageView: 0x7fd43e41cbd0; frame = (0 88; 375 0.333333); userInteractionEnabled = NO; layer = <CALayer: 0x600001f6c7c0>>
| | | <UIVisualEffectView: 0x7fd43e41ce00; frame = (0 0; 375 88); layer = <CALayer: 0x600001f6c7e0>>
| | | | <_UIVisualEffectBackdropView: 0x7fd43e426220; frame = (0 0; 375 88); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x600001f6e4e0>>
| | | | <_UIVisualEffectSubview: 0x7fd43e4237e0; frame = (0 0; 375 88); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x600001f6e780>>
| | <_UINavigationBarLargeTitleView: 0x7fd43e41deb0; frame = (0 0; 0 50); clipsToBounds = YES; hidden = YES; layer = <CALayer: 0x600001f6c9a0>>
| | | <UILabel: 0x7fd43e41b8a0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c006e0>>
| | <_UINavigationBarContentView: 0x7fd43e41d660; frame = (0 0; 375 44); layer = <CALayer: 0x600001f6c820>>
| | | <UILabel: 0x7fd43e605510; frame = (164 11.6667; 47 20.3333); text = 'Demo'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003c09130>>
| | <_UINavigationBarModernPromptView: 0x7fd43e4235e0; frame = (0 0; 0 50); alpha = 0; hidden = YES; layer = <CALayer: 0x600001f6dc20>>))
参考リンク
ビューの階層構造をログ出力する
https://qiita.com/calmscape/items/35aa39cb4101bec53e94
デバッグのためにView階層を把握する
https://qiita.com/akatsuki174/items/45d4bd7cb150defbf116
iOS UIViewの階層構造をprintする extension by Swift 3.0
https://qiita.com/gi0_0iv/items/159fcdb19074ee66ca38
How to debug your view hierarchy using recursiveDescription
https://www.hackingwithswift.com/articles/101/how-to-debug-your-view-hierarchy-using-recursivedescription
Technical Note TN2239 iOS Debugging Magic
https://developer.apple.com/library/archive/technotes/tn2239/_index.html