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?


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

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

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

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

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



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

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

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!


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

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

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

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.


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.


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

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


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

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

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.