Jak wyświetlić wszystkie podglądy w kontrolerze uiview w iOS?

87

Chcę wymienić wszystkie podglądy w pliku UIViewController. Próbowałem self.view.subviews, ale nie wszystkie podglądy są wymienione, na przykład podglądy podrzędne w UITableViewCellnie zostały znalezione. Dowolny pomysł?

user403015
źródło
Możesz znaleźć wszystkie podglądy przez wyszukiwanie rekurencyjne. tzn. podwidok kontrolny ma podglądy ..
Mahesh

Odpowiedzi:

172

Musisz rekurencyjnie iterować widoki podrzędne.

- (void)listSubviewsOfView:(UIView *)view {

    // Get the subviews of the view
    NSArray *subviews = [view subviews];

    // Return if there are no subviews
    if ([subviews count] == 0) return; // COUNT CHECK LINE

    for (UIView *subview in subviews) {

        // Do what you want to do with the subview
        NSLog(@"%@", subview);

        // List the subviews of subview
        [self listSubviewsOfView:subview];
    }
}

Jak skomentował @Greg Meletic, możesz pominąć LICZNIK LINII SPRAWDZANIA powyżej.

EmptyStack
źródło
20
Technicznie rzecz biorąc, nie musisz sprawdzać, czy liczba
subview
5
NSLog (@ "\ n% @", [(id) self.view performSelector: @selector (recursiveDescription)]); Ta linia drukuje ten sam wynik co twój!
Hemang
Jeśli tu jesteś, PROSZĘ sprawdzić znacznie prostszą recursiveDescriptionodpowiedź od @natbro - stackoverflow.com/a/8962824/429521
Felipe Sabino
Oto moje ulepszenie rozwiązania @EmptyStack. Pokazuje nazwę klasy i współrzędne ramki z rekursywnym
wcięciem
35

Wbudowany sposób xcode / gdb do zrzucania hierarchii widoków jest przydatny - recursiveDescription, na http://developer.apple.com/library/ios/#technotes/tn2239/_index.html

Wyświetla bardziej kompletną hierarchię widoków, która może okazać się przydatna:

> po [_myToolbar recursiveDescription]

<UIToolbarButton: 0xd866040; frame = (152 0; 15 44); opaque = NO; layer = <CALayer: 0xd864230>>
   | <UISwappableImageView: 0xd8660f0; frame = (0 0; 0 0); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xd86a160>>
natbro
źródło
Gdzie umieścić plik po [_myToolbar recursiveDescription]w moim kodzie lub w innym miejscu.
पवन
1
@WildPointer Mówi o konsoli. Aby to zrobić, potrzebujesz gdzieś punktu przerwania. po = print object
smileBot
1
+1 Apple zaleca takie podejście (zgodnie z sesją WWDC 2013 Hidden Gems in Cocoa and Cocoa Touch, wskazówka nr 5).
Slipp D. Thompson,
@ पवन zobacz moją odpowiedź tutaj . Przepisałem tę odpowiedź.
Honey
16

Eleganckie rozwiązanie rekurencyjne w Swift:

extension UIView {

    func subviewsRecursive() -> [UIView] {
        return subviews + subviews.flatMap { $0.subviewsRecursive() }
    }

}

Możesz wywołać subviewsRecursive () w dowolnym UIView:

let allSubviews = self.view.subviewsRecursive()
Alex
źródło
13

Musisz drukować rekurencyjnie, ta metoda również wyświetla karty w oparciu o głębokość widoku

-(void) printAllChildrenOfView:(UIView*) node depth:(int) d
{
    //Tabs are just for formatting
    NSString *tabs = @"";
    for (int i = 0; i < d; i++)
    {
        tabs = [tabs stringByAppendingFormat:@"\t"];
    }

    NSLog(@"%@%@", tabs, node);

    d++; //Increment the depth
    for (UIView *child in node.subviews)
    {
        [self printAllChildrenOfView:child depth:d];
    }

}
James Webster
źródło
13

