Bagaimana cara mencantumkan semua subview di uiviewcontroller di iOS?

87

Saya ingin membuat daftar semua subview di UIViewController. Saya sudah mencoba self.view.subviews, tetapi tidak semua subview dicantumkan, misalnya subview UITableViewCelltidak ditemukan. Ada ide?

pengguna403015
sumber
Anda dapat mengetahui semua subview dengan pencarian rekursif. yaitu, periksa subview memiliki subview ..
Mahesh

Jawaban:

172

Anda harus mengulang tampilan sub-tampilan secara rekursif.

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

Seperti yang dikomentari oleh @Greg Meletic, Anda dapat melewati COUNT LINE CHECK di atas.

EmptyStack
sumber
20
Secara teknis, Anda tidak perlu memeriksa apakah jumlah subview adalah 0.
Greg Maletic
5
NSLog (@ "\ n% @", [(id) self.view performSelector: @selector (recursiveDescription)]); Baris ini mencetak hasil yang sama seperti milik Anda!
Hemang
Jika Anda di sini, HARAP periksa recursiveDescriptionjawaban yang lebih sederhana dari @natbro - stackoverflow.com/a/8962824/429521
Felipe Sabino
Ini adalah peningkatan saya untuk solusi @EmptyStack. Ini menunjukkan nama kelas & koordinat bingkai yang secara rekursif diindentasi
Pierre de LESPINAY
35

Cara bawaan xcode / gdb untuk membuang hierarki tampilan berguna - recursiveDescription, sesuai http://developer.apple.com/library/ios/#technotes/tn2239/_index.html

Ini menghasilkan hierarki tampilan yang lebih lengkap yang mungkin berguna bagi Anda:

> 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
sumber
Di mana harus meletakkan po [_myToolbar recursiveDescription]di kode saya atau di tempat lain.
पवन
1
@WildPointer Dia berbicara tentang konsol. Anda memerlukan breakpoint di suatu tempat untuk melakukan ini. po = objek cetak
smileBot
1
+1 Apple merekomendasikan pendekatan ini (sesuai sesi WWDC 2013 Hidden Gems in Cocoa and Cocoa Touch, tip # 5).
Slipp D. Thompson
@ पवन lihat jawaban saya di sini . Saya menulis ulang jawaban ini.
Madu
17

Solusi rekursif yang elegan di Swift:

extension UIView {

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

}

Anda dapat memanggil subviewsRecursive () di UIView mana pun:

let allSubviews = self.view.subviewsRecursive()
Alex
sumber
13

Anda perlu mencetak secara rekursif, metode ini juga tab berdasarkan kedalaman tampilan

-(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
sumber
13

Ini adalah versi cepatnya

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

Saya agak terlambat ke pesta, tetapi solusi yang sedikit lebih umum:

@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
sumber
6

Jika yang Anda inginkan hanyalah array UIViews, ini adalah solusi satu liner ( Swift 4+ ):

extension UIView {
  var allSubviews: [UIView] {
    return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
  }
}
Federico Zanetello
sumber
terima kasih untuk solusi ini! Saya menghabiskan waktu berjam-jam untuk mencari tahu tetapi kemudian posting Anda menyelamatkan saya!
pengguna2525211
5

Saya menggunakan cara ini:

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

di UIViewController.

Grafit
sumber
4

Detail

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

Larutan

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

Pemakaian

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

Hasil

masukkan deskripsi gambar di sini

Bodnarchuk dengan mudah
sumber
3

Menurut pendapat saya, kategori atau ekstensi UIView jauh lebih baik daripada yang lain dan rekursif adalah poin kunci untuk mendapatkan semua subview.

Belajarlah lagi:

https://github.com/ZhipingYang/XYDebugView

masukkan deskripsi gambar di sini

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

contoh

NSArray *views = [viewController.view recurrenceAllSubviews];

Swift 3.1.0

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

contoh

let views = viewController.view.recurrenceAllSubviews()

secara langsung, gunakan fungsi sequence untuk mendapatkan semua subview

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
sumber
2

Alasan subview di UITableViewCell tidak dicetak adalah karena Anda harus mengeluarkan semua subview di tingkat atas. Tampilan sel bukanlah sub-tampilan langsung dari tampilan Anda.

Untuk mendapatkan subview UITableViewCell, Anda perlu menentukan subview mana yang termasuk dalam UITableViewCell (menggunakan isKindOfClass:) di loop cetak Anda dan kemudian mengulang melalui subviewnya.

Sunting: Posting blog tentang Easy UIView Debugging ini berpotensi membantu

Suhail Patel
sumber
2

Saya menulis kategori untuk mendaftar semua tampilan yang dipegang oleh pengontrol tampilan yang terinspirasi oleh jawaban yang diposting sebelumnya.

@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

Untuk mendaftar semua tampilan yang dipegang oleh pengontrol tampilan, hanya NSLog("%@",[self listOfSubviews]), yang selfberarti pengontrol tampilan itu sendiri. Padahal itu tidak berhenti efisien.

Selain itu, Anda dapat menggunakan NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]);untuk melakukan hal yang sama, dan menurut saya ini lebih efisien daripada penerapan saya.

