¿Cómo puedo recorrer todas las subvistas de una UIView, y sus subvistas y sus subvistas?

Respuestas:

122

Usar recursividad:

// UIView+HierarchyLogging.h
@interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy;
@end

// UIView+HierarchyLogging.m
@implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy
{
    NSLog(@"%@", self);
    for (UIView *subview in self.subviews)
    {
        [subview logViewHierarchy];
    }
}
@end

// In your implementation
[myView logViewHierarchy];
Ole Begemann
fuente
2
¿Hay alguna forma de pasar una función en logViewHierarchy? De esa manera, puede adaptarse a sus necesidades en diferentes momentos.
docchang
2
@docchang: Los bloques Obj-C son ideales para ese caso de uso. Pasas un bloque al método, ejecutas el bloque y lo pasas por la jerarquía con cada llamada al método.
Ole Begemann
Agradable. Publiqué un fragmento a continuación que agrega sangría de espacios en blanco a esta técnica.
jaredsinclair
34

Bueno, aquí está mi solución usando recursividad y un contenedor (categoría / extensión) para la clase UIView.

// UIView+viewRecursion.h
@interface UIView (viewRecursion)
- (NSMutableArray*) allSubViews;
@end

// UIView+viewRecursion.m
@implementation UIView (viewRecursion)
- (NSMutableArray*)allSubViews
{
   NSMutableArray *arr=[[[NSMutableArray alloc] init] autorelease];
   [arr addObject:self];
   for (UIView *subview in self.subviews)
   {
     [arr addObjectsFromArray:(NSArray*)[subview allSubViews]];
   }
   return arr;
}
@end

Uso: Ahora debería recorrer todas las sub vistas y manipularlas según sea necesario.

//disable all text fields
for(UIView *v in [self.view allSubViews])
{
     if([v isKindOfClass:[UITextField class]])
     {
         ((UITextField*)v).enabled=NO;
     }
}
RamaKrishna Chunduri
fuente
3
Tenga en cuenta que hay pérdida de memoria en la allSubViewsfunción: debe crear una matriz como [[[NSMutableArray alloc] init] autorelease]o como [NSMutableArray array](que es lo mismo).
ivanzoid
1
Por "un contenedor para UIView", en realidad se refiere a "una categoría para UIView".
Dimitris
Sí Dimitris, me refiero a la categoría.
RamaKrishna Chunduri
21

Aquí hay otra implementación de Swift:

extension UIView {
    var allSubviews: [UIView] {
        return self.subviews.flatMap { [$0] + $0.allSubviews }
    }
}
Cal Stephens
fuente
Simple y mejor
Balaji Ramakrishnan
16

Una solución en Swift 3 que lo da todo subviewssin incluir la vista en sí:

extension UIView {
var allSubViews : [UIView] {

        var array = [self.subviews].flatMap {$0}

        array.forEach { array.append(contentsOf: $0.allSubViews) }

        return array
    }
}
ielyamani
fuente
¿Qué se necesita de ".flatMap {$ 0}" en esta línea "var array = [self.subviews] .flatMap {$ 0}"?
Rahul Patel
@RahulPatel elimina nilelementos de una matriz, por seguridad al llamar allSubViewsa subvistas.
ielyamani
12

Etiqueto todo cuando se crea. Entonces es fácil encontrar cualquier subvista.

view = [aView viewWithTag:tag];
Corey DonCorsean Ricketts
fuente
10

A continuación se muestra un ejemplo con la funcionalidad de ruptura y bucle de vista real.

Rápido:

extension UIView {

    func loopViewHierarchy(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
        var stop = false
        block(self, &stop)
        if !stop {
            self.subviews.forEach { $0.loopViewHierarchy(block: block) }
        }
    }

}

Ejemplo de llamada:

mainView.loopViewHierarchy { (view, stop) in
    if view is UIButton {
        /// use the view
        stop = true
    }
}

Bucle invertido:

extension UIView {

    func loopViewHierarchyReversed(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
        for i in stride(from: self.highestViewLevel(view: self), through: 1, by: -1) {
            let stop = self.loopView(view: self, level: i, block: block)
            if stop {
                break
            }
        }
    }

