framework - uikit ios
¿Cómo enumerar todas las subvistas en un uiviewcontroller en iOS? (21)
Quiero enumerar todas las subvistas en un UIViewController
. Intenté self.view.subviews
, pero no todas las subvistas están enumeradas, por ejemplo, las subvistas en UITableViewCell
no se encuentran. ¿Alguna idea?
Detalles
xCode 9.0.1, Swift 4
Solución
extension UIView {
private var viewInfo: String {
return "/(classForCoder), frame: /(frame))"
}
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.count != 0 {
result += subviews(parentView: subview, level: level+1, printSubviews: printSubviews)
}
}
return result
}
var allSubviews: [UIView] {
return subviews(parentView: self)
}
func printSubviews() {
_ = subviews(parentView: self, printSubviews: true)
}
}
Uso
view.printSubviews()
print("/(view.allSubviews.count)")
Resultado
Alternativamente, si desea devolver una matriz de todas las subvistas (y subvistas anidadas) desde una extensión UIView:
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
}
Aquí está la versión rápida
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)
}
}
Debe iterar recursivamente las vistas secundarias.
- (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];
}
}
Como comentó @Greg Meletic, puede omitir la LÍNEA COUNT CHECK anterior.
Ejemplo simple de Swift:
var arrOfSub = self.view.subviews
print("Number of Subviews: /(arrOfSub.count)")
for item in arrOfSub {
print(item)
}
El motivo por el que no se imprimen las subvistas en una UITableViewCell es porque debe generar todas las subvistas en el nivel superior. Las subvistas de la celda no son las subvistas directas de su vista.
Para obtener las subvistas de UITableViewCell, debe determinar qué subvistas pertenecen a una UITableViewCell (usando isKindOfClass:
en su ciclo de impresión y luego recorrer sus subvistas
Editar: esta publicación de blog en Easy UIView Debugging podría ayudar
Elegante solución recursiva en Swift:
extension UIView {
func subviewsRecursive() -> [UIView] {
return subviews + subviews.flatMap { $0.subviewsRecursive() }
}
}
Puede llamar a subviewsRecursive () en cualquier UIView:
let allSubviews = self.view.subviewsRecursive()
En mi camino, la categoría o extensión de UIView es mucho mejor que otras y recursivo es el punto clave para obtener todas las subvistas
aprende más:
https://github.com/ZhipingYang/XYDebugView
C objetivo
@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
ejemplo
NSArray *views = [viewController.view recurrenceAllSubviews];
Swift 3.1
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
}
}
ejemplo
let views = viewController.view.recurrenceAllSubviews()
directamente, use la función de secuencia para obtener todas las subvistas
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 = Array(viewSequence).flatMap{ $0 }
Escribí una categoría para enumerar todas las vistas que tenía un controlador de vista que se inspiraba en las respuestas publicadas anteriormente.
@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
Para enumerar todas las vistas mantenidas por un controlador de vista, solo NSLog("%@",[self listOfSubviews])
, que significa el propio controlador de vista. Aunque no deja de ser eficiente.
Además, puede usar NSLog(@"/n%@", [(id)self.view performSelector:@selector(recursiveDescription)]);
hacer lo mismo, y creo que es más eficiente que mi implementación.
Esto es una reescritura de this respuesta:
Primero debe obtener el puntero / referencia al objeto que desea imprimir todas sus subvistas. A veces puede encontrar más fácil encontrar ese objeto accediéndolo a través de su subvista. Me gusta po someSubview.superview
. Esto te dará algo como:
Optional<UIView>
▿ some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
- FaceBookApp es el nombre de tu aplicación
- WhatsNewView es el tipo de su
superview
-
0x7f91747c71f0
es el puntero a la supervista.
Para imprimir la supervista, debe usar puntos de interrupción.
Ahora para hacer este paso, puede hacer clic en "ver jerarquía de depuración". No hay necesidad de puntos de interrupción
Entonces podrías hacer fácilmente:
po [0x7f91747c71f0 recursiveDescription]
que para mí me devolvió algo así como:
<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>>
como debes haber adivinado, mi supervista tiene 4 subvistas:
- un stackView (el stackView tiene una imagen y otro stackView (este stackView tiene 2 etiquetas personalizadas ))
- un textView
- una vista
- un botón
Esto es bastante nuevo para mí, pero me ha ayudado a depurar los marcos (y el texto y el tipo) de mis vistas. Una de mis subvistas no aparecía en la pantalla, así que utilicé RecursiveDescription y me di cuenta de que el ancho de mi uno de mis subView era 0
... así que fui corregido sus restricciones y apareció la subvista.
La forma incorporada de xcode / gdb para volcar la jerarquía de vista es útil - recursiveDescription, por http://developer.apple.com/library/ios/#technotes/tn2239/_index.html
Genera una jerarquía de vistas más completa que puede ser útil:
> 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>>
Llego un poco tarde a la fiesta, pero una solución un poco más general:
@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
Lo he hecho en una categoría de UIView
simplemente llame a la función que pasa el índice para imprimirlos con un buen formato de árbol. Esta es solo otra opción de la respuesta publicada por 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++];
}
}
}
Espero que ayude :)
Necesita imprimir de forma recursiva, este método también pestañas según la profundidad de la vista
-(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];
}
}
Podrías probar un truco de arreglo elegante, como:
[self.view.subviews makeObjectsPerformSelector: @selector(printAllChildrenOfView)];
Solo una linea de codigo Por supuesto, puede que necesite ajustar su método printAllChildrenOfView
para no tomar ningún parámetro o crear un nuevo método.
Si lo único que quieres es una matriz de UIView
s, esta es una solución de un trazador de líneas ( Swift 4+ ):
extension UIView {
var allSubviews: [UIView] {
return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
}
}
Versión de 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);
}
}
Alternativamente, si quiere encontrar todas las subvistas de un tipo específico, yo uso:
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;
}
Yo uso de esta manera:
NSLog(@"%@", [self.view subviews]);
en el UIViewController.
self.view.subviews mantiene la jerarquía de vistas. Para obtener subvistas de uitableviewcell debes hacer algo como a continuación.
for (UIView *subView in self.view.subviews) {
if ([subView isKindOfClass:[UITableView class]]) {
for (UIView *tableSubview in subView.subviews) {
.......
}
}
}
Compatible con Swift 2.0
Aquí un método recursivo para obtener todas las subvistas de una vista genérica:
extension UIView {
func subviewsList() -> [UIView] {
var subviews = self.subviews
if subviews.count == 0 {
return subviews + []
}
for v in subviews {
subviews += v.listSubviewsOfView()
}
return subviews
}
}
Para que pueda llamar a todas partes de esta manera:
let view = FooController.view
let subviews = view.subviewsList()