Jak mogę przeglądać wszystkie podglądy UIView, ich podglądy i podglądy

Odpowiedzi:

122

Użyj rekursji:

// 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
źródło
2
Czy istnieje sposób na przekazanie funkcji w logViewHierarchy? W ten sposób może odpowiadać Twoim potrzebom w różnym czasie.
docchang,
2
@docchang: Bloki Obj-C świetnie nadają się do tego przypadku użycia. Przekazujesz blok do metody, wykonujesz blok i przekazujesz go w dół hierarchii z każdym wywołaniem metody.
Ole Begemann
Miły. Opublikowałem poniżej fragment kodu, który dodaje wcięcie spacjami do tej techniki.
jaredsinclair
34

Oto moje rozwiązanie wykorzystujące rekursję i opakowanie (kategorię / rozszerzenie) dla klasy 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

Sposób użycia: Teraz powinieneś przeglądać wszystkie widoki podrzędne i manipulować nimi w razie potrzeby.

//disable all text fields
for(UIView *v in [self.view allSubViews])
{
     if([v isKindOfClass:[UITextField class]])
     {
         ((UITextField*)v).enabled=NO;
     }
}
RamaKrishna Chunduri
źródło
3
Zauważ, że w allSubViewsfunkcji występuje przeciek pamięci : musisz utworzyć tablicę jako [[[NSMutableArray alloc] init] autorelease]lub jako [NSMutableArray array](co jest tym samym).
ivanzoid
1
Przez „opakowanie dla UIView” masz na myśli „kategorię dla UIView”.
Dimitris
Tak, Dimitris, miałem na myśli kategorię.
RamaKrishna Chunduri
21

Oto kolejna implementacja Swift:

extension UIView {
    var allSubviews: [UIView] {
        return self.subviews.flatMap { [$0] + $0.allSubviews }
    }
}
Cal Stephens
źródło
Prosty i najlepszy
Balaji Ramakrishnan
16

Rozwiązanie w Swift 3, które daje wszystko subviewsbez uwzględniania samego widoku:

extension UIView {
var allSubViews : [UIView] {

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

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

        return array
    }
}
ielyamani
źródło
Po co nam „.flatMap {$ 0}” w tym wierszu „var array = [self.subviews] .flatMap {$ 0}”?
Rahul Patel
@RahulPatel usuwa nilelementy z tablicy, dla bezpieczeństwa podczas wywoływania allSubViewspodglądów podrzędnych.
ielyamani
12

Oznaczam wszystko, gdy jest tworzone. Wtedy łatwo jest znaleźć dowolny podgląd.

view = [aView viewWithTag:tag];
Corey Don Corsean Ricketts
źródło
12

Właśnie znalazłem interesujący sposób na zrobienie tego za pomocą debugera:

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

odnosi się do niniejszej notatki technicznej firmy Apple:

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

Po prostu upewnij się, że debugger jest wstrzymany (ustaw punkt przerwania lub wstrzymaj go ręcznie) i możesz poprosić o plik recursiveDescription.

dżibuti33
źródło
10

Oto przykład z rzeczywistą zapętleniem i zerwaniem funkcjonalności.

Szybki:

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

}

Przykład połączenia:

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

Odwrócona pętla:

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

Przykład połączenia:

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

Cel 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

Przykład połączenia:

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

Z pomocą Ole Begemanna. Dodałem kilka wierszy, aby uwzględnić w nim koncepcję bloku.

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

Korzystanie z kategorii HierarchyLogging w ViewController. Masz teraz swobodę w tym, co musisz zrobić.

void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
    if ([view isKindOfClass:[UIButton class]]) {
        NSLog(@"%@", view);
    }
};
[self.view logViewHierarchy: ViewActionBlock];
docchang
źródło
Wygląda na to, że stworzyłem bardzo podobne rozwiązanie, które zaproponowałeś tutaj. Poszedłem dalej i opublikowałem to na githubie na wypadek, gdyby inni uznali to za przydatne. Oto moja odpowiedź z linkiem :) stackoverflow.com/a/10440510/553394
Eric Goldberg
Jak mogę dodać sposób zatrzymania rekursji z poziomu bloku? Powiedzmy, że używam tego do zlokalizowania określonego widoku, a gdy już go znajdę, chciałbym zatrzymać się w tym miejscu i nie przeszukiwać pozostałej części hierarchii widoków.
Mic Pringle,
4