    private func loopView(view: UIView, level: Int, block: (_ view: UIView, _ stop: inout Bool) -> ()) -> Bool {
        if level == 1 {
            var stop = false
            block(view, &stop)
            return stop
        } else if level > 1 {
            for subview in view.subviews.reversed() {
            let stop = self.loopView(view: subview, level: level - 1, block: block)
                if stop {
                    return stop
                }
            }
        }
        return false
    }

    private func highestViewLevel(view: UIView) -> Int {
        var highestLevelForView = 0
        for subview in view.subviews.reversed() {
            let highestLevelForSubview = self.highestViewLevel(view: subview)
            highestLevelForView = max(highestLevelForView, highestLevelForSubview)
        }
        return highestLevelForView + 1
    }
}

Ejemplo de llamada:

mainView.loopViewHierarchyReversed { (view, stop) in
    if view is UIButton {
        /// use the view
        stop = true
    }
}

C objetivo:

typedef void(^ViewBlock)(UIView* view, BOOL* stop);

@interface UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block;
@end

@implementation UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block {
    BOOL stop = NO;
    if (block) {
        block(self, &stop);
    }
    if (!stop) {
        for (UIView* subview in self.subviews) {
            [subview loopViewHierarchy:block];
        }
    }
}
@end

Ejemplo de llamada:

[mainView loopViewHierarchy:^(UIView* view, BOOL* stop) {
    if ([view isKindOfClass:[UIButton class]]) {
        /// use the view
        *stop = YES;
    }
}];
Vahram Dodoryan
fuente
7

Con la ayuda de Ole Begemann. Agregué algunas líneas para incorporar el concepto de bloque.

UIView + HierarchyLogging.h

typedef void (^ViewActionBlock_t)(UIView *);
@interface UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction;
@end

UIView + HierarchyLogging.m

@implementation UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction {
    //view action block - freedom to the caller
    viewAction(self);

    for (UIView *subview in self.subviews) {
        [subview logViewHierarchy:viewAction];
    }
}
@end

Usando la categoría HierarchyLogging en su ViewController. Ahora tiene libertad para lo que necesita hacer.

void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
    if ([view isKindOfClass:[UIButton class]]) {
        NSLog(@"%@", view);
    }
};
[self.view logViewHierarchy: ViewActionBlock];
docchang
fuente
Parece que hice una solución muy similar a la que propusiste aquí. Seguí adelante y lo publiqué en github en caso de que otros pudieran encontrarlo útil. Aquí está mi respuesta con el enlace :) stackoverflow.com/a/10440510/553394
Eric Goldberg
¿Cómo podría agregar una forma de detener la recursividad desde dentro del bloque? Supongamos que estoy usando esto para ubicar una vista específica, una vez que la encuentre, me gustaría detenerme allí y no continuar buscando en el resto de la jerarquía de vistas.
Mic Pringle
4

No es necesario crear ninguna función nueva. Solo hazlo cuando depures con Xcode.

Establezca un punto de interrupción en un controlador de vista y haga que la aplicación se detenga en este punto de interrupción.

Haz clic derecho en el área vacía y presiona "Agregar expresión ..." en la ventana Watch de Xcode.

Ingrese esta línea:

(NSString*)[self->_view recursiveDescription]

Si el valor es demasiado largo, haga clic derecho y seleccione "Imprimir descripción de ...". Verá todas las subvistas de self.view en la ventana de la consola. Cambie self -> _ view a otra cosa si no desea ver subvistas de self.view.

¡Hecho! ¡No gdb!

Vince Yuan
fuente
¿Puede explicar dónde está el 'área vacía'?
Probé
4

Aquí hay un código recursivo: -

 for (UIView *subViews in yourView.subviews) {
    [self removSubviews:subViews];

}   

-(void)removSubviews:(UIView *)subView
{
   if (subView.subviews.count>0) {
     for (UIView *subViews in subView.subviews) {

        [self removSubviews:subViews];
     }
  }
  else
  {
     NSLog(@"%i",subView.subviews.count);
    [subView removeFromSuperview];
  }
}
Leena
fuente
solución útil y ahorro de tiempo .. gracias 1 más para usted
Brainstorm Technolabs
3

