Wie liste ich alle Unteransichten in einem uiviewcontroller in iOS auf?

86

Ich möchte alle Unteransichten in a auflisten UIViewController. Ich habe es versucht self.view.subviews, aber nicht alle Unteransichten werden aufgelistet, zum Beispiel werden die Unteransichten in der UITableViewCellnicht gefunden. Irgendeine Idee?

user403015
quelle
Sie können alle Unteransichten durch rekursive Suche herausfinden. dh überprüfen Unteransicht hat Unteransichten ..
Mahesh

Antworten:

171

Sie müssen die Unteransichten rekursiv iterieren.

- (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 LINE oben überspringen.

EmptyStack
quelle
20
Technisch gesehen müssen Sie nicht überprüfen, ob die Anzahl der Unteransichten 0 beträgt.
Greg Maletic
5
NSLog (@ "\ n% @", [(id) self.view performSelector: @selector (recursiveDescription)]); Diese Zeile druckt das gleiche Ergebnis wie Ihre!
Hemang
Wenn Sie hier sind, überprüfen recursiveDescriptionSie bitte
Felipe Sabino
Hier ist meine Verbesserung der @ EmptyStack-Lösung. Es zeigt den Klassennamen und die Rahmenkoordinaten rekursiv eingerückt
Pierre de LESPINAY
35

Die in xcode / gdb integrierte Methode zum Speichern der Ansichtshierarchie ist nützlich - recursiveDescription ( 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>>
Natbro
quelle
Wo soll ich den Po [_myToolbar recursiveDescription]in meinen Code oder anderswo setzen?
19.
1
@ WildPointer Er spricht über die Konsole. Sie benötigen irgendwo einen Haltepunkt, um dies zu tun. po =
Druckobjekt
1
+1 Apple empfiehlt diesen Ansatz (gemäß der Sitzung Hidden Gems in Cocoa und Cocoa Touch von WWDC 2013, Tipp 5).
Slipp D. Thompson
@ पवन siehe meine Antwort hier . Ich habe diese Antwort umgeschrieben.
Honig
16

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()
Alex
quelle
13

Sie müssen rekursiv drucken, diese Methode auch Registerkarten basierend auf 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];
    }

}
James Webster
quelle
12

Hier ist die schnelle 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)
    }
}
Arkader
quelle
7

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
Gordon Dove
quelle
6

Wenn Sie nur ein Array von UIViews wünschen, ist dies eine Einzeiler-Lösung ( Swift 4+ ):

extension UIView {
  var allSubviews: [UIView] {
    return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
  }
}
Federico Zanetello
quelle
Danke für diese Lösung! Ich habe Stunden damit verbracht, es herauszufinden, aber dann hat mich dein Beitrag gerettet!
user2525211
5

Ich benutze diesen Weg:

NSLog(@"%@", [self.view subviews]);

im UIViewController.

Graphit
quelle
4

Einzelheiten

  • Xcode 9.0.1, Swift 4
  • Xcode 10.2 (10E125), Swift 5

Lösung

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) }
}

Verwendung

 view.printSubviews()
 print("\(view.allSubviews.count)")

Ergebnis

Geben Sie hier die Bildbeschreibung ein

Wassili Bodnarchuk
quelle
3

In meiner Hinsicht ist die Kategorie oder Erweiterung von UIView viel besser als andere und rekursiv ist der entscheidende Punkt, um alle Unteransichten zu erhalten

Mehr erfahren:

https://github.com/ZhipingYang/XYDebugView

Geben Sie hier die Bildbeschreibung ein

Ziel c

@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];

Swift 3.1

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 }
Zhiping Yang
quelle
2

Der Grund, warum die Unteransichten in einer UITableViewCell nicht gedruckt werden, liegt 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 abzurufen, müssen Sie ermitteln, welche Unteransichten zu einer UITableViewCell gehören (mithilfe von isKindOfClass: ) in Ihrer Druckschleife gehören, und dann die Unteransichten durchlaufen

Bearbeiten: Dieser Blog-Beitrag zu Easy UIView Debugging kann möglicherweise hilfreich sein

