UIView의 모든 하위보기, 하위보기 및 하위보기를 어떻게 반복 할 수 있습니까?
답변:
재귀 사용 :
// 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];
여기에 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;
}
}
allSubViews
함수에 메모리 누수가 있음을 유의하십시오 . 배열을 as [[[NSMutableArray alloc] init] autorelease]
또는 as [NSMutableArray array]
(동일) 로 만들어야합니다 .
다음은 또 다른 Swift 구현입니다.
extension UIView {
var allSubviews: [UIView] {
return self.subviews.flatMap { [$0] + $0.allSubviews }
}
}
subviews
뷰 자체를 포함하지 않고 모든 것을 제공하는 Swift 3의 솔루션 :
extension UIView {
var allSubViews : [UIView] {
var array = [self.subviews].flatMap {$0}
array.forEach { array.append(contentsOf: $0.allSubViews) }
return array
}
}
nil
호출 할 때 안전을 위해 배열에서 요소를 제거합니다 allSubViews
.
생성 될 때 모든 것을 태그합니다. 그러면 하위보기를 쉽게 찾을 수 있습니다.
view = [aView viewWithTag:tag];
디버거를 통해이 작업을 수행하는 흥미로운 방법을 찾았습니다.
http://idevrecipes.com/2011/02/10/exploring-iphone-view-hierarchies/
이 Apple Technote를 참조하십시오.
https://developer.apple.com/library/content/technotes/tn2239/_index.html#SECUIKIT
디버거가 일시 중지되었는지 확인하고 (중단 지점을 수동으로 설정) recursiveDescription
.
다음은 실제 뷰 루핑 및 중단 기능이있는 예입니다.
빠른:
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;
}
}];
Ole Begemann의 도움으로. 블록 개념을 통합하기 위해 몇 줄을 추가했습니다.
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];
새로운 기능을 만들 필요가 없습니다. Xcode로 디버깅 할 때만하십시오.
뷰 컨트롤러에서 중단 점을 설정하고이 중단 점에서 앱을 일시 중지합니다.
빈 영역을 마우스 오른쪽 버튼으로 클릭하고 Xcode의 Watch 창에서 "식 추가 ..."를 누릅니다.
다음 줄을 입력하십시오.
(NSString*)[self->_view recursiveDescription]
값이 너무 길면 마우스 오른쪽 단추로 클릭하고 "...에 대한 설명 인쇄"를 선택하십시오. 콘솔 창에서 self.view의 모든 하위보기를 볼 수 있습니다. self.view의 하위보기를 보지 않으려면 self-> _ view를 다른 것으로 변경하십시오.
끝난! gdb가 없습니다!
다음은 재귀 코드입니다.
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];
}
}
그건 그렇고, 나는 이런 종류의 작업을 돕기 위해 오픈 소스 프로젝트를 만들었습니다. 정말 쉽고 Objective-C 2.0 블록을 사용하여 계층 구조의 모든 뷰에서 코드를 실행합니다.
https://github.com/egold/UIViewRecursion
예:
-(void)makeAllSubviewsGreen
{
[self.view runBlockOnAllSubviews:^(UIView *view) {
view.backgroundColor = [UIColor greenColor];
}];
}
다음은 위 의 Ole Begemann의 답변 에 대한 변형이며 , 계층 구조를 설명하기 위해 들여 쓰기를 추가합니다.
// 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
재귀를 사용하는 모든 답변 (디버거 옵션 제외)이 범주를 사용했다고 생각합니다. 범주가 필요하지 않거나 원하지 않는 경우 인스턴스 메서드를 사용할 수 있습니다. 예를 들어 뷰 계층 구조의 모든 레이블 배열을 가져와야하는 경우 이렇게 할 수 있습니다.
@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
}
}
아래 메서드는 하나 이상의 가변 배열을 만든 다음 입력보기의 하위보기를 반복합니다. 이렇게하면 초기 하위보기를 추가 한 다음 해당 하위보기의 하위보기가 있는지 여부를 쿼리합니다. 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];