Oto szybka wersja

 func listSubviewsOfView(view:UIView){

    // Get the subviews of the view
    var subviews = view.subviews

    // Return if there are no subviews
    if subviews.count == 0 {
        return
    }

    for subview : AnyObject in subviews{

        // Do what you want to do with the subview
        println(subview)

        // List the subviews of subview
        listSubviewsOfView(subview as UIView)
    }
}
Arkader
źródło
7

Trochę spóźniłem się na imprezę, ale rozwiązanie nieco bardziej ogólne:

@implementation UIView (childViews)

- (NSArray*) allSubviews {
    __block NSArray* allSubviews = [NSArray arrayWithObject:self];

    [self.subviews enumerateObjectsUsingBlock:^( UIView* view, NSUInteger idx, BOOL*stop) {
        allSubviews = [allSubviews arrayByAddingObjectsFromArray:[view allSubviews]];
                   }];
        return allSubviews;
    }

@end
Gordon Dove
źródło
6

Jeśli wszystko, czego potrzebujesz, to tablica UIViews, jest to rozwiązanie typu one liner ( Swift 4+ ):

extension UIView {
  var allSubviews: [UIView] {
    return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
  }
}
Federico Zanetello
źródło
dzięki za to rozwiązanie! Spędziłem godziny na zastanawianiu się, ale wtedy Twój post mnie uratował!
user2525211
5

Używam w ten sposób:

NSLog(@"%@", [self.view subviews]);

w UIViewController.

Grafit
źródło
4

Detale

  • Xcode 9.0.1, Swift 4
  • Xcode 10.2 (10E125), Swift 5

Rozwiązanie

extension UIView {
    private func subviews(parentView: UIView, level: Int = 0, printSubviews: Bool = false) -> [UIView] {
        var result = [UIView]()
        if level == 0 && printSubviews {
            result.append(parentView)
            print("\(parentView.viewInfo)")
        }

        for subview in parentView.subviews {
            if printSubviews { print("\(String(repeating: "-", count: level))\(subview.viewInfo)") }
            result.append(subview)
            if subview.subviews.isEmpty { continue }
            result += subviews(parentView: subview, level: level+1, printSubviews: printSubviews)
        }
        return result
    }
    private var viewInfo: String { return "\(classForCoder), frame: \(frame))" }
    var allSubviews: [UIView] { return subviews(parentView: self) }
    func printSubviews() { _ = subviews(parentView: self, printSubviews: true) }
}

Stosowanie

 view.printSubviews()
 print("\(view.allSubviews.count)")

Wynik

wprowadź opis obrazu tutaj

Wasilij Bodnarczuk
źródło
3

Na swój sposób kategoria lub rozszerzenie UIView jest znacznie lepsze niż inne, a rekurencja jest kluczowym punktem, aby uzyskać wszystkie podglądy

Ucz się więcej:

https://github.com/ZhipingYang/XYDebugView

wprowadź opis obrazu tutaj

Cel C

@implementation UIView (Recurrence)

- (NSArray<UIView *> *)recurrenceAllSubviews
{
    NSMutableArray <UIView *> *all = @[].mutableCopy;
    void (^getSubViewsBlock)(UIView *current) = ^(UIView *current){
        [all addObject:current];
        for (UIView *sub in current.subviews) {
            [all addObjectsFromArray:[sub recurrenceAllSubviews]];
        }
    };
    getSubViewsBlock(self);
    return [NSArray arrayWithArray:all];
}
@end

przykład

NSArray *views = [viewController.view recurrenceAllSubviews];

Swift 3.1

extension UIView {
    func recurrenceAllSubviews() -> [UIView] {
        var all = [UIView]()
        func getSubview(view: UIView) {
            all.append(view)
            guard view.subviews.count>0 else { return }
            view.subviews.forEach{ getSubview(view: $0) }
        }
        getSubview(view: self)
        return all
    }
}

przykład

let views = viewController.view.recurrenceAllSubviews()

bezpośrednio, użyj funkcji sekwencji, aby uzyskać wszystkie podglądy

