Jak mogę przejrzeć wszystkie podglądy UIView oraz ich podglądy i podglądy?
Jak mogę przejrzeć wszystkie podglądy UIView oraz ich podglądy i podglądy?
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];
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;
}
}
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 }
}
}
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
}
}
nil
elementy z tablicy, dla bezpieczeństwa podczas wywoływania allSubViews
podglądów podrzędnych.
Oznaczam wszystko, gdy jest tworzone. Wtedy łatwo jest znaleźć dowolny podgląd.
view = [aView viewWithTag:tag];
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
.
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;
}
}];
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];
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!
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.
https://github.com/egold/UIViewRecursion
Przykład:
-(void)makeAllSubviewsGreen
{
[self.view runBlockOnAllSubviews:^(UIView *view) {
view.backgroundColor = [UIColor greenColor];
}];
}
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
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.
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.
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ć.
@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
}
}
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];