objective c - Orden de conjuntos IBOutletCollection en Interface Builder
objective-c ios (8)
Estoy usando IBOutletCollections para agrupar varias instancias de elementos UI similares. En particular, agrupo un número de UIButtons (que son similares a los buzzers en un juego de preguntas) y un grupo de UILabels (que muestran la puntuación). Quiero asegurarme de que la etiqueta directamente sobre el botón actualice la puntuación. 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 forma en Interface Builder para establecer el orden correcto?
Aquí hay una extensión que creé en Array<UIView>
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
}
}
}
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 desorden, esto podría ayudar (en tiempo de ejecución). También una nota: lo más probable es que el orden en el que se agregaron los objetos a la colección tuvo algo que ver con el "ID de objeto" que se encuentra en la pestaña Inspector de identidad, que puede ser esporádico a medida que edita la interfaz e introduce nuevos objetos en la Recolecció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
Luego incluya el archivo de encabezado como eligió para darle un nombre y el código puede ser:
- (void)viewDidLoad
{
[super viewDidLoad];
// Order the labels based on their y position
self.labelsArray = [self.labelsArray sortByUIViewOriginY];
}
Encontré que Xcode ordena la colección alfabéticamente usando el ID de la conexión. Si abre el editor de versiones en su archivo de plumilla, puede editar fácilmente los ID (asegurándose de que sean únicos, de lo contrario Xcode se bloqueará).
<outletCollection property="characterKeys" destination="QFa-Hp-9dk" id="aaa-0g-pwu"/>
<outletCollection property="characterKeys" destination="ahU-9i-wYh" id="aab-EL-hVT"/>
<outletCollection property="characterKeys" destination="Kkl-0x-mFt" id="aac-0c-Ot1"/>
<outletCollection property="characterKeys" destination="Neo-PS-Fel" id="aad-bK-O6z"/>
<outletCollection property="characterKeys" destination="AYG-dm-klF" id="aae-Qq-bam"/>
<outletCollection property="characterKeys" destination="Blz-fZ-cMU" id="aaf-lU-g7V"/>
<outletCollection property="characterKeys" destination="JCi-Hs-8Cx" id="aag-zq-6hK"/>
<outletCollection property="characterKeys" destination="DzW-qz-gFo" id="aah-yJ-wbx"/>
Ayuda si primero ordena su objeto manualmente en el Esquema del documento de IB para que aparezcan en secuencia en el código xml.
Necesitaba este pedido para una colección de objetos de 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 <Foundation/Foundation.h>
@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;
}
}
No estoy seguro de cuándo cambió exactamente esto, pero al menos de Xcode 4.2, 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 alternativa, puede asignar a cada una una etiqueta, de forma secuencial. Haga que los botones tengan un rango de 100, 101, 102, etc. y las etiquetas 200, 201, 202, etc. Luego, agregue 100 a la etiqueta del botón para obtener la etiqueta de su etiqueta correspondiente. Luego puede obtener la etiqueta usando viewForTag:
Alternativamente, puede agrupar los objetos correspondientes en su propia vista UIView
, de modo que solo tenga un botón y una etiqueta por vista.
Parece muy aleatorio cómo se ordena IBOutletCollection. Tal vez no entiendo la metodología de Nick Lockwood correctamente, pero también hice un nuevo proyecto, agregué un montón de UILabels y los conecté a una colección en el orden en que se agregaron a la vista.
Después de iniciar sesión, tengo un orden aleatorio. Fue muy frustrante.
Mi solución fue establecer etiquetas en IB y luego ordenar las colecciones así:
[self setResultRow1:[self sortCollection: [self resultRow1]]];
Aquí, resultRow1 es una colección de IBOutlet de aproximadamente 7 etiquetas, con etiquetas 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 la sobrecarga de tener que recorrer y comparar etiquetas cada vez que necesito acceder a un elemento.
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 lo he probado yo mismo, pero si está dispuesto a confiar en un comportamiento indocumentado, entonces puede encontrar 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 una IBOutletCollection
en IB, por lo que deberá ordenar la matriz después de que se haya cargado en función de alguna propiedad de las vistas. Puede ordenar las vistas según su propiedad de tag
, pero establecer etiquetas manualmente en IB puede ser bastante tedioso.
Afortunadamente, tendemos a exponer nuestras vistas en el orden en que queremos acceder a ellas, por lo que a menudo es suficiente ordenar la matriz según la posición x o y 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)];
}];
}