Làm cách nào để tôi có thể lặp lại tất cả các lượt xem phụ của một UIView cũng như các lượt xem phụ và lượt xem phụ của họ


81

Làm cách nào để tôi có thể xem qua tất cả các lượt xem phụ của một UIView và các lượt xem phụ cũng như lượt xem phụ của họ?


7
Nếu bạn thực sự CẦN lặp lại thì câu trả lời được chấp nhận là đúng, nếu bạn chỉ tìm kiếm một chế độ xem duy nhất, hãy gắn thẻ nó và sử dụng viewWithTag thay thế - hãy tự tiết kiệm cho mình! - developer.apple.com/library/ios/documentation/UIKit/Reference/…
Norman H

Câu trả lời:


122

Sử dụng đệ quy:

// 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
Có cách nào để truyền một hàm trong logViewHierarchy không? Bằng cách đó, nó có thể phù hợp với nhu cầu của bạn vào những thời điểm khác nhau.
docchang

2
@docchang: Các khối obj-C rất phù hợp cho trường hợp sử dụng đó. Bạn truyền một khối cho phương thức, thực thi khối và chuyển nó xuống hệ thống phân cấp với mỗi lần gọi phương thức.
Ole Begemann

Đẹp. Tôi đã đăng một đoạn mã dưới đây bổ sung thụt lề khoảng trắng cho kỹ thuật này.
jaredsinclair

34

Đây là giải pháp của tôi bằng cách sử dụng đệ quy và trình bao bọc (danh mục / phần mở rộng) cho lớp 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

Cách sử dụng: Bây giờ bạn sẽ lặp lại tất cả các khung nhìn phụ và thao tác chúng khi cần.

//disable all text fields
for(UIView *v in [self.view allSubViews])
{
     if([v isKindOfClass:[UITextField class]])
     {
         ((UITextField*)v).enabled=NO;
     }
}

3
Lưu ý rằng có rò rỉ bộ nhớ trong allSubViewschức năng: bạn phải tạo mảng dưới dạng [[[NSMutableArray alloc] init] autorelease]hoặc dưới dạng [NSMutableArray array](giống nhau).
ivanzoid

1
Bởi "một trình bao bọc cho UIView", bạn thực sự có nghĩa là "một danh mục cho UIView".
Dimitris

Có Dimitris, ý tôi là thể loại.
RamaKrishna Chunduri

21

Đây là một triển khai Swift khác:

extension UIView {
    var allSubviews: [UIView] {
        return self.subviews.flatMap { [$0] + $0.allSubviews }
    }
}

Đơn giản và hay nhất
Balaji Ramakrishnan

16

Một giải pháp trong Swift 3 cung cấp tất cả subviewsmà không bao gồm chế độ xem:

extension UIView {
var allSubViews : [UIView] {

        var array = [self.subviews].flatMap {$0}

        array.forEach { array.append(contentsOf: $0.allSubViews) }

        return array
    }
}

Bạn cần ".flatMap {$ 0}" trong dòng "var array = [self.subviews] .flatMap {$ 0}" này là gì?
Rahul Patel

@RahulPatel nó loại bỏ nilcác phần tử khỏi một mảng, để đảm bảo an toàn khi gọi các lượt xem phụ allSubViews.
ielyamani

12

Tôi gắn thẻ mọi thứ khi nó được tạo. Sau đó, thật dễ dàng để tìm thấy bất kỳ lượt xem phụ nào.

view = [aView viewWithTag:tag];

12

Vừa tìm thấy một cách thú vị để thực hiện việc này thông qua trình gỡ lỗi:

http://idevrecipes.com/2011/02/10/exploring-iphone-view-hierarchies/

tham chiếu Apple Technote này:

https://developer.apple.com/library/content/technotes/tn2239/_index.html#SECUIKIT

Chỉ cần đảm bảo rằng trình gỡ lỗi của bạn bị tạm dừng (đặt điểm ngắt để tạm dừng nó theo cách thủ công) và bạn có thể yêu cầu recursiveDescription.


10

Đây là một ví dụ với chức năng lặp và ngắt chế độ xem thực tế.

Nhanh:

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

}

Ví dụ về cuộc gọi:

mainView.loopViewHierarchy { (view, stop) in
    if view is UIButton {
        /// use the view
        stop = true
    }
}

Vòng lặp đảo ngược:

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

Ví dụ về cuộc gọi:

mainView.loopViewHierarchyReversed { (view, stop) in
    if view is UIButton {
        /// use the view
        stop = true
    }
}

Mục tiêu-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

Ví dụ về cuộc gọi:

[mainView loopViewHierarchy:^(UIView* view, BOOL* stop) {
    if ([view isKindOfClass:[UIButton class]]) {
        /// use the view
        *stop = YES;
    }
}];

7

Với sự giúp đỡ của Ole Begemann. Tôi đã thêm một vài dòng để kết hợp khái niệm khối vào nó.

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

Sử dụng danh mục HierarchyLogging trong ViewController của bạn. Bây giờ bạn có quyền tự do với những gì bạn cần làm.

void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
    if ([view isKindOfClass:[UIButton class]]) {
        NSLog(@"%@", view);
    }
};
[self.view logViewHierarchy: ViewActionBlock];

