Jeśli naprawdę POTRZEBUJESZ zapętlić, to zaakceptowana odpowiedź jest poprawna, jeśli szukasz tylko jednego widoku, oznacz go i użyj zamiast tego viewWithTag - oszczędzaj sobie trochę bólu! - developer.apple.com/library/ios/documentation/UIKit/Reference/…
Norman H
Odpowiedzi:
122
Użyj rekursji:
// UIView+HierarchyLogging.h@interfaceUIView (ViewHierarchyLogging)
- (void)logViewHierarchy;
@end// UIView+HierarchyLogging.m@implementationUIView (ViewHierarchyLogging)
- (void)logViewHierarchy
{
NSLog(@"%@", self);
for (UIView *subview inself.subviews)
{
[subview logViewHierarchy];
}
}
@end// In your implementation
[myView logViewHierarchy];
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.
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”.
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.
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];
}
}
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.
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.
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
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ć.
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;
}
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
Odpowiedzi:
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];
źródło
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; } }
źródło
allSubViews
funkcji występuje przeciek pamięci : musisz utworzyć tablicę jako[[[NSMutableArray alloc] init] autorelease]
lub jako[NSMutableArray array]
(co jest tym samym).Oto kolejna implementacja Swift:
extension UIView { var allSubviews: [UIView] { return self.subviews.flatMap { [$0] + $0.allSubviews } } }
źródło
Rozwiązanie w Swift 3, które daje wszystko
subviews
bez uwzględniania samego widoku:extension UIView { var allSubViews : [UIView] { var array = [self.subviews].flatMap {$0} array.forEach { array.append(contentsOf: $0.allSubViews) } return array } }
źródło
nil
elementy z tablicy, dla bezpieczeństwa podczas wywoływaniaallSubViews
podglądów podrzędnych.Oznaczam wszystko, gdy jest tworzone. Wtedy łatwo jest znaleźć dowolny podgląd.
źródło
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
.źródło
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; } }];
źródło
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];
źródło
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!
źródło
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]; } }
źródło
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]; }]; }
źródło
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
źródło
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.
źródło
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.
źródło
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
źródło
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
źródło
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 } }
źródło
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];
źródło