Por cierto, hice un proyecto de código abierto para ayudar con este tipo de tarea. Es realmente fácil y usa bloques Objective-C 2.0 para ejecutar código en todas las vistas en una jerarquía.

https://github.com/egold/UIViewRecursion

Ejemplo:

-(void)makeAllSubviewsGreen
{
    [self.view runBlockOnAllSubviews:^(UIView *view) {

        view.backgroundColor = [UIColor greenColor];
    }];
}
Eric Goldberg
fuente
2

Aquí hay una variación de la respuesta de Ole Begemann anterior, que agrega sangría para ilustrar la jerarquía:

// UIView+HierarchyLogging.h
@interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces;
@end

// UIView+HierarchyLogging.m
@implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces {
    if (whiteSpaces == nil) {
        whiteSpaces = [NSString string];
    }
    NSLog(@"%@%@", whiteSpaces, self);

    NSString *adjustedWhiteSpaces = [whiteSpaces stringByAppendingFormat:@"    "];

    for (UIView *subview in self.subviews) {
        [subview logViewHierarchy:adjustedWhiteSpaces];
    }
}
@end
jaredsinclair
fuente
1

El código publicado en esta respuesta atraviesa todas las ventanas y todas las vistas y todas sus subvistas. Se usó para volcar una impresión de la jerarquía de vistas en NSLog, pero puede usarlo como base para cualquier recorrido de la jerarquía de vistas. Utiliza una función C recursiva para recorrer el árbol de vistas.

progrmr
fuente
0

Escribí una categoría algún tiempo para depurar algunas vistas.

IIRC, el código publicado es el que funcionó. Si no, le indicará la dirección correcta. Usar bajo su propio riesgo, etc.

TechZen
fuente
0

Esto también muestra el nivel de jerarquía

@implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(int)level
{
    NSLog(@"%d - %@", level, self);
    for (UIView *subview in self.subviews)
    {
        [subview logViewHierarchy:(level+1)];
    }
}
@end
snez
fuente
0

Ojalá hubiera encontrado esta página primero, pero si (por alguna razón) desea hacer esto de forma no recursiva, no en una categoría y con más líneas de código

smlxl
fuente
0

Creo que todas las respuestas que usan la recursividad (excepto la opción del depurador) usan categorías. Si no necesita / desea una categoría, puede usar un método de instancia. Por ejemplo, si necesita obtener una matriz de todas las etiquetas en su jerarquía de vista, puede hacer esto.

@interface MyViewController ()
@property (nonatomic, retain) NSMutableArray* labelsArray;
@end

@implementation MyViewController

- (void)recursiveFindLabelsInView:(UIView*)inView
{
    for (UIView *view in inView.subviews)
    {
        if([view isKindOfClass:[UILabel class]])
           [self.labelsArray addObject: view];
        else
           [self recursiveFindLabelsInView:view];
    }
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.labelsArray = [[NSMutableArray alloc] init];
    [self recursiveFindLabelsInView:self.view];

    for (UILabel *lbl in self.labelsArray)
    {
        //Do something with labels
    }
}
Donovan
fuente
-1

El método siguiente crea una o más matrices mutables, luego recorre las subvistas de la vista de entrada. Al hacerlo, agrega la subvista inicial y luego consulta si hay alguna subvista de esa subvista. Si es cierto, se vuelve a llamar a sí mismo. Lo hace hasta que se hayan agregado todas las vistas de la jerarquía.

-(NSArray *)allSubs:(UIView *)view {

    NSMutableArray * ma = [NSMutableArray new];
    for (UIView * sub in view.subviews){
        [ma addObject:sub];
        if (sub.subviews){
            [ma addObjectsFromArray:[self allSubs:sub]];
        }
    }
    return ma;
}

Llamar usando:

NSArray * subviews = [self allSubs:someView];
Johnny Rockex
fuente
1
Utilice el enlace de edición para explicar cómo funciona este código y no solo dé el código, ya que es más probable que una explicación ayude a los futuros lectores. Consulte también Cómo responder . fuente
Jed Fox
1
Por favor vea mi comentario arriba.
Jed Fox