Comment lister toutes les sous-vues dans un uiviewcontroller sous iOS?

86

Je veux lister toutes les sous-vues dans un fichier UIViewController. J'ai essayé self.view.subviews, mais toutes les sous-vues ne sont pas répertoriées, par exemple, les sous-vues de UITableViewCellne sont pas trouvées. Une idée?

utilisateur403015
la source
Vous pouvez trouver toutes les sous-vues par recherche récursive. ie vérifier que la sous-vue a des sous-vues ..
Mahesh

Réponses:

171

Vous devez itérer récursivement les sous-vues.

- (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];
    }
}

Comme l'a commenté @Greg Meletic, vous pouvez ignorer la COUNT CHECK LINE ci-dessus.

EmptyStack
la source
20
Techniquement, vous n'avez pas besoin de vérifier si le nombre de sous-vues est égal à 0.
Greg Maletic
5
NSLog (@ "\ n% @", [(id) self.view performSelector: @selector (recursiveDescription)]); Cette ligne imprime le même résultat que le vôtre!
Hemang
Si vous êtes ici, veuillez vérifier la recursiveDescriptionréponse beaucoup plus simple de @natbro - stackoverflow.com/a/8962824/429521
Felipe Sabino
Voici mon amélioration de la solution @EmptyStack. Il montre le nom de la classe et les coordonnées du cadre récursivement en retrait
Pierre de LESPINAY
35

La méthode intégrée xcode / gdb pour vider la hiérarchie des vues est utile - recursiveDescription, par http://developer.apple.com/library/ios/#technotes/tn2239/_index.html

Il génère une hiérarchie de vues plus complète que vous pourriez trouver utile:

> 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
la source
Où mettre le po [_myToolbar recursiveDescription]dans mon code ou ailleurs.
पवन
1
@WildPointer Il parle de la console. Vous avez besoin d'un point d'arrêt quelque part pour ce faire. po = objet d'impression
smileBot
1
+1 Apple recommande cette approche (conformément à la session Hidden Gems in Cocoa and Cocoa Touch de la WWDC 2013, conseil n ° 5).
Slipp D.Thompson
@ पवन voir ma réponse ici . J'ai fait une réécriture de cette réponse.
Honey
16

Solution récursive élégante dans Swift:

extension UIView {

    func subviewsRecursive() -> [UIView] {
        return subviews + subviews.flatMap { $0.subviewsRecursive() }
    }

}

Vous pouvez appeler subviewsRecursive () sur n'importe quel UIView:

let allSubviews = self.view.subviewsRecursive()
Alex
la source
13

Vous devez imprimer de manière récursive, cette méthode utilise également des onglets en fonction de la profondeur de la vue

-(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
la source
13

Voici la version rapide

 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
la source
7

Je suis un peu en retard à la fête, mais solution un peu plus générale:

@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
la source
6

Si tout ce que vous voulez est un tableau de UIViews, c'est une solution à une seule ligne ( Swift 4+ ):

extension UIView {
  var allSubviews: [UIView] {
    return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
  }
}
Federico Zanetello
la source
merci pour cette solution! J'ai passé des heures à comprendre, mais votre message m'a sauvé!
user2525211
5

J'utilise de cette façon:

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

dans le UIViewController.

Graphite
la source
4

Détails

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

Solution

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

Usage

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

Résultat

entrez la description de l'image ici

Vasily Bodnarchuk
la source
3

À ma manière, la catégorie ou l'extension d'UIView est bien meilleure que les autres et récursive est le point clé pour obtenir toutes les sous-vues

apprendre encore plus:

https://github.com/ZhipingYang/XYDebugView

entrez la description de l'image ici

Objectif 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

exemple

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

exemple

let views = viewController.view.recurrenceAllSubviews()

directement, utilisez la fonction de séquence pour obtenir toutes les sous-vues

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
la source
2

La raison pour laquelle les sous-vues dans un UITableViewCell ne sont pas imprimées est que vous devez générer toutes les sous-vues du niveau supérieur. Les sous-vues de la cellule ne sont pas les sous-vues directes de votre vue.

Afin d'obtenir les sous-vues de UITableViewCell, vous devez déterminer les sous-vues appartenant à un UITableViewCell (en utilisant isKindOfClass: ) dans votre boucle d'impression, puis parcourez ses sous-vues.

Edit: Ce billet de blog sur le débogage Easy UIView peut potentiellement aider

Suhail Patel
la source
2

J'ai écrit une catégorie pour répertorier toutes les vues détenues par un contrôleur de vue inspiré des réponses publiées auparavant.

@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

Pour lister toutes les vues détenues par un contrôleur de vue, juste NSLog("%@",[self listOfSubviews]), ce qui selfsignifie le contrôleur de vue lui-même. Bien que ce ne soit pas efficace.

De plus, vous pouvez utiliser NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]);pour faire la même chose, et je pense que c'est plus efficace que ma mise en œuvre.

