Як я можу переглядати всі підпрограми UIView, а також їхні підпрограми та їхні підпрограми


82

Як я можу прокрутити всі підпрограми UIView, а також їхні підпрограми та їхні підпрограми?


7
Якщо ви дійсно ПОТРІБНІ прокручуватись, то прийнята відповідь є правильною, якщо просто шукаєте єдине представлення, то позначте його та використовуйте замість цього viewWithTag - заощадите собі біль! - developer.apple.com/library/ios/documentation/UIKit/Reference/…
Норман Г

Відповіді:


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

2
@docchang: Блоки Obj-C чудово підходять для цього випадку використання. Ви передаєте метод методу, виконуєте блок і передаєте його по ієрархії з кожним викликом методу.
Оле Бегеманн,

Приємно. Я розмістив фрагмент нижче, який додає пробіли до цього методу.
jaredsinclair

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функція має витік пам'яті : ви повинні створити масив як [[[NSMutableArray alloc] init] autorelease]або як [NSMutableArray array](що однаково).
ivanzoid

1
Під "обгорткою для UIView" ви насправді маєте на увазі "категорію для UIView".
Димитріс,

Так Димитріс, я мав на увазі категорію.
RamaKrishna Chunduri


16

Рішення в Swift 3, яке дає все, subviewsне включаючи сам вигляд:

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}"?
Рахул Патель,

@RahulPatel він видаляє nilелементи з масиву, для безпеки під час виклику allSubViewsв підпроглядах.
ієлямані


12

Щойно знайшов цікавий спосіб зробити це за допомогою налагоджувача:

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

посилається на цей Apple Technote:

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

Просто переконайтеся, що ваш налагоджувач призупинено (або встановіть точку зупинки, щоб призупинити його вручну), і ви можете попросити recursiveDescription.


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

Завдання-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

Використання категорії HierarchyLogging у вашому ViewController. Тепер ви маєте свободу у тому, що вам потрібно робити.

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

Схоже, я зробив дуже подібне рішення, як те, яке ви запропонували тут. Я продовжив і розмістив його на github на випадок, якщо іншим це може стати в нагоді. Ось моя відповідь за посиланням :) stackoverflow.com/a/10440510/553394
Ерік Голдберг

Як я можу додати спосіб зупинити рекурсію зсередини блоку? Скажімо, я використовую це для пошуку конкретного подання, як тільки я його знайшов, я хотів би зупинитися на цьому і не продовжувати пошук в решті ієрархії подання.
Mic Pringle

4

Не потрібно створювати будь-яку нову функцію. Просто робіть це під час налагодження за допомогою Xcode.

Встановіть точку зупинки в контролері перегляду та зробіть програму паузою в цій точці зупинки.

Клацніть правою кнопкою миші порожню область та натисніть "Додати вираз ..." у вікні спостереження Xcode.

Введіть цей рядок:

(NSString*)[self->_view recursiveDescription]

Якщо значення занадто довге, клацніть правою кнопкою миші та виберіть "Друк Опис ...". Ви побачите всі підпрограми self.view у вікні консолі. Змініть self -> _ view на щось інше, якщо ви не хочете бачити підпрограми self.view.

Готово! Ні ГДБ!


Ви можете пояснити, де знаходиться "порожня зона"? Я пробував усе навколо вікна Xcode, як тільки точка зупинки
спрацювала,

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

корисне рішення та економія часу .. подяка 1 плюс для вас
Brainstorm Technolabs

3

До речі, я створив проект з відкритим кодом, щоб допомогти у виконанні такого роду завдань. Це дуже просто, і використовує блоки Objective-C 2.0 для виконання коду на всіх представленнях в ієрархії.

https://github.com/egold/UIViewRecursion

Приклад:

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

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

2

Ось варіація відповіді Оле Бегемана вище, яка додає відступ для ілюстрації ієрархії:

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

Метод, наведений нижче, створює один або кілька змінних масивів, а потім циклічно переглядає підзаписи вхідного подання. Роблячи це, він додає початковий підпрогляд, а потім запитує, чи є в цьому підпрогляді будь-які підпрограми. Якщо це правда, вона знову називає себе. Він робить це доти, доки не буде додано всі погляди ієрархії.

-(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
Будь ласка, дивіться мій коментар вище.
Джед Фокс,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.