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?


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.


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

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

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

}

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

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

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

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.


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


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 }

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


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.


2

Exemplo simples de Swift:

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

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.


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

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.

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.


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
}

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;

}

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


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 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) {
                .......
            }
      }
 }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.