WeZZard
la source
2

Exemple simple de Swift:

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

Vous pouvez essayer une astuce de tableau sophistiquée, comme:

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

Juste une ligne de code. Bien sûr, vous devrez peut-être ajuster votre méthode printAllChildrenOfViewpour ne prendre aucun paramètre ou créer une nouvelle méthode.

johnbakers
la source
1

Compatible Swift 2.0

Voici une méthode récursive pour obtenir toutes les sous-vues d'une vue générique:

extension UIView {
  func subviewsList() -> [UIView] {
      var subviews = self.subviews
      if subviews.count == 0 {
          return subviews + []
      }
      for v in subviews {
         subviews += v.listSubviewsOfView()
      }
      return subviews
  }
}

Ainsi, vous pouvez appeler partout de cette manière:

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

entrez la description de l'image ici

Solution la plus courte

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

Résultat

UIView
UIView
UISlider
UISwitch
UITextField
_UILayoutGuide
_UILayoutGuide

Remarques

  • Comme vous pouvez le voir, cette méthode ne répertorie pas les sous-vues de manière récursive. Voir quelques-unes des autres réponses à cela.
Suragch
la source
1

C'est une réécriture de ceci réponse:

Vous devez d'abord obtenir le pointeur / la référence de l'objet que vous souhaitez imprimer toutes ses sous-vues. Parfois, vous pouvez trouver plus facile de trouver cet objet en y accédant via sa sous-vue. Comme po someSubview.superview. Cela vous donnera quelque chose comme:

Optional<UIView>
   some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
  • FaceBookApp est le nom de votre application
  • WhatsNewView est le type de votresuperview
  • 0x7f91747c71f0 est le pointeur vers le superview.

Pour imprimer le superView, vous devez utiliser des points d'arrêt.


Maintenant, pour faire cette étape, vous pouvez simplement cliquer sur «Afficher la hiérarchie de débogage». Pas besoin de points d'arrêt

entrez la description de l'image ici

Ensuite, vous pouvez facilement faire:

po [0x7f91747c71f0 recursiveDescription]

qui pour moi a renvoyé quelque chose comme:

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

comme vous devez l'avoir deviné, ma supervision a 4 sous-vues:

  • un stackView (le stackView lui-même a une image et un autre stackView (ce stackView a 2 custom étiquettes ))
  • un textView
  • une vue
  • un bouton

C'est assez nouveau pour moi, mais cela m'a aidé à déboguer les cadres de mes vues (ainsi que le texte et le type). Une de mes sous-vues n'apparaissait pas à l'écran, alors j'ai utilisé recursiveDescription et j'ai réalisé que la largeur de l'une de mes sous-vues était 0... alors j'ai corrigé ses contraintes et la sous-vue apparaissait.

Mon chéri
la source
0

Sinon, si vous souhaitez renvoyer un tableau de toutes les sous-vues (et sous-vues imbriquées) à partir d'une extension UIView:

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
la source
0

Version AC # Xamarin:

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

Alternativement, si vous souhaitez trouver toutes les sous-vues d'un type spécifique que j'utilise:

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
la source
0

Je l'ai fait dans une catégorie d' UIViewappeler simplement la fonction en passant l'index pour les imprimer avec un joli format d'arbre. Ceci est juste une autre option de la réponse publiée par 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++];
        }
    }
}

J'espère que cela aide :)

valbu17
la source
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
la source
-1

self.view.subviews maintient la hiérarchie des vues. Pour obtenir des sous-vues de uitableviewcell, vous devez faire quelque chose comme ci-dessous.

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