Suhail Patel
quelle
2

Ich habe eine Kategorie geschrieben, um alle Ansichten eines View Controllers aufzulisten, die von den zuvor veröffentlichten Antworten inspiriert wurden.

@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 durch einen View - Controller gehalten, gerade NSLog("%@",[self listOfSubviews]), was selfbedeutet , die View - Controller selbst. Obwohl es nicht sehr effizient ist.

Außerdem können Sie NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]);das Gleiche tun, und ich denke, es ist effizienter als meine Implementierung.

WeZZard
quelle
2

Einfaches Swift-Beispiel:

 var arrOfSub = self.view.subviews
 print("Number of Subviews: \(arrOfSub.count)")
 for item in arrOfSub {
    print(item)
 }
unbesiegbar
quelle
1

Sie könnten einen ausgefallenen Array-Trick ausprobieren, wie:

[self.view.subviews makeObjectsPerformSelector: @selector(printAllChildrenOfView)];

Nur eine Codezeile. Natürlich müssen Sie möglicherweise Ihre Methode anpassen printAllChildrenOfView, um keine Parameter zu übernehmen oder eine neue Methode zu erstellen.

Johnbakers
quelle
1

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 anrufen:

let view = FooController.view
let subviews = view.subviewsList()
Luca Davanzo
quelle
1

Geben Sie hier die Bildbeschreibung ein

Kürzeste Lösung

for subview in self.view.subviews {
    print(subview.dynamicType)
}

Ergebnis

UIView
UIView
UISlider
UISwitch
UITextField
_UILayoutGuide
_UILayoutGuide

Anmerkungen

  • Wie Sie sehen können, listet diese Methode die Unteransichten nicht rekursiv auf. Sehen Sie einige der anderen Antworten dafür.
Suragch
quelle
1

Dies ist eine Umschreibung dieser Antwort:

Sie müssen zuerst den Zeiger / Verweis auf das Objekt erhalten, mit dem Sie alle Unteransichten drucken möchten. Manchmal fällt es Ihnen leichter, dieses Objekt zu finden, indem Sie über seine Unteransicht darauf zugreifen. Wie po someSubview.superview. Dies gibt Ihnen so etwas wie:

Optional<UIView>
   some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
  • FaceBookApp ist Ihr App-Name
  • WhatsNewView ist der Typ Ihressuperview
  • 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 Haltepunkte erforderlich

Geben Sie hier die Bildbeschreibung ein

Dann könnten Sie leicht tun:

po [0x7f91747c71f0 recursiveDescription]

was für mich so etwas wie 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 erraten haben müssen, hat meine Übersicht 4 Unteransichten:

  • eine stackView (die stackView selbst hat ein Bild und eine andere stackView (diese stackView hat 2 benutzerdefinierte Beschriftungen))
  • eine Textansicht
  • eine Sicht
  • ein Knopf

Dies ist für mich ziemlich neu, 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, daher wurde recursiveDescription verwendet, und mir wurde klar, dass die Breite meiner Unteransichten 0... war. Daher habe ich die Einschränkungen korrigiert und die Unteransicht wurde angezeigt.

Honig
quelle
0

Alternativ, wenn Sie ein Array aller Unteransichten (und verschachtelten 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
}
Adam Johns
quelle
0

AC # 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 finden möchten, den ich verwende:

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;

}
Craig Champion
quelle
0

Ich habe es in einer Kategorie gemacht, in der ich UIVieweinfach die Funktion aufrufe, die den Index übergibt, um sie mit einem schönen 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 :)

valbu17
quelle
0
- (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;
}
sarra srairi
quelle
-1

self.view.subviews behalten die Hierarchie der Ansichten bei. Um Unteransichten von uitableviewcell zu erhalten, müssen Sie wie folgt vorgehen.

 for (UIView *subView in self.view.subviews) {
      if ([subView isKindOfClass:[UITableView class]]) {
            for (UIView *tableSubview in subView.subviews) {
                .......
            }
      }
 }
Hitesh
quelle