let viewSequence = sequence(state: [viewController.view]) { (state: inout [UIView] ) -> [UIView]? in
    guard state.count > 0 else { return nil }
    defer {
        state = state.map{ $0.subviews }.flatMap{ $0 }
    }
    return state
}
let views = viewSequence.flatMap{ $0 }
Zhiping Yang
źródło
2

Przyczyną, dla której podglądy w UITableViewCell nie są drukowane, jest to, że musisz wypisywać wszystkie podglądy na najwyższym poziomie. Podglądy komórki nie są bezpośrednimi widokami podrzędnymi Twojego widoku.

Aby uzyskać widoki podrzędne UITableViewCell, musisz określić, które podwidoki należą do UITableViewCell (używając isKindOfClass:) w pętli drukowania, a następnie przejrzeć jego podglądy

Edycja: ten post na blogu dotyczący łatwego debugowania UIView może potencjalnie pomóc

Suhail Patel
źródło
2

Napisałem kategorię, aby wymienić wszystkie widoki posiadane przez kontroler widoku, zainspirowane odpowiedziami zamieszczonymi wcześniej.

@interface UIView (ListSubviewHierarchy)
- (NSString *)listOfSubviews;
@end

@implementation UIView (ListSubviewHierarchy)
- (NSInteger)depth
{
    NSInteger depth = 0;
    if ([self superview]) {
        deepth = [[self superview] depth] + 1;
    }
    return depth;
}

- (NSString *)listOfSubviews
{
    NSString * indent = @"";
    NSInteger depth = [self depth];

    for (int counter = 0; counter < depth; counter ++) {
        indent = [indent stringByAppendingString:@"  "];
    }

    __block NSString * listOfSubviews = [NSString stringWithFormat:@"\n%@%@", indent, [self description];

    if ([self.subviews count] > 0) {
        [self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            UIView * subview = obj;
            listOfSubviews = [listOfSubviews stringByAppendingFormat:@"%@", [subview listOfSubviews]];
        }];
    }
    return listOfSubviews;
}
@end

Aby wyświetlić wszystkie widoki posiadanych przez kontrolera widoku, po prostu NSLog("%@",[self listOfSubviews]), co selfoznacza, że widok kontrolera siebie. Chociaż nie jest to dość wydajne.

Dodatkowo możesz użyć NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]);do zrobienia tego samego i myślę, że jest to bardziej wydajne niż moja implementacja.

WeZZard
źródło
2

Prosty przykład języka Swift:

 var arrOfSub = self.view.subviews
 print("Number of Subviews: \(arrOfSub.count)")
 for item in arrOfSub {
    print(item)
 }
NIEWIDOCZNY
źródło
1

Możesz wypróbować fantazyjną sztuczkę tablicową, na przykład:

[self.view.subviews makeObjectsPerformSelector: @selector(printAllChildrenOfView)];

Tylko jedna linia kodu. Oczywiście może być konieczne dostosowanie metody, printAllChildrenOfViewaby nie przyjmowała żadnych parametrów ani nie tworzyła nowej metody.

johnbakers
źródło
1

Kompatybilny ze Swift 2.0

Tutaj rekurencyjna metoda uzyskiwania wszystkich podwidoków widoku ogólnego:

extension UIView {
  func subviewsList() -> [UIView] {
      var subviews = self.subviews
      if subviews.count == 0 {
          return subviews + []
      }
      for v in subviews {
         subviews += v.listSubviewsOfView()
      }
      return subviews
  }
}

Możesz więc dzwonić wszędzie w ten sposób:

let view = FooController.view
let subviews = view.subviewsList()
Luca Davanzo
źródło
1

wprowadź opis obrazu tutaj

Najkrótsze rozwiązanie

for subview in self.view.subviews {
    print(subview.dynamicType)
}

Wynik

UIView
UIView
UISlider
UISwitch
UITextField
_UILayoutGuide
_UILayoutGuide

