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

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

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


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

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



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

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

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!


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

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

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

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.


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.


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

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


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

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

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,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.