J'utilise IBOutletCollections pour regrouper plusieurs instances d'éléments d'interface utilisateur similaires. En particulier, je regroupe un certain nombre de boutons UIB (qui sont similaires aux buzzers dans un jeu de quiz) et un groupe d'étiquettes UIL (qui affichent le score). Je veux m'assurer que l'étiquette directement sur le bouton met à jour le score. J'ai pensé qu'il était 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. Existe-t-il un moyen dans Interface Builder de définir le bon ordre.
EDIT: Plusieurs commentateurs ont affirmé que les versions plus récentes de Xcode renvoient IBOutletCollections
dans l'ordre des connexions. D'autres ont affirmé 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 fier à un comportement non documenté, vous constaterez peut-être que le tri 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, vous devrez donc trier le tableau après qu'il ait été chargé en fonction d'une propriété des vues. Vous pouvez trier les vues en fonction de leur propriété tag
, mais la définition manuelle des balises dans IB peut être assez fastidieuse.
Heureusement, nous avons tendance à présenter nos vues dans l'ordre dans lequel nous voulons y accéder, il est donc souvent suffisant de trier 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 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 soucier de tout gâcher, cela pourrait aider (au moment de l'exécution). Remarque également: l'ordre dans lequel les objets ont été ajoutés à la collection a probablement quelque chose à voir avec l '"ID d'objet" que vous trouvez dans l'onglet Inspecteur d'identité, qui peut devenir sporadique lorsque vous modifiez l'interface et introduisez de nouveaux objets dans le collecte à un moment ultérieur.
.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 fichier d'en-tête comme vous avez choisi de 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 moins, cela ne semble plus être un problème. IBOutletCollections conserve désormais 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 bien le cas: IBOutletCollectionTest
Pas pour autant que je sache.
Pour contourner ce problème, vous pouvez attribuer à chacun d'eux une balise, de manière séquentielle. Placez les boutons dans la plage 100, 101, 102, etc. et les étiquettes 200, 201, 202, etc. Puis ajoutez 100 à l'étiquette du bouton pour obtenir l'étiquette de l'étiquette correspondante. Vous pouvez ensuite obtenir l'étiquette en utilisant viewForTag:
.
Vous pouvez également regrouper les objets correspondants dans leur propre UIView
, de sorte que vous n'ayez qu'un seul bouton et une étiquette par vue.
J'ai trouvé que Xcode triait la collection par ordre alphabétique en utilisant l'ID de la connexion. Si vous ouvrez l'éditeur de version sur votre fichier nib, vous pouvez facilement modifier les identifiants (en vous assurant qu'ils sont uniques, sinon Xcode plantera).
<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 commandez d'abord votre objet manuellement dans la structure du document d'IB afin qu'ils apparaissent dans l'ordre dans le code xml.
La façon dont IBOutletCollection est ordonnée semble très aléatoire. Peut-être que je ne comprends pas correctement la méthodologie de Nick Lockwood - mais j'ai également créé un nouveau projet, ajouté un tas d'étiquettes UIL et les ai connectés à une collection dans l'ordre dans lequel ils ont été ajoutés à la vue.
Après la connexion, j'ai reçu un ordre aléatoire. C'était très frustrant.
Ma solution de contournement consistait à définir des balises dans IB, puis à trier les collections comme suit:
[self setResultRow1:[self sortCollection: [self resultRow1]]];
Ici, resultRow1 est une IBOutletCollection d'environ 7 étiquettes, avec des étiquettes définies via IB. Voici la méthode de tri:
-(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;
}
Ce faisant, je peux maintenant accéder aux objets en utilisant [resultRow1 objectAtIndex: i]
ou autre. Cela évite d'avoir à parcourir et à comparer les balises chaque fois que j'ai besoin d'accéder à un élément.
J'avais besoin de cette commande pour une collection d'objets UITextField pour définir où le bouton "Suivant" sur le clavier 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 mise en page)
- (void)viewDidAppear:(BOOL)animated {
_availableTextFields = [_availableTextFields sortByUIViewOrigin];
UITextField *previousField;
for (UITextField *field in _availableTextFields) {
if (previousField) {
previousField.nextTextField = field;
}
previousField = field;
}
}
L'extension proposée par @ scott-gardner est géniale et résout des problèmes tels qu'une collection de [UIButtons] ne s'affichant pas dans l'ordre attendu. Le code ci-dessous est simplement mis à jour pour Swift 5. Merci beaucoup à Scott pour cette! 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](bit dot ly/SortUIViewsInPlaceByTag)
*/
mutating func sortUIViewsInPlaceByTag() {
sort { (left: Element, right: Element) in
left.tag < right.tag
}
}
}
Voici une extension que j'ai créée sur Array<UIView>
pour trier par tag
, par exemple, utile lorsque vous travaillez avec 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
}
}
}