Có vẻ như tôi đã đưa ra một giải pháp rất giống với giải pháp bạn đề xuất ở đây. Tôi đã tiếp tục và đăng nó trên github trong trường hợp những người khác có thể thấy nó hữu ích. Đây là câu trả lời của tôi w / liên kết :) stackoverflow.com/a/10440510/553394
Eric Goldberg

Làm cách nào tôi có thể thêm cách để tạm dừng đệ quy từ bên trong khối? Giả sử tôi đang sử dụng cái này để định vị một chế độ xem cụ thể, khi tôi đã tìm thấy nó, tôi muốn dừng lại ở đó và không tiếp tục tìm kiếm phần còn lại của hệ thống phân cấp chế độ xem.
Mic Pringle

4

Không cần tạo bất kỳ chức năng mới nào. Chỉ làm điều đó khi gỡ lỗi bằng Xcode.

Đặt một điểm ngắt trong bộ điều khiển chế độ xem và đặt ứng dụng tạm dừng tại điểm ngắt này.

Nhấp chuột phải vào vùng trống và nhấn "Thêm Biểu thức ..." trong cửa sổ Xem của Xcode.

Nhập dòng này:

(NSString*)[self->_view recursiveDescription]

Nếu giá trị quá dài, hãy nhấp chuột phải vào nó và chọn "In Mô tả của ...". Bạn sẽ thấy tất cả các lượt xem phụ của self.view trong cửa sổ bảng điều khiển. Thay đổi chế độ xem self -> _ sang thứ khác nếu bạn không muốn xem các lượt xem phụ của self.view.

Làm xong! Không có gdb!


bạn có thể giải thích "khu vực trống" là ở đâu không? Tôi đã thử tất cả các xung quanh cửa sổ Xcode khi breakpoint đã bị sa thải nhưng không thể tìm thấy nơi để nhập chuỗi
SundialSoft

4

Đây là một đoạn mã đệ quy: -

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

giải pháp hữu ích và tiết kiệm thời gian .. thanx 1 cộng cho u
Brainstorm Technolabs

3

Nhân tiện, tôi đã thực hiện một dự án mã nguồn mở để trợ giúp loại nhiệm vụ này. Nó thực sự dễ dàng và sử dụng các khối Objective-C 2.0 để thực thi mã trên tất cả các chế độ xem trong một hệ thống phân cấp.

https://github.com/egold/UIViewRecursion

Thí dụ:

-(void)makeAllSubviewsGreen
{
    [self.view runBlockOnAllSubviews:^(UIView *view) {

        view.backgroundColor = [UIColor greenColor];
    }];
}

2

Đây là một biến thể cho câu trả lời của Ole Begemann ở trên, nó bổ sung thêm thụt lề để minh họa hệ thống phân cấp:

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

Mã được đăng trong câu trả lời này đi qua tất cả các cửa sổ và tất cả các chế độ xem và tất cả các chế độ xem phụ của chúng. Nó được sử dụng để kết xuất bản in của hệ thống phân cấp chế độ xem sang NSLog nhưng bạn có thể sử dụng nó làm cơ sở cho bất kỳ quá trình truyền tải nào của hệ thống phân cấp chế độ xem. Nó sử dụng một hàm C đệ quy để duyệt qua cây xem.


0

Tôi đã viết một chuyên mục một thời gian trở lại để gỡ lỗi một số quan điểm.

IIRC, mã đã đăng là mã đã hoạt động. Nếu không, nó sẽ chỉ bạn đi đúng hướng. Sử dụng có rủi ro riêng, v.v.


0

Điều này cũng hiển thị mức phân cấp

@implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(int)level
{
    NSLog(@"%d - %@", level, self);
    for (UIView *subview in self.subviews)
    {
        [subview logViewHierarchy:(level+1)];
    }
}
@end

0

Ước gì tôi tìm thấy trang này trước, nhưng nếu (vì lý do nào đó) bạn muốn thực hiện điều này không đệ quy, không phải trong Danh mục và với nhiều dòng mã hơn


0

Tôi nghĩ rằng tất cả các câu trả lời bằng cách sử dụng đệ quy (ngoại trừ tùy chọn trình gỡ lỗi) đã sử dụng các danh mục. Nếu bạn không cần / muốn một danh mục, bạn chỉ có thể sử dụng một phương thức thể hiện. Ví dụ: nếu bạn cần lấy một mảng tất cả các nhãn trong hệ thống phân cấp chế độ xem của mình, bạn có thể làm điều này.

@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

Phương thức bên dưới tạo một hoặc nhiều mảng có thể thay đổi, sau đó lặp qua các lần xem con của dạng xem đầu vào. Khi làm như vậy, nó sẽ thêm lượt xem phụ ban đầu, sau đó truy vấn xem có bất kỳ lượt xem phụ nào của lượt xem phụ đó hay không. Nếu đúng, nó sẽ tự gọi lại. Nó làm như vậy cho đến khi tất cả các chế độ xem của hệ thống phân cấp đã được thêm vào.

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

Gọi bằng:

NSArray * subviews = [self allSubs:someView];

1
Vui lòng sử dụng liên kết chỉnh sửa để giải thích cách hoạt động của mã này và không chỉ cung cấp mã, vì giải thích có nhiều khả năng giúp ích cho người đọc trong tương lai. Xem thêm Cách trả lời . nguồn
Jed Fox

1
Hãy xem bình luận của tôi ở trên.
Jed Fox
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.