IBOutletCollection establece el orden en Interface Builder

Estoy utilizando IBOutletCollections para agrupar varias instancias de elementos de interfaz de usuario similares. En particular, agrupo varios UIButtons (que son similares a zumbadores en un juego de preguntas) y un grupo de UILabels (que muestran el puntaje). Quiero asegurarme de que la etiqueta directamente sobre el botón actualice el puntaje. Pensé que es más fácil acceder a ellos por índice. Desafortunadamente, incluso si los agrego en el mismo orden, no siempre tienen los mismos índices. ¿Hay alguna manera en Interface Builder para establecer el orden correcto?

EDITAR: Varios comentaristas han afirmado que las versiones más recientes de Xcode devuelven IBOutletCollections en el orden en que se realizan las conexiones. Otros han afirmado que este enfoque no funcionó para ellos en los guiones gráficos. No he probado esto por mí mismo, pero si está dispuesto a confiar en el comportamiento no documentado, entonces puede descubrir que la clasificación explícita que he propuesto a continuación ya no es necesaria.


Lamentablemente, no parece haber ninguna forma de controlar el orden de un IBOutletCollection en IB, por lo que deberá ordenar el conjunto una vez que se haya cargado en función de alguna propiedad de las vistas. Puede ordenar las vistas en función de su propiedad de tag , pero establecer manualmente las tags en IB puede ser bastante tedioso.

