Ich möchte alle Unteransichten in einem UIViewController
auflisten. Ich habe es versucht self.view.subviews
, aber nicht alle Unteransichten werden aufgelistet, z. B. werden die Unteransichten in UITableViewCell
nicht gefunden. Irgendeine Idee?
Sie müssen die Unteransichten rekursiv durchlaufen.
- (void)listSubviewsOfView:(UIView *)view {
// Get the subviews of the view
NSArray *subviews = [view subviews];
// Return if there are no subviews
if ([subviews count] == 0) return; // COUNT CHECK LINE
for (UIView *subview in subviews) {
// Do what you want to do with the subview
NSLog(@"%@", subview);
// List the subviews of subview
[self listSubviewsOfView:subview];
}
}
Wie von @Greg Meletic kommentiert, können Sie die COUNT CHECK-Zeile oben überspringen.
Die in xcode/gdb integrierte Methode zum Sichern der Ansichtshierarchie ist nützlich - recursiveDescription, per http://developer.Apple.com/library/ios/#technotes/tn2239/_index.html
Es gibt eine vollständigere Ansichtshierarchie aus, die Sie möglicherweise nützlich finden:
> po [_myToolbar recursiveDescription]
<UIToolbarButton: 0xd866040; frame = (152 0; 15 44); opaque = NO; layer = <CALayer: 0xd864230>>
| <UISwappableImageView: 0xd8660f0; frame = (0 0; 0 0); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xd86a160>>
Sie müssen rekursiv drucken. Diese Methode richtet sich auch nach der Tiefe der Ansicht
-(void) printAllChildrenOfView:(UIView*) node depth:(int) d
{
//Tabs are just for formatting
NSString *tabs = @"";
for (int i = 0; i < d; i++)
{
tabs = [tabs stringByAppendingFormat:@"\t"];
}
NSLog(@"%@%@", tabs, node);
d++; //Increment the depth
for (UIView *child in node.subviews)
{
[self printAllChildrenOfView:child depth:d];
}
}
Elegante rekursive Lösung in Swift:
extension UIView {
func subviewsRecursive() -> [UIView] {
return subviews + subviews.flatMap { $0.subviewsRecursive() }
}
}
Sie können subviewsRecursive () in jedem UIView aufrufen:
let allSubviews = self.view.subviewsRecursive()
Hier ist die Swift Version
func listSubviewsOfView(view:UIView){
// Get the subviews of the view
var subviews = view.subviews
// Return if there are no subviews
if subviews.count == 0 {
return
}
for subview : AnyObject in subviews{
// Do what you want to do with the subview
println(subview)
// List the subviews of subview
listSubviewsOfView(subview as UIView)
}
}
Ich bin ein bisschen zu spät zur Party, aber eine etwas allgemeinere Lösung:
@implementation UIView (childViews)
- (NSArray*) allSubviews {
__block NSArray* allSubviews = [NSArray arrayWithObject:self];
[self.subviews enumerateObjectsUsingBlock:^( UIView* view, NSUInteger idx, BOOL*stop) {
allSubviews = [allSubviews arrayByAddingObjectsFromArray:[view allSubviews]];
}];
return allSubviews;
}
@end
Wenn alles, was Sie wollen, ein Array von UIView
s ist, ist dies eine einzeilige Lösung (Swift 4 +):
extension UIView {
var allSubviews: [UIView] {
return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
}
}
Ich benutze diesen Weg:
NSLog(@"%@", [self.view subviews]);
im UIViewController.
extension UIView {
private func subviews(parentView: UIView, level: Int = 0, printSubviews: Bool = false) -> [UIView] {
var result = [UIView]()
if level == 0 && printSubviews {
result.append(parentView)
print("\(parentView.viewInfo)")
}
for subview in parentView.subviews {
if printSubviews { print("\(String(repeating: "-", count: level))\(subview.viewInfo)") }
result.append(subview)
if subview.subviews.isEmpty { continue }
result += subviews(parentView: subview, level: level+1, printSubviews: printSubviews)
}
return result
}
private var viewInfo: String { return "\(classForCoder), frame: \(frame))" }
var allSubviews: [UIView] { return subviews(parentView: self) }
func printSubviews() { _ = subviews(parentView: self, printSubviews: true) }
}
view.printSubviews()
print("\(view.allSubviews.count)")
In meinen Augen ist die Kategorie oder Erweiterung von UIView viel besser als andere und rekursiv ist der entscheidende Punkt, um alle Unteransichten zu erhalten
erfahren Sie mehr:
https://github.com/ZhipingYang/XYDebugView
@implementation UIView (Recurrence)
- (NSArray<UIView *> *)recurrenceAllSubviews
{
NSMutableArray <UIView *> *all = @[].mutableCopy;
void (^getSubViewsBlock)(UIView *current) = ^(UIView *current){
[all addObject:current];
for (UIView *sub in current.subviews) {
[all addObjectsFromArray:[sub recurrenceAllSubviews]];
}
};
getSubViewsBlock(self);
return [NSArray arrayWithArray:all];
}
@end
Beispiel
NSArray *views = [viewController.view recurrenceAllSubviews];
extension UIView {
func recurrenceAllSubviews() -> [UIView] {
var all = [UIView]()
func getSubview(view: UIView) {
all.append(view)
guard view.subviews.count>0 else { return }
view.subviews.forEach{ getSubview(view: $0) }
}
getSubview(view: self)
return all
}
}
Beispiel
let views = viewController.view.recurrenceAllSubviews()
verwenden Sie direkt die Sequenzfunktion , um alle Unteransichten abzurufen
let viewSequence = sequence(state: [viewController.view]) { (state: inout [UIView] ) -> [UIView]? in
guard state.count > 0 else { return nil }
defer {
state = state.map{ $0.subviews }.flatMap{ $0 }
}
return state
}
let views = viewSequence.flatMap{ $0 }
Ich habe eine Kategorie geschrieben, um alle Ansichten aufzulisten, die von einem Ansichtscontroller gehalten werden, der von den zuvor veröffentlichten Antworten inspiriert wurde.
@interface UIView (ListSubviewHierarchy)
- (NSString *)listOfSubviews;
@end
@implementation UIView (ListSubviewHierarchy)
- (NSInteger)depth
{
NSInteger depth = 0;
if ([self superview]) {
deepth = [[self superview] depth] + 1;
}
return depth;
}
- (NSString *)listOfSubviews
{
NSString * indent = @"";
NSInteger depth = [self depth];
for (int counter = 0; counter < depth; counter ++) {
indent = [indent stringByAppendingString:@" "];
}
__block NSString * listOfSubviews = [NSString stringWithFormat:@"\n%@%@", indent, [self description];
if ([self.subviews count] > 0) {
[self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UIView * subview = obj;
listOfSubviews = [listOfSubviews stringByAppendingFormat:@"%@", [subview listOfSubviews]];
}];
}
return listOfSubviews;
}
@end
Um alle Ansichten aufzulisten, die von einem Ansichtscontroller gehalten werden, geben Sie einfach NSLog("%@",[self listOfSubviews])
ein, wobei self
den Ansichtscontroller selbst bedeutet. Obwohl es nicht effizient zu beenden ist.
Außerdem können Sie NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]);
verwenden, um dasselbe zu tun, und ich denke, es ist effizienter als meine Implementierung.
Einfaches Swift Beispiel:
var arrOfSub = self.view.subviews
print("Number of Subviews: \(arrOfSub.count)")
for item in arrOfSub {
print(item)
}
Der Grund, warum die Unteransichten in einer UITableViewCell nicht gedruckt werden, besteht darin, dass Sie alle Unteransichten in der obersten Ebene ausgeben müssen. Die Unteransichten der Zelle sind nicht die direkten Unteransichten Ihrer Ansicht.
Um die Unteransichten der UITableViewCell zu erhalten, müssen Sie in Ihrer Druckschleife ermitteln, welche Unteransichten zu einer UITableViewCell gehören (mit isKindOfClass:
), Und dann die Unteransichten durchlaufen
Bearbeiten: Dieser Blog-Beitrag auf Easy UIView Debugging kann möglicherweise helfen
Swift 2.0 kompatibel
Hier eine rekursive Methode, um alle Unteransichten einer generischen Ansicht zu erhalten:
extension UIView {
func subviewsList() -> [UIView] {
var subviews = self.subviews
if subviews.count == 0 {
return subviews + []
}
for v in subviews {
subviews += v.listSubviewsOfView()
}
return subviews
}
}
So können Sie überall auf diese Weise anrufen:
let view = FooController.view
let subviews = view.subviewsList()
Sie könnten einen ausgefallenen Array-Trick ausprobieren, wie zum Beispiel:
[self.view.subviews makeObjectsPerformSelector: @selector(printAllChildrenOfView)];
Nur eine Codezeile. Natürlich müssen Sie möglicherweise Ihre Methode printAllChildrenOfView
anpassen, um keine Parameter zu übernehmen oder eine neue Methode zu erstellen.
Dies ist eine Umschreibung von this answer:
Sie müssen zuerst den Zeiger/Verweis auf das Objekt erhalten, das Sie zum Drucken aller seiner Unteransichten verwenden möchten. Manchmal fällt es Ihnen möglicherweise leichter, dieses Objekt zu finden, indem Sie über seine Unteransicht darauf zugreifen. Wie po someSubview.superview
. Dies gibt Ihnen etwas wie:
Optional<UIView>
▿ some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
superview
0x7f91747c71f0
Ist der Zeiger auf die Übersicht.Um die SuperView zu drucken, müssen Sie Haltepunkte verwenden.
Um diesen Schritt auszuführen, klicken Sie einfach auf "Debug-Hierarchie anzeigen". Keine Notwendigkeit für Haltepunkte
Dann könnten Sie leicht tun:
po [0x7f91747c71f0 recursiveDescription]
was für mich so etwas zurückgegeben hat:
<FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
| <UIStackView: 0x7f91747c75f0; frame = (45 60; 264 93); layer = <CATransformLayer: 0x610000230ec0>>
| | <UIImageView: 0x7f916ef38c30; frame = (10.6667 0; 243 58); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x61000003b840>>
| | <UIStackView: 0x7f91747c8230; frame = (44.6667 58; 174.667 35); layer = <CATransformLayer: 0x6100006278c0>>
| | | <FacebookApp.CopyableUILabel: 0x7f91747a80b0; baseClass = UILabel; frame = (44 0; 86.6667 16); text = 'What's New'; gestureRecognizers = <NSArray: 0x610000c4a770>; layer = <_UILabelLayer: 0x610000085550>>
| | | <FacebookApp.CopyableUILabel: 0x7f916ef396a0; baseClass = UILabel; frame = (0 21; 174.667 14); text = 'Version 14.0.5c Oct 05, 2...'; gestureRecognizers = <NSArray: 0x610000c498a0>; layer = <_UILabelLayer: 0x610000087300>>
| <UITextView: 0x7f917015ce00; frame = (45 183; 264 403); text = ' • new Adding new feature...'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x6100000538f0>; layer = <CALayer: 0x61000042f000>; contentOffset: {0, 0}; contentSize: {264, 890}>
| | <<_UITextContainerView: 0x7f9170a13350; frame = (0 0; 264 890); layer = <_UITextTiledLayer: 0x6080002c0930>> minSize = {0, 0}, maxSize = {1.7976931348623157e+308, 1.7976931348623157e+308}, textContainer = <NSTextContainer: 0x610000117b20 size = (264.000000,340282346638528859811704183484516925440.000000); widthTracksTextView = YES; heightTracksTextView = NO>; exclusionPaths = 0x61000001bc30; lineBreakMode = 0>
| | | <_UITileLayer: 0x60800023f8a0> (layer)
| | | <_UITileLayer: 0x60800023f3c0> (layer)
| | | <_UITileLayer: 0x60800023f360> (layer)
| | | <_UITileLayer: 0x60800023eca0> (layer)
| | <UIImageView: 0x7f9170a7d370; frame = (-39 397.667; 36 2.33333); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x60800023f4c0>>
| | <UIImageView: 0x7f9170a7d560; frame = (258.667 -39; 2.33333 36); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x60800023f5e0>>
| <UIView: 0x7f916ef149c0; frame = (0 587; 354 0); layer = <CALayer: 0x6100006392a0>>
| <UIButton: 0x7f91747a8730; frame = (0 0; 0 0); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x610000639320>>
| | <UIButtonLabel: 0x7f916ef00a80; frame = (0 -5.66667; 0 16); text = 'See More Details'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x610000084d80>>
wie Sie sich sicher gedacht haben, hat mein Überblick 4 Unteransichten:
Dies ist ziemlich neu für mich, hat mir jedoch geholfen, die Frames (und den Text und den Typ) meiner Ansichten zu debuggen. Eine meiner Unteransichten wurde nicht auf dem Bildschirm angezeigt, also wurde recursiveDescription verwendet und mir wurde klar, dass die Breite meiner Unteransichten 0
War. Also korrigierte ich die Einschränkungen und die Unteransicht wurde angezeigt.
- (NSString *)recusiveDescription:(UIView *)view
{
NSString *s = @"";
NSArray *subviews = [view subviews];
if ([subviews count] == 0) return @"no subviews";
for (UIView *subView in subviews) {
s = [NSString stringWithFormat:@"<%@; frame = (%f %f : %f %f) \n ",NSStringFromClass([subView class]), subView.frame.Origin.x, subView.frame.Origin.y ,subView.frame.size.width, subView.frame.size.height];
[self recusiveDescription:subView];
}
return s;
}
Alternativ, wenn Sie ein Array aller Unteransichten (und verschachtelter Unteransichten) von einer UIView-Erweiterung zurückgeben möchten:
func getAllSubviewsRecursively() -> [AnyObject] {
var allSubviews: [AnyObject] = []
for subview in self.subviews {
if let subview = subview as? UIView {
allSubviews.append(subview)
allSubviews = allSubviews + subview.getAllSubviewsRecursively()
}
}
return allSubviews
}
Eine C # Xamarin-Version:
void ListSubviewsOfView(UIView view)
{
var subviews = view.Subviews;
if (subviews.Length == 0) return;
foreach (var subView in subviews)
{
Console.WriteLine("Subview of type {0}", subView.GetType());
ListSubviewsOfView(subView);
}
}
Wenn Sie alternativ alle Unteransichten eines bestimmten Typs suchen möchten, verwende ich:
List<T> FindViews<T>(UIView view)
{
List<T> allSubviews = new List<T>();
var subviews = view.Subviews.Where(x => x.GetType() == typeof(T)).ToList();
if (subviews.Count == 0) return allSubviews;
foreach (var subView in subviews)
{
allSubviews.AddRange(FindViews<T>(subView));
}
return allSubviews;
}
Ich habe es in einer Kategorie von UIView
gemacht, rufe einfach die Funktion auf, die den Index übergibt, um sie mit einem Nice-Baumformat zu drucken. Dies ist nur eine weitere Option der Antwort von James Webster .
#pragma mark - Views Tree
- (void)printSubviewsTreeWithIndex:(NSInteger)index
{
if (!self)
{
return;
}
NSString *tabSpace = @"";
@autoreleasepool
{
for (NSInteger x = 0; x < index; x++)
{
tabSpace = [tabSpace stringByAppendingString:@"\t"];
}
}
NSLog(@"%@%@", tabSpace, self);
if (!self.subviews)
{
return;
}
@autoreleasepool
{
for (UIView *subView in self.subviews)
{
[subView printViewsTreeWithIndex:index++];
}
}
}
Ich hoffe, es hilft :)