Como listar todas as subvisualizações em um uiviewcontroller no iOS?

86

Eu quero listar todas as subvisualizações em a UIViewController. Eu tentei self.view.subviews, mas nem todas as subvisualizações estão listadas, por exemplo, as subvisualizações no UITableViewCellnão foram encontradas. Qualquer ideia?

user403015
fonte
Você pode descobrir todas as subvisualizações por pesquisa recursiva. ou seja, verificar subviews tem subviews ..
Mahesh

Respostas:

171

Você precisa iterar recursivamente as subvisões.

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

Conforme comentado por @Greg Meletic, você pode pular a LINHA DE VERIFICAÇÃO DE CONTAGEM acima.

EmptyStack
fonte
20
Tecnicamente, você não precisa verificar se a contagem de
subvisualização
5
NSLog (@ "\ n% @", [(id) self.view performSelector: @selector (recursiveDescription)]); Esta linha imprime o mesmo resultado que a sua!
Hemang
Se você está aqui, POR FAVOR, verifique a recursiveDescriptionresposta muito mais simples de @natbro - stackoverflow.com/a/8962824/429521
Felipe Sabino
Aqui está minha melhoria para a solução @EmptyStack. Mostra o nome da classe e as coordenadas do quadro recursivamente recursivamente
Pierre de LESPINAY
35

A forma integrada xcode / gdb de despejar a hierarquia de visualização é útil - recursiveDescription, de acordo com http://developer.apple.com/library/ios/#technotes/tn2239/_index.html

Ele produz uma hierarquia de visualização mais completa que pode ser útil:

> 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
fonte
Onde colocar o po [_myToolbar recursiveDescription]no meu código ou em outro lugar.
पवन
1
@WildPointer Ele está falando sobre o console. Você precisa de um ponto de interrupção em algum lugar para fazer isso. po = imprimir objeto
smileBot
1
+1 A Apple recomenda essa abordagem (de acordo com a sessão Hidden Gems in Cocoa e Cocoa Touch da WWDC 2013, dica nº 5).
Slipp D. Thompson
@ पवन veja minha resposta aqui . Eu reescrevi esta resposta.
Honey
16

Solução recursiva elegante em Swift:

extension UIView {

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

}

Você pode chamar subviewsRecursive () em qualquer UIView:

let allSubviews = self.view.subviewsRecursive()
Alex
fonte
13

Você precisa imprimir recursivamente, este método também abas com base na profundidade da 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];
    }

}
James Webster
fonte
13

Aqui está a versão rápida

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

Estou um pouco atrasado para a festa, mas uma solução um pouco mais geral:

@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
fonte
6

Se tudo o que você deseja é uma matriz de UIViews, esta é uma solução de um revestimento ( Swift 4+ ):

extension UIView {
  var allSubviews: [UIView] {
    return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
  }
}
Federico Zanetello
fonte
obrigado por esta solução! Passei horas tentando descobrir, mas então sua postagem me salvou!
user2525211
5

Eu uso desta forma:

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

no UIViewController.

Grafite
fonte
4

Detalhes

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

Solução

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

Uso

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

Resultado

insira a descrição da imagem aqui

Vasily Bodnarchuk
fonte
3

A meu ver, a categoria ou extensão do UIView é muito melhor do que outras e recursiva é o ponto chave para obter todas as subvisualizações

Saber mais:

https://github.com/ZhipingYang/XYDebugView

insira a descrição da imagem aqui

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

exemplo

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

exemplo

let views = viewController.view.recurrenceAllSubviews()

diretamente, use a função de sequência para obter todas as subvisualizações

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

A razão pela qual as subvisualizações em um UITableViewCell não são impressas é porque você deve enviar todas as subvisualizações no nível superior. As subvisualizações da célula não são as subvisualizações diretas da sua visão.

A fim de obter as subvisualizações de UITableViewCell, você precisa determinar quais subvisualizações pertencem a um UITableViewCell (usando isKindOfClass:) em seu loop de impressão e, em seguida, percorrer suas subvisualizações

Edit: esta postagem do blog sobre Easy UIView Debugging pode potencialmente ajudar

Suhail Patel
fonte
2

Eu escrevi uma categoria para listar todas as visualizações mantidas por um controlador de visualização inspirado nas respostas postadas antes.

@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

Para listar todas as visualizações mantidas por um controlador de visualização, apenas NSLog("%@",[self listOfSubviews]), o que selfsignifica o próprio controlador de visualização. Embora não seja muito eficiente.

Além disso, você pode usar NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]);para fazer a mesma coisa, e acho que é mais eficiente do que minha implementação.

WeZZard
fonte
2

Exemplo simples de Swift:

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

Você pode tentar um truque de array sofisticado, como:

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

Apenas uma linha de código. Claro, você pode precisar ajustar seu método printAllChildrenOfViewpara não tomar nenhum parâmetro ou fazer um novo método.

johnbakers
fonte
1

Compatível com Swift 2.0

Aqui, um método recursivo para obter todas as subvisualizações de uma visão genérica:

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

Então você pode ligar para qualquer lugar desta forma:

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

insira a descrição da imagem aqui

Solução mais curta

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

Resultado

UIView
UIView
UISlider
UISwitch
UITextField
_UILayoutGuide
_UILayoutGuide

Notas

  • Como você pode ver, este método não lista as subvisualizações recursivamente. Veja algumas das outras respostas para isso.
Suragch
fonte
1

Isso é uma reescrita desta resposta:

Você deve primeiro obter o ponteiro / referência para o objeto que pretende imprimir todas as suas subvisualizações. Às vezes, você pode achar mais fácil encontrar esse objeto acessando-o por meio de sua subvisão. Gosto po someSubview.superview. Isso lhe dará algo como:

Optional<UIView>
   some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
  • FaceBookApp é o nome do seu aplicativo
  • WhatsNewView é o tipo de seusuperview
  • 0x7f91747c71f0 é o ponteiro para a supervisualização.

Para imprimir o superView, você deve usar pontos de interrupção.


Agora, para fazer esta etapa, você pode apenas clicar em 'ver hierarquia de depuração'. Não há necessidade de pontos de interrupção

insira a descrição da imagem aqui

Então você poderia facilmente fazer:

po [0x7f91747c71f0 recursiveDescription]

que para mim retornou algo como:

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

como você deve ter adivinhado, meu superview tem 4 subviews:

  • uma stackView (a própria stackView tem uma imagem e outra stackView (esta stackView tem 2 rótulos personalizados ))
  • um textView
  • uma vista
  • um botão

Isso é bastante novo para mim, mas me ajudou a depurar os quadros de minhas visualizações (e texto e tipo). Uma das minhas subvisualizações não estava aparecendo na tela, então usei recursiveDescription e percebi que a largura da minha uma das subvisualizações era 0... então fui corrigido suas restrições e a subvisualização estava aparecendo.

Mel
fonte
0

Como alternativa, se você quiser retornar uma matriz de todas as subvisualizações (e subvisualizações aninhadas) de uma extensão 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
fonte
0

Versão 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);
    }
}

Alternativamente, se você quiser encontrar todas as subvisualizações de um tipo específico que eu uso:

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

Eu fiz isso em uma categoria de UIViewapenas chamar a função passando o índice para imprimi-los com um formato de árvore agradável. Esta é apenas mais uma opção da resposta postada por 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++];
        }
    }
}

Espero que ajude :)

valbu17
fonte
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
fonte
-1

self.view.subviews mantém a hierarquia de visualizações. Para obter subvisualizações de uitableviewcell, você deve fazer algo como a seguir.

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