Wie kann ich alle Unteransichten einer UIView sowie deren Unteransichten und Unteransichten durchlaufen?

81

Wie kann ich alle Unteransichten einer UIView sowie deren Unteransichten und Unteransichten durchlaufen?

123hal321
quelle
7
Wenn Sie wirklich eine Schleife durchlaufen MÜSSEN, ist die akzeptierte Antwort korrekt. Wenn Sie nur nach einer einzelnen Ansicht suchen, markieren Sie sie und verwenden Sie stattdessen viewWithTag - sparen Sie sich etwas Schmerz! - developer.apple.com/library/ios/documentation/UIKit/Reference/…
Norman H

Antworten:

122

Rekursion verwenden:

// 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
quelle
2
Gibt es eine Möglichkeit, eine Funktion in der logViewHierarchy zu übergeben? Auf diese Weise kann es zu unterschiedlichen Zeiten Ihren Anforderungen entsprechen.
Docchang
2
@docchang: Obj-C-Blöcke eignen sich hervorragend für diesen Anwendungsfall. Sie übergeben einen Block an die Methode, führen den Block aus und übergeben ihn bei jedem Methodenaufruf in der Hierarchie.
Ole Begemann
Nett. Ich habe unten einen Ausschnitt gepostet, der dieser Technik Leerzeicheneinrückungen hinzufügt.
Jaredsinclair
34

Nun, hier ist meine Lösung mit Rekursion und einem Wrapper (Kategorie / Erweiterung) für die UIView-Klasse.

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

Verwendung: Jetzt sollten Sie alle Unteransichten durchlaufen und sie nach Bedarf bearbeiten.

//disable all text fields
for(UIView *v in [self.view allSubViews])
{
     if([v isKindOfClass:[UITextField class]])
     {
         ((UITextField*)v).enabled=NO;
     }
}
RamaKrishna Chunduri
quelle
3
Beachten Sie, dass die allSubViewsFunktion einen Speicherverlust aufweist : Sie müssen entweder ein Array als [[[NSMutableArray alloc] init] autorelease]oder als erstellen [NSMutableArray array](was dasselbe ist).
Ivanzoid
1
Mit "ein Wrapper für UIView" meinen Sie tatsächlich "eine Kategorie für UIView".
Dimitris
Ja Dimitris, ich meinte Kategorie.
RamaKrishna Chunduri
20

Hier ist eine weitere Swift-Implementierung:

extension UIView {
    var allSubviews: [UIView] {
        return self.subviews.flatMap { [$0] + $0.allSubviews }
    }
}
Cal Stephens
quelle
Einfach und am besten
Balaji Ramakrishnan
16

Eine Lösung in Swift 3, die alles bietet, subviewsohne die Ansicht selbst einzubeziehen:

extension UIView {
var allSubViews : [UIView] {

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

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

        return array
    }
}
ielyamani
quelle
Was braucht ".flatMap {$ 0}" in dieser Zeile "var array = [self.subviews] .flatMap {$ 0}"?
Rahul Patel
@RahulPatel entfernt nilElemente aus einem Array, um die Sicherheit beim Aufrufen allSubViewsvon Unteransichten zu gewährleisten.
ielyamani
12

Ich tagge alles, wenn es erstellt wird. Dann ist es einfach, eine Unteransicht zu finden.

view = [aView viewWithTag:tag];
Corey DonCorsean Ricketts
quelle
12

Ich habe gerade einen interessanten Weg gefunden, dies über den Debugger zu tun:

http://idevrecipes.com/2011/02/10/exploring-iphone-view-hierarchies/

verweist auf diese Apple Technote:

https://developer.apple.com/library/content/technotes/tn2239/_index.html#SECUIKIT

Stellen Sie einfach sicher, dass Ihr Debugger angehalten ist (entweder setzen Sie einen Haltepunkt, um ihn manuell anzuhalten), und Sie können nach dem fragen recursiveDescription.

dschibuti33
quelle
10

Hier ist ein Beispiel mit tatsächlichen Funktionen zum Schleifen und Unterbrechen von Ansichten.

Schnell:

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

}

Beispiel aufrufen:

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

Umgekehrte Schleife:

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

Beispiel aufrufen:

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

Ziel 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

Beispiel aufrufen:

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

Mit Hilfe von Ole Begemann. Ich habe ein paar Zeilen hinzugefügt, um das Blockkonzept darin zu integrieren.

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

Verwenden der Kategorie "HierarchyLogging" in Ihrem ViewController. Sie haben jetzt die Freiheit, was Sie tun müssen.

