¿Cómo puedo recorrer todas las subvistas de una UIView y sus subvistas y sus subvistas?
¿Cómo puedo recorrer todas las subvistas de una UIView y sus subvistas y sus subvistas?
Respuestas:
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];
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;
}
}
allSubViews
función: debe crear una matriz como [[[NSMutableArray alloc] init] autorelease]
o como [NSMutableArray array]
(que es lo mismo).
Aquí hay otra implementación de Swift:
extension UIView {
var allSubviews: [UIView] {
return self.subviews.flatMap { [$0] + $0.allSubviews }
}
}
Una solución en Swift 3 que lo da todo subviews
sin 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
}
}
nil
elementos de una matriz, por seguridad al llamar allSubViews
a subvistas.
Etiqueto todo cuando se crea. Entonces es fácil encontrar cualquier subvista.
view = [aView viewWithTag:tag];
Acabo de encontrar una forma interesante de hacer esto a través del depurador:
http://idevrecipes.com/2011/02/10/exploring-iphone-view-hierarchies/
hace referencia a esta nota técnica de Apple:
https://developer.apple.com/library/content/technotes/tn2239/_index.html#SECUIKIT
Solo asegúrese de que su depurador esté en pausa (establezca un punto de interrupción o pause manualmente) y puede solicitar el archivo recursiveDescription
.
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;
}
}];
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];
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!
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];
}
}
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];
}];
}
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
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.
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.
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
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
}
}
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];