Comment puis-je parcourir toutes les sous-vues d'un UIView, leurs sous-vues et leurs sous-vues?
Utiliser la récursivité:
// 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];
Eh bien voici ma solution utilisant la récursivité et un wrapper (catégorie/extension) pour la classe 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
Utilisation: Vous devriez maintenant parcourir toutes les sous-vues et les manipuler selon vos besoins.
//disable all text fields
for(UIView *v in [self.view allSubViews])
{
if([v isKindOfClass:[UITextField class]])
{
((UITextField*)v).enabled=NO;
}
}
Une solution dans Swift 3 qui donne tout subviews
sans inclure la vue elle-même:
extension UIView {
var allSubViews : [UIView] {
var array = [self.subviews].flatMap {$0}
array.forEach { array.append(contentsOf: $0.allSubViews) }
return array
}
}
Je viens de trouver un moyen intéressant de le faire via le débogueur:
http://idevrecipes.com/2011/02/10/exploring-iphone-view-hierarchies/
fait référence à ceci Apple Note technique:
https://developer.Apple.com/library/content/technotes/tn2239/_index.html#SECUIKIT
Assurez-vous simplement que votre débogueur est en pause (définissez un point d'arrêt ou de le mettre en pause manuellement) et vous pouvez demander le recursiveDescription
.
Je marque tout quand il est créé. Ensuite, il est facile de trouver n'importe quelle sous-vue.
view = [aView viewWithTag:tag];
Avec l'aide d'Ole Begemann. J'ai ajouté quelques lignes pour incorporer le concept de bloc.
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
Utilisation de la catégorie HierarchyLogging dans votre ViewController. Vous êtes maintenant libre de ce que vous devez faire.
void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
if ([view isKindOfClass:[UIButton class]]) {
NSLog(@"%@", view);
}
};
[self.view logViewHierarchy: ViewActionBlock];
Voici une autre implémentation Swift:
extension UIView {
var allSubviews: [UIView] {
return self.subviews.flatMap { [$0] + $0.allSubviews }
}
}
Voici un exemple avec la fonctionnalité de bouclage et de coupure de vue réelle.
Swift:
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) }
}
}
}
Exemple d'appel:
mainView.loopViewHierarchy { (view, stop) in
if view is UIButton {
/// use the view
stop = true
}
}
Boucle inversée:
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
}
}
Exemple d'appel:
mainView.loopViewHierarchyReversed { (view, stop) in
if view is UIButton {
/// use the view
stop = true
}
}
Objective-C:
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
Exemple d'appel:
[mainView loopViewHierarchy:^(UIView* view, BOOL* stop) {
if ([view isKindOfClass:[UIButton class]]) {
/// use the view
*stop = YES;
}
}];
Il n'est pas nécessaire de créer une nouvelle fonction. Faites-le simplement lors du débogage avec Xcode.
Définissez un point d'arrêt dans un contrôleur de vue et mettez l'application en pause à ce point d'arrêt.
Cliquez avec le bouton droit sur la zone vide et appuyez sur "Ajouter une expression ..." dans la fenêtre de surveillance de Xcode.
Entrez cette ligne:
(NSString*)[self->_view recursiveDescription]
Si la valeur est trop longue, cliquez dessus avec le bouton droit de la souris et choisissez "Imprimer la description de ...". Vous verrez toutes les sous-vues de self.view dans la fenêtre de la console. Changez auto -> _ view en autre chose si vous ne voulez pas voir les sous-vues de self.view.
Terminé! Pas de gdb!
Voici un code récursif: -
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];
}
}
A propos, j'ai créé un projet open source pour aider à ce genre de tâche. C'est très simple, et utilise des blocs Objective-C 2.0 pour exécuter du code sur toutes les vues d'une hiérarchie.
https://github.com/egold/UIViewRecursion
Exemple:
-(void)makeAllSubviewsGreen
{
[self.view runBlockOnAllSubviews:^(UIView *view) {
view.backgroundColor = [UIColor greenColor];
}];
}
Voici une variation sur réponse d'Ole Begemann ci-dessus, qui ajoute une indentation pour illustrer la hiérarchie:
// 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
Le code publié dans cette réponse traverse toutes les fenêtres et toutes les vues et toutes leurs sous-vues. Il a été utilisé pour sauvegarder une impression de la hiérarchie de vues dans NSLog, mais vous pouvez l'utiliser comme base pour toute traversée de la hiérarchie de vues. Il utilise une fonction C récursive pour parcourir l'arborescence de la vue.
J'ai écrit ne catégorie quelque temps en arrière pour déboguer quelques vues.
IIRC, le code affiché est celui qui a fonctionné. Sinon, cela vous orientera dans la bonne direction. Utiliser à vos propres risques, etc.
J'aurais aimé trouver cette page d'abord, mais si (pour une raison quelconque) vous voulez le faire de manière non récursive, pas dans une catégorie et avec plus de lignes de code
Cela affiche également le niveau hiérarchique
@implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(int)level
{
NSLog(@"%d - %@", level, self);
for (UIView *subview in self.subviews)
{
[subview logViewHierarchy:(level+1)];
}
}
@end
Je pense que toutes les réponses utilisant la récursivité (à l'exception de l'option de débogage) utilisaient des catégories. Si vous n'avez pas besoin ou ne voulez pas d'une catégorie, vous pouvez simplement utiliser une méthode d'instance. Par exemple, si vous devez obtenir un tableau de toutes les étiquettes dans votre hiérarchie de vues, vous pouvez le faire.
@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
}
}