Come elencare tutte le sottoview in un uiviewcontroller in iOS?


86

Voglio elencare tutte le sottoview in un file UIViewController. Ho provato self.view.subviews, ma non tutte le visualizzazioni secondarie sono elencate, ad esempio, le visualizzazioni secondarie in UITableViewCellnon sono state trovate. Qualche idea?


Puoi trovare tutte le visualizzazioni secondarie tramite la ricerca ricorsiva. vale a dire controllare che la sottoview ha sottoview ..
Mahesh

Risposte:


171

Devi iterare ricorsivamente le viste secondarie.

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

Come commentato da @Greg Meletic, puoi saltare la COUNT LINEA DI CONTROLLO sopra.


20
Tecnicamente, non è necessario verificare se il conteggio delle sottoview è 0.
Greg Maletic

5
NSLog (@ "\ n% @", [(id) self.view performSelector: @selector (recursiveDescription)]); Questa riga stampa lo stesso risultato del tuo!
Hemang

Se siete qui, verificare quanto molto più semplice recursiveDescriptionrisposta da @natbro - stackoverflow.com/a/8962824/429521
Felipe Sabino

Ecco il mio miglioramento alla soluzione @EmptyStack. Mostra il nome della classe e le coordinate del frame rientrate in modo ricorsivo
Pierre de LESPINAY

35

Il modo integrato xcode / gdb per eseguire il dump della gerarchia di visualizzazione è utile: recursiveDescription, per http://developer.apple.com/library/ios/#technotes/tn2239/_index.html

Produce una gerarchia di visualizzazione più completa che potresti trovare 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>>

Dove mettere il PO [_myToolbar recursiveDescription]nel mio codice o altrove.
पवन

1
@WildPointer Sta parlando della console. Hai bisogno di un punto di interruzione da qualche parte per farlo. po = oggetto di stampa
smileBot

1
+1 Apple consiglia questo approccio (secondo la sessione Hidden Gems in Cocoa and Cocoa Touch del WWDC 2013, suggerimento n. 5).
Slipp D. Thompson

@ पवन vedi la mia risposta qui . Ho riscritto questa risposta.
Miele

16

Elegante soluzione ricorsiva in Swift:

extension UIView {

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

}

Puoi chiamare subviewsRecursive () su qualsiasi UIView:

let allSubviews = self.view.subviewsRecursive()

13

È necessario stampare in modo ricorsivo, questo metodo anche le schede in base alla profondità della vista

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

Ecco la versione rapida

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

7

Sono un po 'in ritardo alla festa, ma una soluzione un po' più generale:

@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

6

Se tutto ciò che desideri è un array di messaggi di posta UIViewelettronica, questa è una soluzione di riga ( Swift 4+ ):

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

grazie per questa soluzione! Ho passato ore a capire ma poi il tuo post mi ha salvato!
user2525211

5

Uso in questo modo:

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

in UIViewController.


4

Dettagli

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

Soluzione

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

Utilizzo

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

Risultato

inserisci qui la descrizione dell'immagine


3

A mio modo, la categoria o l'estensione di UIView è molto migliore di altre e la ricorsività è il punto chiave per ottenere tutte le sottoview

Per saperne di più:

https://github.com/ZhipingYang/XYDebugView

inserisci qui la descrizione dell'immagine

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

esempio

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

esempio

let views = viewController.view.recurrenceAllSubviews()

direttamente, usa la funzione sequenza per ottenere tutte le viste secondarie

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 }

2

Il motivo per cui le sottoview in un UITableViewCell non vengono stampate è perché devi emettere tutte le sottoview nel livello superiore. Le visualizzazioni secondarie della cella non sono le visualizzazioni secondarie dirette della visualizzazione.

Per ottenere le sottoview di UITableViewCell, devi determinare quali sottoview appartengono a un UITableViewCell (utilizzando isKindOfClass:) nel tuo ciclo di stampa e quindi scorrere le sue sottoview