void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
    if ([view isKindOfClass:[UIButton class]]) {
        NSLog(@"%@", view);
    }
};
[self.view logViewHierarchy: ViewActionBlock];
docchang
quelle
Sieht so aus, als hätte ich eine sehr ähnliche Lösung gefunden wie die, die Sie hier vorgeschlagen haben. Ich habe es auf Github gepostet, falls andere es nützlich finden könnten. Hier ist meine Antwort mit dem Link :) stackoverflow.com/a/10440510/553394
Eric Goldberg
Wie kann ich eine Möglichkeit hinzufügen, die Rekursion innerhalb des Blocks anzuhalten? Angenommen, ich verwende dies, um eine bestimmte Ansicht zu finden. Sobald ich sie gefunden habe, möchte ich dort anhalten und den Rest der Ansichtshierarchie nicht weiter durchsuchen.
Mic Pringle
4

Es muss keine neue Funktion erstellt werden. Tun Sie es einfach beim Debuggen mit Xcode.

Legen Sie einen Haltepunkt in einem Ansichts-Controller fest und lassen Sie die App an diesem Haltepunkt anhalten.

Klicken Sie mit der rechten Maustaste auf den leeren Bereich und klicken Sie im Xcode-Überwachungsfenster auf "Ausdruck hinzufügen ...".

Geben Sie diese Zeile ein:

(NSString*)[self->_view recursiveDescription]

Wenn der Wert zu lang ist, klicken Sie mit der rechten Maustaste darauf und wählen Sie "Beschreibung von ... drucken". Sie sehen alle Unteransichten von self.view im Konsolenfenster. Ändern Sie die Ansicht self -> _ in eine andere Ansicht, wenn Sie keine Unteransichten von self.view anzeigen möchten.

Erledigt! Kein gdb!

Vince Yuan
quelle
Können Sie erklären, wo sich der "leere Bereich" befindet? Ich habe alles rund um das Xcode-Fenster versucht, sobald der Haltepunkt ausgelöst wurde, kann aber nicht finden, wo die Zeichenfolge
eingegeben werden soll
4

Hier ist ein rekursiver Code: -

 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
quelle
hilfreiche Lösung und Zeitersparnis .. Danke 1 plus für u
Brainstorm Technolabs
3

Übrigens habe ich ein Open-Source-Projekt erstellt, um bei dieser Art von Aufgabe zu helfen. Es ist wirklich einfach und verwendet Objective-C 2.0-Blöcke, um Code für alle Ansichten in einer Hierarchie auszuführen.

https://github.com/egold/UIViewRecursion

Beispiel:

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

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

Hier ist eine Variation der obigen Antwort von Ole Begemann , die Einrückungen hinzufügt, um die Hierarchie zu veranschaulichen:

// 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
quelle
1

Der in dieser Antwort veröffentlichte Code durchläuft alle Fenster und alle Ansichten sowie alle ihre Unteransichten. Es wurde verwendet, um einen Ausdruck der Ansichtshierarchie an NSLog zu senden, aber Sie können ihn als Grundlage für jede Durchquerung der Ansichtshierarchie verwenden. Es verwendet eine rekursive C-Funktion, um den Ansichtsbaum zu durchlaufen.

Progrmr
quelle
0

Ich habe vor einiger Zeit eine Kategorie geschrieben , um einige Ansichten zu debuggen.

IIRC, der gebuchte Code hat funktioniert. Wenn nicht, zeigt es Ihnen in die richtige Richtung. Verwendung auf eigenes Risiko usw.

TechZen
quelle
0

Dies zeigt auch die Hierarchieebene an

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

Ich wünschte, ich hätte diese Seite zuerst gefunden, aber wenn Sie dies (aus irgendeinem Grund) nicht rekursiv tun möchten, nicht in einer Kategorie und mit mehr Codezeilen

smlxl
quelle
0

Ich denke, alle Antworten mit Rekursion (mit Ausnahme der Debugger-Option) verwendeten Kategorien. Wenn Sie keine Kategorie benötigen / möchten, können Sie einfach eine Instanzmethode verwenden. Wenn Sie beispielsweise ein Array aller Beschriftungen in Ihrer Ansichtshierarchie abrufen müssen, können Sie dies tun.

@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
quelle
-1

Die folgende Methode erstellt ein oder mehrere veränderbare Arrays und durchläuft dann die Unteransichten der Eingabeansicht. Dabei wird die anfängliche Unteransicht hinzugefügt und dann abgefragt, ob Unteransichten dieser Unteransicht vorhanden sind. Wenn true, ruft es sich erneut auf. Dies geschieht so lange, bis alle Ansichten der Hierarchie hinzugefügt wurden.

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

Rufen Sie an mit:

NSArray * subviews = [self allSubs:someView];
Johnny Rockex
quelle
1
Bitte benutzen Sie den Bearbeitungslink, um zu erklären, wie dieser Code funktioniert, und geben Sie nicht nur den Code an, da eine Erklärung eher zukünftigen Lesern helfen wird. Siehe auch Antworten . Quelle
Jed Fox
1
Bitte beachten Sie meinen Kommentar oben.
Jed Fox