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

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

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

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

¿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



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

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

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!


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

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

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

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.


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.


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

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


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

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

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.