Modifica: questo post del blog su Easy UIView Debugging potrebbe potenzialmente aiutare


2

Ho scritto una categoria per elencare tutte le visualizzazioni detenute da un controller di visualizzazione che si ispira alle risposte pubblicate in precedenza.

@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

Per elencare tutte le visualizzazioni detenute da un controller di visualizzazione, basta NSLog("%@",[self listOfSubviews]), il che selfsignifica il controller di visualizzazione stesso. Anche se non è efficiente.

Inoltre, puoi usare NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]);per fare la stessa cosa e penso che sia più efficiente della mia implementazione.


2

Semplice esempio Swift:

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

1

Potresti provare un trucco di matrice fantasia, come:

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

Solo una riga di codice. Ovviamente, potresti dover regolare il tuo metodo printAllChildrenOfViewper non prendere alcun parametro o creare un nuovo metodo.


1

Compatibile con Swift 2.0

Ecco un metodo ricorsivo per ottenere tutte le sottoview di una visualizzazione generica:

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

Quindi puoi chiamare ovunque in questo modo:

let view = FooController.view
let subviews = view.subviewsList()

1

inserisci qui la descrizione dell'immagine

Soluzione più breve

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

Risultato

UIView
UIView
UISlider
UISwitch
UITextField
_UILayoutGuide
_UILayoutGuide

Appunti

  • Come puoi vedere, questo metodo non elenca le sottoview in modo ricorsivo. Vedi alcune delle altre risposte per questo.

1

Questa è una riscrittura di questa risposta:

Devi prima ottenere il puntatore / riferimento all'oggetto su cui intendi stampare tutte le sue viste secondarie. A volte potresti trovare più facile trovare quell'oggetto accedendovi attraverso la sua sottoview. Mi piace po someSubview.superview. Questo ti darà qualcosa come:

Optional<UIView>
   some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
  • FaceBookApp è il nome della tua app
  • WhatsNewView è il tipo di filesuperview
  • 0x7f91747c71f0 è il puntatore alla superview.

Per stampare il superView, è necessario utilizzare i punti di interruzione.


Ora per fare questo passaggio puoi semplicemente fare clic su "Visualizza gerarchia di debug". Non c'è bisogno di punti di interruzione

inserisci qui la descrizione dell'immagine

Quindi potresti facilmente fare:

po [0x7f91747c71f0 recursiveDescription]

che per me ha restituito qualcosa del tipo:

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

come avrai intuito la mia superview ha 4 sottoview:

  • uno stackView (lo stackView stesso ha un'immagine e un altro stackView (questo stackView ha 2 custom etichette ))
  • un textView
  • una vista
  • un bottone

Questo è abbastanza nuovo per me, ma mi ha aiutato a eseguire il debug dei frame delle mie visualizzazioni (e del testo e del tipo). Una delle mie sottoview non appariva sullo schermo, quindi ho usato recursiveDescription e mi sono reso conto che la larghezza di una delle mie sottoview era 0... quindi ho corretto i suoi vincoli e la sottoview appariva.


0

In alternativa, se desideri restituire un array di tutte le sottoview (e le sottoview nidificate) da un'estensione 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

AC # versione 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);
    }
}

In alternativa, se vuoi trovare tutte le sottoview di un tipo specifico che utilizzo:

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

L'ho fatto in una categoria di UIViewchiamare semplicemente la funzione passando l'indice per stamparli con un bel formato ad albero. Questa è solo un'altra opzione della risposta pubblicata da 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++];
        }
    }
}

Spero possa essere d'aiuto :)


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

-1

self.view.subviews mantiene la gerarchia delle visualizzazioni. Per ottenere le sottoview di uitableviewcell devi fare qualcosa come di seguito.

 for (UIView *subView in self.view.subviews) {
      if ([subView isKindOfClass:[UITableView class]]) {
            for (UIView *tableSubview in subView.subviews) {
                .......
            }
      }
 }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.