web-dev-qa-db-fra.com

Comment lister toutes les sous-vues dans un contrôleur uiview sous iOS?

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

83
user403015

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 ligne COUNT CHECK LINE ci-dessus.

171
EmptyStack

La méthode intégrée de 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 qui pourrait vous être 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>>
35
natbro

Vous devez imprimer de manière récursive, cette méthode comporte également des onglets basés sur 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];
    }

}
13
James Webster

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()
12
Alex

Voici la version Swift

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

Je suis un peu en retard à la fête, mais une 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
7
Gordon Dove

Si tout ce que vous voulez, c'est un tableau de UIViews, c'est une solution complète (Swift 4 +):

extension UIView {
  var allSubviews: [UIView] {
    return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
  }
}
6

J'utilise cette façon:

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

dans le UIViewController.

5
Graphite

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

enter image description here

4
Vasily Bodnarchuk

À mon sens, la catégorie ou l'extension d'UIView est bien meilleure que les autres et le point clé pour obtenir toutes les vues secondaires est récursif.

apprendre encore plus:

https://github.com/ZhipingYang/XYDebugView

enter image description here

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 sequence 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 }
3
Zhiping Yang

J'ai écrit une catégorie pour répertorier toutes les vues détenues par un contrôleur de vue inspiré par les réponses postées précédemment.

@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 répertorier toutes les vues détenues par un contrôleur de vue, il suffit de NSLog("%@",[self listOfSubviews]), qui self désigne 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 mon implémentation.

2
WeZZard

Simple Swift exemple:

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

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

Pour obtenir les sous-vues de UITableViewCell, vous devez déterminer quelles sous-vues appartiennent à un UITableViewCell (à l'aide de isKindOfClass:) dans votre boucle d'impression, puis passez en revue ses sous-vues

Edit: Cet article de blog sur Easy UIView Debugging peut potentiellement aider

2
Suhail Patel

enter image description here

La solution la plus courte

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

Résultat

UIView
UIView
UISlider
UISwitch
UITextField
_UILayoutGuide
_UILayoutGuide

Notes

  • Comme vous pouvez le constater, cette méthode ne répertorie pas les sous-vues de manière récursive. Voir certaines des autres réponses pour cela.
1
Suragch

compatible Swift 2.

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

Vous pouvez donc appeler partout de cette façon:

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

Vous pouvez essayer une astuce de tableau de fantaisie, comme:

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

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

1
johnbakers

C'est une réécriture de this answer:

Vous devez d’abord obtenir le pointeur/la référence à l’objet pour lequel vous souhaitez imprimer toutes ses sous-vues. Parfois, vous trouverez 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 votre nom de l'application
  • WhatsNewView est le type de votre superview
  • 0x7f91747c71f0 est le pointeur sur la vue d'ensemble.

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

enter image description here

Ensuite, vous pouvez facilement faire:

po [0x7f91747c71f0 recursiveDescription]

qui pour moi est retourné 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 l'aurez deviné, mon superview a 4 sous-vues:

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

Ceci est assez nouveau pour moi, mais m'a aidé à déboguer les cadres (le texte et le type) de mes vues. Une de mes sous-vues n'apparaissait pas à l'écran, alors description récursive a été utilisée et j'ai réalisé que la largeur de l'une de mes sous-vues était 0... alors je suis allé corriger ses contraintes et la sous-vue apparaissait.

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

Sinon, si vous souhaitez renvoyer un tableau de toutes les sous-vues (et les sous-vues imbriquées) 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
}
0
Adam Johns

Une version C # 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);
    }
}

Sinon, si vous voulez 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;

}
0
Craig Champion

Je l’ai fait dans une catégorie de UIView, il suffit d’appeler la fonction en passant l’index pour les imprimer au format Nice tree. Ceci est juste une autre option de la réponse posté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 ça aide :)

0
valbu17