Uwagi

  • Jak widać, ta metoda nie wyświetla rekurencyjnych widoków podrzędnych. Zobacz inne odpowiedzi na to pytanie.
Suragch
źródło
1

To przepisanie tej odpowiedzi:

Najpierw musisz pobrać wskaźnik / odniesienie do obiektu, który chcesz wydrukować, wszystkie jego podglądy. Czasami łatwiej jest znaleźć ten obiekt, uzyskując dostęp do niego za pośrednictwem jego podglądu. Lubię po someSubview.superview. To da ci coś takiego:

Optional<UIView>
   some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
  • FaceBookApp to nazwa Twojej aplikacji
  • WhatsNewView to typ Twojego plikusuperview
  • 0x7f91747c71f0 jest wskaźnikiem do superviewu.

Aby wydrukować superView, musisz użyć punktów przerwania.


Teraz, aby wykonać ten krok, wystarczy kliknąć opcję „Wyświetl hierarchię debugowania”. Nie ma potrzeby stosowania punktów przerwania

wprowadź opis obrazu tutaj

Wtedy możesz łatwo zrobić:

po [0x7f91747c71f0 recursiveDescription]

który dla mnie zwrócił coś takiego:

<FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
   | <UIStackView: 0x7f91747c75f0; frame = (45 60; 264 93); layer = <CATransformLayer: 0x610000230ec0>>
   |    | <UIImageView: 0x7f916ef38c30; frame = (10.6667 0; 243 58); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x61000003b840>>
   |    | <UIStackView: 0x7f91747c8230; frame = (44.6667 58; 174.667 35); layer = <CATransformLayer: 0x6100006278c0>>
   |    |    | <FacebookApp.CopyableUILabel: 0x7f91747a80b0; baseClass = UILabel; frame = (44 0; 86.6667 16); text = 'What's New'; gestureRecognizers = <NSArray: 0x610000c4a770>; layer = <_UILabelLayer: 0x610000085550>>
   |    |    | <FacebookApp.CopyableUILabel: 0x7f916ef396a0; baseClass = UILabel; frame = (0 21; 174.667 14); text = 'Version 14.0.5c Oct 05, 2...'; gestureRecognizers = <NSArray: 0x610000c498a0>; layer = <_UILabelLayer: 0x610000087300>>
   | <UITextView: 0x7f917015ce00; frame = (45 183; 264 403); text = '   •   new Adding new feature...'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x6100000538f0>; layer = <CALayer: 0x61000042f000>; contentOffset: {0, 0}; contentSize: {264, 890}>
   |    | <<_UITextContainerView: 0x7f9170a13350; frame = (0 0; 264 890); layer = <_UITextTiledLayer: 0x6080002c0930>> minSize = {0, 0}, maxSize = {1.7976931348623157e+308, 1.7976931348623157e+308}, textContainer = <NSTextContainer: 0x610000117b20 size = (264.000000,340282346638528859811704183484516925440.000000); widthTracksTextView = YES; heightTracksTextView = NO>; exclusionPaths = 0x61000001bc30; lineBreakMode = 0>
   |    |    | <_UITileLayer: 0x60800023f8a0> (layer)
   |    |    | <_UITileLayer: 0x60800023f3c0> (layer)
   |    |    | <_UITileLayer: 0x60800023f360> (layer)
   |    |    | <_UITileLayer: 0x60800023eca0> (layer)
   |    | <UIImageView: 0x7f9170a7d370; frame = (-39 397.667; 36 2.33333); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x60800023f4c0>>
   |    | <UIImageView: 0x7f9170a7d560; frame = (258.667 -39; 2.33333 36); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x60800023f5e0>>
   | <UIView: 0x7f916ef149c0; frame = (0 587; 354 0); layer = <CALayer: 0x6100006392a0>>
   | <UIButton: 0x7f91747a8730; frame = (0 0; 0 0); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x610000639320>>
   |    | <UIButtonLabel: 0x7f916ef00a80; frame = (0 -5.66667; 0 16); text = 'See More Details'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x610000084d80>>