WeZZard
sumber
2

Contoh Swift sederhana:

 var arrOfSub = self.view.subviews
 print("Number of Subviews: \(arrOfSub.count)")
 for item in arrOfSub {
    print(item)
 }
inVINCEable
sumber
1

Anda bisa mencoba trik array yang mewah, seperti:

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

Hanya satu baris kode. Tentu saja, Anda mungkin perlu menyesuaikan metode Anda printAllChildrenOfViewagar tidak mengambil parameter apa pun atau membuat metode baru.

johnbakers
sumber
1

Kompatibel dengan Swift 2.0

Di sini metode rekursif untuk mendapatkan semua subview dari tampilan generik:

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

Jadi Anda dapat menelepon ke mana saja dengan cara ini:

let view = FooController.view
let subviews = view.subviewsList()
Luca Davanzo
sumber
1

masukkan deskripsi gambar di sini

Solusi terpendek

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

Hasil

UIView
UIView
UISlider
UISwitch
UITextField
_UILayoutGuide
_UILayoutGuide

Catatan

  • Seperti yang Anda lihat, metode ini tidak mencantumkan subview secara rekursif. Lihat beberapa jawaban lain untuk itu.
Suragch
sumber
1

Ini adalah penulisan ulang dari ini jawaban :

Anda harus terlebih dahulu mendapatkan penunjuk / referensi ke objek yang ingin Anda cetak semua subviewnya. Terkadang Anda mungkin lebih mudah menemukan objek itu dengan mengaksesnya melalui subviewnya. Suka po someSubview.superview. Ini akan memberi Anda sesuatu seperti:

Optional<UIView>
  ▿ some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
  • FaceBookApp adalah nama aplikasi Anda
  • WhatsNewView adalah jenis dari Andasuperview
  • 0x7f91747c71f0 adalah penunjuk ke tampilan super.

Untuk mencetak superView, Anda harus menggunakan breakpoint.


Sekarang untuk melakukan langkah ini, Anda cukup mengklik 'lihat hierarki debug'. Tidak perlu breakpoint

masukkan deskripsi gambar di sini

Maka Anda dapat dengan mudah melakukan:

po [0x7f91747c71f0 recursiveDescription]

yang bagi saya mengembalikan sesuatu seperti:

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

seperti yang Anda tebak, superview saya memiliki 4 subview:

  • stackView (stackView itu sendiri memiliki gambar dan stackView lainnya (stackView ini memiliki 2 kustom label ))
  • sebuah textView
  • pemandangan
  • sebuah tombol

Ini cukup baru bagi saya, tetapi telah membantu saya men-debug bingkai tampilan saya (dan teks dan jenis). Salah satu subview saya tidak muncul di layar, jadi gunakan recursiveDescription dan saya menyadari lebar salah satu subview saya adalah 0... jadi saya mengoreksi batasannya dan subview muncul.

Madu
sumber
0

Atau jika Anda ingin mengembalikan array dari semua subview (dan subview bersarang) dari UIView Extension:

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
sumber
0

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

Atau, jika Anda ingin menemukan semua subview dari tipe tertentu yang saya gunakan:

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
sumber
0

Saya telah melakukannya dalam kategori UIViewpanggil saja fungsi yang melewati indeks untuk mencetaknya dengan format pohon yang bagus. Ini hanyalah opsi lain dari jawaban yang diposting oleh James Webster .

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

Saya harap ini membantu :)

valbu17
sumber
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
sumber
-1

self.view.subviews mempertahankan hierarki pandangan.Untuk mendapatkan subviews dari uitableviewcell Anda harus melakukan sesuatu seperti di bawah ini.

 for (UIView *subView in self.view.subviews) {
      if ([subView isKindOfClass:[UITableView class]]) {
            for (UIView *tableSubview in subView.subviews) {
                .......
            }
      }
 }
Hitesh
sumber