UIViewのすべてのサブビュー、およびそれらのサブビューとそのサブビューをループするにはどうすればよいですか?


82

UIViewのすべてのサブビュー、およびそれらのサブビューとそのサブビューをループするにはどうすればよいですか?


7
本当にループする必要がある場合は、受け入れられた答えは正しいです。単一のビューを探しているだけの場合は、タグを付けて、代わりにviewWithTagを使用してください。- developer.apple.com/library/ios/documentation/UIKit/Reference/...
ノーマン・H

回答:


122

再帰を使用する:

// 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
logViewHierarchyで関数を渡す方法はありますか?そうすれば、さまざまな時期のニーズに合わせることができます。
docchang 2011

2
@docchang:Obj-Cブロックはそのユースケースに最適です。ブロックをメソッドに渡し、ブロックを実行して、メソッド呼び出しごとに階層を下に渡します。
Ole Begemann 2011

いいね。この手法に空白のインデントを追加するスニペットを以下に投稿しました。
jaredsinclair 2013

34

これが、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

使用法:これで、すべてのサブビューをループし、必要に応じて操作する必要があります。

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

3
allSubViews関数にメモリリークがあることに注意してください。配列をas[[[NSMutableArray alloc] init] autorelease]またはas [NSMutableArray array](同じ)で作成する必要があります。
ivanzoid 2011年

1
「UIViewのラッパー」とは、実際には「UIViewのカテゴリ」を意味します。
ディミトリス2012年

はいディミトリス、私はカテゴリーを意味しました。
RamaKrishna Chunduri 2012年


16

subviewsビュー自体を含めずにすべてを提供するSwift3のソリューション:

extension UIView {
var allSubViews : [UIView] {

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

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

        return array
    }
}

この行の「.flatMap {$ 0}」の必要性は何ですか "var array = [self.subviews] .flatMap {$ 0}"?
RahulPatel18年

@RahulPatelは、サブビューをnil呼び出すときの安全のために、配列から要素を削除allSubViewsします。
ielyamani

12

作成時にすべてにタグを付けます。そうすれば、サブビューを簡単に見つけることができます。

view = [aView viewWithTag:tag];


10

これは、実際のビューのループ機能と分割機能の例です。

迅速:

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

}

呼び出し例:

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

逆ループ:

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

呼び出し例:

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

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

呼び出し例:

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

7

オレ・ベゲマンの助けを借りて。ブロックの概念を組み込むために、いくつかの行を追加しました。

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

ViewControllerでHierarchyLoggingカテゴリを使用します。これで、必要なことを自由に行うことができます。

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

あなたがここで提案したものと非常によく似た解決策を作ったようです。他の人が役立つかもしれない場合に備えて、私は先に進んでgithubに投稿しました。リンク付きの私の答えは次のとおりです:)stackoverflow.com/a/10440510/553394
Eric Goldberg

ブロック内から再帰を停止する方法を追加するにはどうすればよいですか?これを使用して特定のビューを見つけたとします。それを見つけたら、そこで停止して、ビュー階層の残りの部分の検索を続行しないようにします。
マイクプリングル2014年

4

新しい関数を作成する必要はありません。Xcodeでデバッグするときにそれを行うだけです。

ビューコントローラにブレークポイントを設定し、このブレークポイントでアプリを一時停止します。

空の領域を右クリックして、Xcodeのウォッチウィンドウで[式の追加...]を押します。

次の行を入力します:

(NSString*)[self->_view recursiveDescription]

値が長すぎる場合は、値を右クリックして[...の説明を印刷]を選択します。self.viewのすべてのサブビューがコンソールウィンドウに表示されます。self.viewのサブビューを表示したくない場合は、self-> _viewを別のものに変更してください。

完了!gdbはありません!


「空のエリア」がどこにあるか説明できますか?私は、ブレークポイントが発射したらXcodeのウィンドウの周りのすべてを試してみましたが、どこ入力する文字列を見つけることができません
SundialSoft

4

再帰コードは次のとおりです。-

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

役立つソリューションと時間の節約..uに
感謝1plus

3

ちなみに、私はこの種のタスクを支援するためにオープンソースプロジェクトを作成しました。非常に簡単で、Objective-C 2.0ブロックを使用して、階層内のすべてのビューでコードを実行します。

https://github.com/egold/UIViewRecursion

例:

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

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

2

上記のOleBegemannの回答のバリエーションを次に示します。これは、階層を示すためにインデントを追加します。

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

この回答に投稿されたコードは、すべてのウィンドウとすべてのビュー、およびそれらのすべてのサブビューをトラバースします。これは、ビュー階層のプリントアウトをNSLogにダンプするために使用されましたが、ビュー階層のトラバースの基礎として使用できます。再帰的なC関数を使用して、ビューツリーを走査します。


0

私が書いたカテゴリにいくつかのビューをデバッグするためにいくつかの時間をバックに。

IIRC、投稿されたコードは機能したものです。そうでなければ、それはあなたを正しい方向に向けます。自己責任でご使用ください。


0

これにより、階層レベルも表示されます

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

0

このページを最初に見つけたらいいのですが、(何らかの理由で)これを非再帰的に、カテゴリではなく、より多くのコード行で実行したい場合


0

再帰を使用したすべての回答(デバッガーオプションを除く)はカテゴリを使用したと思います。カテゴリが必要ない/必要ない場合は、インスタンスメソッドを使用できます。たとえば、ビュー階層内のすべてのラベルの配列を取得する必要がある場合は、これを行うことができます。

@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

以下のメソッドは、1つ以上の可変配列を作成してから、入力ビューのサブビューをループします。そうすることで、最初のサブビューを追加してから、そのサブビューのサブビューがあるかどうかを照会します。trueの場合、それ自体を再度呼び出します。これは、階層のすべてのビューが追加されるまで行われます。

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

使用して呼び出す:

NSArray * subviews = [self allSubs:someView];

1
説明は将来の読者に役立つ可能性が高いため、このコードがどのように機能するかを説明する編集リンクを使用してください。コードを提供するだけではありません。回答方法も参照してください。出典
Jed Fox

1
上記の私のコメントをご覧ください。
Jed Fox
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.