jak pewnie zgadłeś, mój nadzór ma 4 podglądy:

  • a stackView (sam stackView ma obraz i inny stackView (ten stackView ma 2 etykiety własne ))
  • a textView
  • widok
  • przycisk

Jest to dla mnie całkiem nowe, ale pomogło mi debugować ramki moich widoków (oraz tekst i typ). Jeden z moich podglądów podrzędnych nie pojawił się na ekranie, więc użyłem recursiveDescription i zdałem sobie sprawę, że szerokość mojego jednego z moich podglądów podrzędnych była 0... więc poszedłem poprawić jego ograniczenia i pojawił się podwidok .

kochanie
źródło
0

Alternatywnie, jeśli chcesz zwrócić tablicę wszystkich widoków podrzędnych (i zagnieżdżonych widoków podrzędnych) z rozszerzenia UIView:

func getAllSubviewsRecursively() -> [AnyObject] {
    var allSubviews: [AnyObject] = []

    for subview in self.subviews {
        if let subview = subview as? UIView {
            allSubviews.append(subview)
            allSubviews = allSubviews + subview.getAllSubviewsRecursively()
        }
    }

    return allSubviews
}
Adam Johns
źródło
0

Wersja AC # Xamarin:

void ListSubviewsOfView(UIView view)
{
    var subviews = view.Subviews;
    if (subviews.Length == 0) return;

    foreach (var subView in subviews)
    {
        Console.WriteLine("Subview of type {0}", subView.GetType());
        ListSubviewsOfView(subView);
    }
}

Alternatywnie, jeśli chcesz znaleźć wszystkie podglądy określonego typu, których używam:

List<T> FindViews<T>(UIView view)
{
    List<T> allSubviews = new List<T>();
    var subviews = view.Subviews.Where(x =>  x.GetType() == typeof(T)).ToList();

    if (subviews.Count == 0) return allSubviews;

       foreach (var subView in subviews)
       {
            allSubviews.AddRange(FindViews<T>(subView));
        }

    return allSubviews;

}
Craig Champion
źródło
0

Zrobiłem to w kategorii, w której UIViewwystarczy wywołać funkcję przekazującą indeks, aby wydrukować je w ładnym formacie drzewa. To tylko kolejna opcja odpowiedzi zamieszczonej przez Jamesa Webstera .

#pragma mark - Views Tree

- (void)printSubviewsTreeWithIndex:(NSInteger)index
{
    if (!self)
    {
        return;
    }


    NSString *tabSpace = @"";

    @autoreleasepool
    {
        for (NSInteger x = 0; x < index; x++)
        {
            tabSpace = [tabSpace stringByAppendingString:@"\t"];
        }
    }

    NSLog(@"%@%@", tabSpace, self);

    if (!self.subviews)
    {
        return;
    }

    @autoreleasepool
    {
        for (UIView *subView in self.subviews)
        {
            [subView printViewsTreeWithIndex:index++];
        }
    }
}

Mam nadzieję, że to pomoże :)

valbu17
źródło
0
- (NSString *)recusiveDescription:(UIView *)view
{
    NSString *s = @"";
    NSArray *subviews = [view subviews];
    if ([subviews count] == 0) return @"no subviews";

    for (UIView *subView in subviews) {
         s = [NSString stringWithFormat:@"<%@; frame = (%f %f : %f %f) \n ",NSStringFromClass([subView class]), subView.frame.origin.x, subView.frame.origin.y ,subView.frame.size.width, subView.frame.size.height];  
        [self recusiveDescription:subView];
    }
    return s;
}
sarra srairi
źródło
-1

self.view.subviews zachowuje heirarchię widoków. Aby uzyskać subviews uitableviewcell, musisz zrobić coś jak poniżej.

 for (UIView *subView in self.view.subviews) {
      if ([subView isKindOfClass:[UITableView class]]) {
            for (UIView *tableSubview in subView.subviews) {
                .......
            }
      }
 }
Hitesh
źródło