IBOutletCollection passe la command dans Interface Builder

J'utilise IBOutletCollections pour regrouper plusieurs instances d'éléments d'interface user similaires. En particulier, je regroupe un certain nombre de UIButtons (qui sont similaires aux buzzers dans un jeu-questionnaire) et un groupe de UILabels (qui affichent la partition). Je veux m'assurer que l'label directement sur le button met à jour le score. J'ai pensé qu'il est plus facile d'y accéder par index. Malheureusement, même si je les ajoute dans le même ordre, ils n'ont pas toujours les mêmes index. Interface Builder a-t-il un moyen de définir la command correcte?

EDIT: Plusieurs commentateurs ont prétendu que des versions plus récentes de Xcode returnnent IBOutletCollections dans l'ordre où les connections sont faites. D'autres ont prétendu que cette approche ne fonctionnait pas pour eux dans les storyboards. Je ne l'ai pas testé moi-même, mais si vous êtes prêt à vous appuyer sur un comportement non documenté, alors vous pouvez constater que le sorting explicite que j'ai proposé ci-dessous n'est plus nécessaire.


Malheureusement, il ne semble pas y avoir de moyen de contrôler l'ordre d'un IBOutletCollection dans IB, donc vous aurez besoin de sortinger le tableau après qu'il a été chargé en fonction de la propriété des vues. Vous pouvez sortinger les vues en fonction de leur propriété de tag , mais la définition manuelle des balises dans IB peut être plutôt fastidieuse.

Heureusement, nous avons tendance à disposer nos vues dans l'ordre où nous voulons y accéder, il est donc souvent suffisant de sortinger le tableau en fonction de la position x ou y comme ceci:

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

J'ai couru avec la réponse de Cduhn et j'ai créé cette catégorie NSArray. Si maintenant xcode préserve vraiment l'ordre au moment du design, ce code n'est pas vraiment nécessaire, mais si vous devez créer / recréer de grandes collections dans IB et que vous ne voulez pas vous tracasser, cela pourrait vous aider (au moment de l'exécution). Notez également que l'ordre dans lequel les objects ont été ajoutés à la collection a probablement quelque chose à voir avec l'identificateur d'object que vous trouvez dans l'onglet Inspecteur d'identité, qui peut devenir sporadique lorsque vous modifiez l'interface et introduisez de nouveaux objects. collection à une date ultérieure.

.h

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

.m

 @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 

Ensuite, incluez le file d'en-tête comme vous l'avez choisi pour le nommer et le code peut être:

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

Je ne sais pas quand cela a changé exactement, mais à partir de Xcode 4.2 au less, cela ne semble plus être un problème. IBOutletCollections conserve maintenant l'ordre dans lequel les vues ont été ajoutées dans Interface Builder.

METTRE À JOUR:

J'ai fait un projet de test pour vérifier que c'est le cas: IBOutletCollectionTest

J'ai trouvé que Xcode sortinge la collection par ordre alphabétique en utilisant l'identifiant de la connection. Si vous ouvrez l'éditeur de version sur votre file nib, vous pouvez facilement modifier les identifiants (en vous assurant qu'ils sont uniques, sinon Xcode va planter).

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

Cela aide si vous commandz d'abord votre object manuellement dans le plan du document de IB afin qu'ils apparaissent en séquence dans le code XML.

Pas aussi loin que je le sache.

En guise de solution de contournement, vous pouvez atsortingbuer une label à chacun d'entre eux, de manière séquentielle. Avoir les buttons vont de 100, 101, 102, etc. et les labels 200, 201, 202, etc. Puis append 100 à l'label du button pour get l'label de son label correspondante. Vous pouvez ensuite get l'label en utilisant viewForTag:

Alternativement, vous pouvez regrouper les objects correspondants dans leur propre UIView , vous n'avez donc qu'un seul button et une seule label par vue.

Il semble très random comment IBOutletCollection est commandé. Peut-être que je ne comprends pas la méthodologie de Nick Lockwood correctement – mais j'ai aussi fait un nouveau projet, ajouté un tas de UILabels, et les ai connectés à une collection dans l'ordre où ils ont été ajoutés à la vue.

Après la connection, j'ai reçu un ordre random. C'était très frustrant.

Ma solution consistait à définir des balises dans IB, puis à sortinger les collections comme suit:

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

Ici, resultRow1 est un IBOutletCollection d'environ 7 labels, avec des labels définies par IB. Voici la méthode de sorting:

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

En faisant cela, je peux maintenant accéder aux objects en utilisant [resultRow1 objectAtIndex: i] ou tel. Cela évite de devoir parcourir et comparer les labels à chaque fois que j'ai besoin d'accéder à un élément.

J'avais besoin de cette command pour une collection d'objects UITextField pour le réglage où le button "Suivant" du keyboard mènerait à (tabulation de champ). Cela va être une application internationale donc je voulais que la direction de la langue soit ambiguë.

.h

 #import <Foundation/Foundation.h> @interface NSArray (UIViewSort) - (NSArray *)sortByUIViewOrigin; @end 

.m

 #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 

Utilisation (après la layout)

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

Voici une extension que j'ai créée sur Array<UIView> pour sortinger par tag , par exemple, utile lorsque vous travaillez avec IBOutletCollection s.

 extension Array where Element: UIView { /** Sorts an array of `UIView`s or subclasss 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 } } }