Nie trzeba tworzyć żadnej nowej funkcji. Po prostu zrób to podczas debugowania za pomocą Xcode.

Ustaw punkt przerwania w kontrolerze widoku i wstrzymaj aplikację w tym punkcie przerwania.

Kliknij prawym przyciskiem myszy pusty obszar i naciśnij „Dodaj wyrażenie ...” w oknie Watch Xcode.

Wprowadź tę linię:

(NSString*)[self->_view recursiveDescription]

Jeśli wartość jest zbyt długa, kliknij ją prawym przyciskiem myszy i wybierz „Drukuj opis ...”. W oknie konsoli zobaczysz wszystkie podglądy self.view. Zmień self -> _ view na coś innego, jeśli nie chcesz widzieć podglądów podrzędnych self.view.

Gotowe! Nie gdb!

Vince Yuan
źródło
Czy możesz wyjaśnić, gdzie jest „pusty obszar”? Próbowałem całego okna Xcode po uruchomieniu punktu przerwania, ale nie mogę znaleźć, gdzie wprowadzić ciąg
SundialSoft
4

Oto kod rekurencyjny: -

 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
źródło
pomocne rozwiązanie i
oszczędność
3

Nawiasem mówiąc, stworzyłem projekt open source, aby pomóc w tego rodzaju zadaniach. Jest to naprawdę proste i używa bloków Objective-C 2.0 do wykonywania kodu we wszystkich widokach w hierarchii.

https://github.com/egold/UIViewRecursion

Przykład:

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

        view.backgroundColor = [UIColor greenColor];
    }];
}
Eric Goldberg
źródło
2

Oto wariacja na temat powyższej odpowiedzi Ole Begemanna , która dodaje wcięcie w celu zilustrowania hierarchii:

// 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
źródło
1

Kod zamieszczony w tej odpowiedzi przechodzi przez wszystkie okna i wszystkie widoki oraz wszystkie ich podglądy. Został użyty do zrzucenia wydruku hierarchii widoków do NSLog, ale można go użyć jako podstawy do dowolnego przechodzenia w hierarchii widoków. Używa rekurencyjnej funkcji C do przechodzenia przez drzewo widoku.

progrmr
źródło
0

Jakiś czas temu napisałem kategorię, aby debugować niektóre widoki.

IIRC, wysłany kod to ten, który zadziałał. Jeśli nie, wskaże ci właściwy kierunek. Używaj na własne ryzyko itp.

TechZen
źródło
0

Wyświetla również poziom hierarchii

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

Szkoda, że ​​nie znalazłem tej strony jako pierwsza, ale jeśli (z jakiegoś powodu) chcesz to zrobić nierekurencyjnie, nie w kategorii iz większą liczbą wierszy kodu

smlxl
źródło
0

Myślę, że wszystkie odpowiedzi z rekurencją (z wyjątkiem opcji debuggera) wykorzystywały kategorie. Jeśli nie potrzebujesz / nie chcesz kategorii, możesz po prostu użyć metody instancji. Na przykład, jeśli chcesz uzyskać tablicę wszystkich etykiet w hierarchii widoku, możesz to zrobić.

@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
źródło
-1

Poniższa metoda tworzy jedną lub więcej mutowalnych tablic, a następnie przechodzi przez podglądy widoku wejściowego. Robiąc to, dodaje początkowy widok podrzędny, a następnie pyta, czy są jakieś podglądy tego widoku. Jeśli to prawda, woła się ponownie. Dzieje się tak, dopóki wszystkie widoki hierarchii nie zostaną dodane.

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

Zadzwoń za pomocą:

NSArray * subviews = [self allSubs:someView];
Johnny Rockex
źródło
1
Użyj linku edytuj, aby wyjaśnić, jak działa ten kod, a nie tylko podawać kod, ponieważ wyjaśnienie może pomóc przyszłym czytelnikom. Zobacz także Jak odpowiedzieć . źródło
Jed Fox,
1
Zobacz mój komentarz powyżej.
Jed Fox,