Afortunadamente, tendemos a diseñar nuestros puntos de vista en el orden en que deseamos acceder a ellos, por lo que a menudo basta con ordenar la matriz según la posición xey de esta manera:

 - (void)viewDidLoad { [super viewDidLoad]; // Order the labels based on their y position self.labelsArray = [self.labelsArray sortedArrayUsingComparator:^NSComparisonResult(UILabel *label1, UILabel *label2) { CGFloat label1Top = CGRectGetMinY(label1.frame); CGFloat label2Top = CGRectGetMinY(label2.frame); return [@(label1Top) compare:@(label2Top)]; }]; } 

Corrí con la respuesta de cduhn e hice esta categoría de NSArray. Si ahora xcode realmente conserva el orden de tiempo de diseño, este código no es realmente necesario, pero si tiene que crear / recrear grandes colecciones en IB y no quiere preocuparse por desordenar esto podría ayudar (en tiempo de ejecución). También una nota: lo más probable es que el orden en que se agregaron los objetos a la colección tuvo algo que ver con la “Identificación del objeto” que se encuentra en la pestaña Inspector de identidad, que puede ser esporádica a medida que edita la interfaz e introduce nuevos objetos al colección en un momento posterior.

.h

 @interface NSArray (sortBy) - (NSArray*) sortByObjectTag; - (NSArray*) sortByUIViewOriginX; - (NSArray*) sortByUIViewOriginY; @end 

.metro

 @implementation NSArray (sortBy) - (NSArray*) sortByObjectTag { return [self sortedArrayUsingComparator:^NSComparisonResult(id objA, id objB){ return( ([objA tag] < [objB tag]) ? NSOrderedAscending : ([objA tag] > [objB tag]) ? NSOrderedDescending : NSOrderedSame); }]; } - (NSArray*) sortByUIViewOriginX { return [self sortedArrayUsingComparator:^NSComparisonResult(id objA, id objB){ return( ([objA frame].origin.x < [objB frame].origin.x) ? NSOrderedAscending : ([objA frame].origin.x > [objB frame].origin.x) ? NSOrderedDescending : NSOrderedSame); }]; } - (NSArray*) sortByUIViewOriginY { return [self sortedArrayUsingComparator:^NSComparisonResult(id objA, id objB){ return( ([objA frame].origin.y < [objB frame].origin.y) ? NSOrderedAscending : ([objA frame].origin.y > [objB frame].origin.y) ? NSOrderedDescending : NSOrderedSame); }]; } @end 

A continuación, incluya el archivo de encabezado cuando elija nombrarlo y el código puede ser:

 - (void)viewDidLoad { [super viewDidLoad]; // Order the labels based on their y position self.labelsArray = [self.labelsArray sortByUIViewOriginY]; } 

No estoy seguro de cuándo esto cambió exactamente, pero a partir de Xcode 4.2 al menos, esto ya no parece ser un problema. IBOutletCollections ahora conserva el orden en que se agregaron las vistas en Interface Builder.

ACTUALIZAR:

Hice un proyecto de prueba para verificar que este es el caso: IBOutletCollectionTest

No por lo que yo sé.

Como solución, puede asignarles una etiqueta secuencialmente a cada uno de ellos. Haga que los botones tengan un rango de 100, 101, 102, etc. y las tags 200, 201, 202, etc. Luego, agregue 100 a la etiqueta del botón para obtener la etiqueta de su etiqueta correspondiente. A continuación, puede obtener la etiqueta utilizando viewForTag:

Alternativamente, puede agrupar los objetos correspondientes en su propia UIView , por lo que solo tiene un botón y una etiqueta por vista.

Descubrí que Xcode ordena la colección alfabéticamente usando la ID de la conexión. Si abre el editor de versiones en su archivo semilla, puede editar fácilmente las identificaciones (asegurándose de que sean únicas, de lo contrario Xcode se bloqueará).

         

Ayuda si primero ordena su objeto manualmente en el contorno del documento de IB para que se muestren en secuencia en el código xml.

Parece muy aleatorio cómo se ordena IBOutletCollection. Tal vez no estoy entendiendo la metodología de Nick Lockwood correctamente, pero también hice un nuevo proyecto, agregué un montón de UILabels y las conecté a una colección en el orden en que se agregaron a la vista.

Después de iniciar sesión, obtuve un orden aleatorio. Fue muy frustrante

Mi solución fue establecer tags en IB y luego ordenar las colecciones de esta manera:

 [self setResultRow1:[self sortCollection: [self resultRow1]]]; 

Aquí, resultRow1 es una IBOutletCollection de aproximadamente 7 tags, con tags establecidas a través de IB. Aquí está el método de clasificación:

 -(NSArray *)sortCollection:(NSArray *)toSort { NSArray *sortedArray; sortedArray = [toSort sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { NSNumber *tag1 = [NSNumber numberWithInt:[(UILabel*)a tag]]; NSNumber *tag2 = [NSNumber numberWithInt:[(UILabel*)b tag]]; return [tag1 compare:tag2]; }]; return sortedArray; } 

Al hacer esto, ahora puedo acceder a los objetos usando [resultRow1 objectAtIndex: i] o [resultRow1 objectAtIndex: i] . Esto ahorra gastos indirectos al tener que repetir iteraciones y comparar tags cada vez que necesito acceder a un elemento.

Necesitaba este orden para una colección de objetos UITextField para establecer a dónde conduciría el botón “Siguiente” en el teclado (tabulación de campo). Esta va a ser una aplicación internacional, así que quería que la dirección del idioma fuera ambigua.

.h

 #import  @interface NSArray (UIViewSort) - (NSArray *)sortByUIViewOrigin; @end 

.metro

 #import "NSArray+UIViewSort.h" @implementation NSArray (UIViewSort) - (NSArray *)sortByUIViewOrigin { NSLocaleLanguageDirection horizontalDirection = [NSLocale characterDirectionForLanguage:[[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode]]; NSLocaleLanguageDirection verticalDirection = [NSLocale lineDirectionForLanguage:[[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode]]; UIView *window = [[UIApplication sharedApplication] delegate].window; return [self sortedArrayUsingComparator:^NSComparisonResult(id object1, id object2) { CGPoint viewOrigin1 = [(UIView *)object1 convertPoint:((UIView *)object1).frame.origin toView:window]; CGPoint viewOrigin2 = [(UIView *)object2 convertPoint:((UIView *)object2).frame.origin toView:window]; if (viewOrigin1.y < viewOrigin2.y) { return (verticalDirection == kCFLocaleLanguageDirectionLeftToRight) ? NSOrderedDescending : NSOrderedAscending; } else if (viewOrigin1.y > viewOrigin2.y) { return (verticalDirection == kCFLocaleLanguageDirectionLeftToRight) ? NSOrderedAscending : NSOrderedDescending; } else if (viewOrigin1.x < viewOrigin2.x) { return (horizontalDirection == kCFLocaleLanguageDirectionTopToBottom) ? NSOrderedDescending : NSOrderedAscending; } else if (viewOrigin1.x > viewOrigin2.x) { return (horizontalDirection == kCFLocaleLanguageDirectionTopToBottom) ? NSOrderedAscending : NSOrderedDescending; } else return NSOrderedSame; }]; } @end 

Uso (después del diseño)

 - (void)viewDidAppear:(BOOL)animated { _availableTextFields = [_availableTextFields sortByUIViewOrigin]; UITextField *previousField; for (UITextField *field in _availableTextFields) { if (previousField) { previousField.nextTextField = field; } previousField = field; } } 

Aquí hay una extensión que creé en Array para ordenar por tag , por ejemplo, útil cuando se trabaja con IBOutletCollection s.

 extension Array where Element: UIView { /** Sorts an array of `UIView`s or subclasses by `tag`. For example, this is useful when working with `IBOutletCollection`s, whose order of elements can be changed when manipulating the view objects in Interface Builder. Just tag your views in Interface Builder and then call this method on your `IBOutletCollection`s in `viewDidLoad()`. - author: Scott Gardner - seealso: * [Source on GitHub](http://bit.ly/SortUIViewsInPlaceByTag) */ mutating func sortUIViewsInPlaceByTag() { sortInPlace { (left: Element, right: Element) in left.tag < right.tag } } }