46 votes

IBOutletCollection set ordering dans Interface Builder

J'utilise IBOutletCollections pour regrouper plusieurs instances d'éléments d'interface utilisateur similaires. En particulier, je regroupe un certain nombre de UIButtons (qui sont similaires aux buzzers dans un jeu de quiz) et un groupe de UILabels (qui affichent le score). Je veux m'assurer que l'étiquette située directement au-dessus du bouton met à jour le score. Je me suis dit 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 de définir l'ordre correct dans Interface Builder ?

75voto

cduhn Points 11229

EDIT : Plusieurs commentateurs ont affirmé que les versions plus récentes de Xcode retournent IBOutletCollections dans l'ordre où les connexions sont établies. 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é, alors vous trouverez peut-être que le tri explicite que je propose ci-dessous n'est plus nécessaire.


Malheureusement, il ne semble pas y avoir de moyen de contrôler l'ordre d'une IBOutletCollection dans IB, vous devrez donc trier le tableau après son chargement en fonction d'une propriété des vues. Vous pouvez trier les vues en fonction de leur tag mais la définition manuelle des balises dans IB peut être assez fastidieuse.

Heureusement, nous avons tendance à disposer 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)];
    }];
}

0 votes

C'est une bonne idée, mais vous devriez peut-être modifier le code pour comparer à x dans votre premier état if.

0 votes

Cet exemple montre seulement comment trier par la position y. Vous avez raison de dire que vous devrez modifier le comparateur si vous voulez trier par x.

3 votes

Return [[NSNumber numberWithFloat :[obj1 frame].origin.x] compare :[NSNumber numberWithFloat :[obj2 frame].origin.x]] ; - ceci va trier par x

23voto

Dabbu Points 285

J'ai suivi la réponse de cduhn et j'ai créé cette catégorie NSArray. Si maintenant xcode préserve vraiment l'ordre du design-time, ce code n'est pas vraiment nécessaire, mais si vous vous retrouvez à devoir créer/recréer de grandes collections dans IB et que vous ne voulez pas vous soucier de faire des erreurs, cela pourrait aider (au moment de l'exécution). Autre remarque : il est fort probable que l'ordre dans lequel les objets ont été ajoutés à la collection ait quelque chose à voir avec l'"Object ID" que vous trouvez dans l'onglet Identity Inspector, qui peut devenir sporadique lorsque vous modifiez l'interface et introduisez de nouveaux objets dans la collection ultérieurement.

.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

Incluez ensuite 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];
}

0 votes

Cela semble être une bonne solution si vous ne faites pas confiance à l'ordre de collecte des débouchés. Une autre option consiste à utiliser des sorties numérotées pour chaque vue (par exemple, label0, label1, etc.) et à y accéder en utilisant [self valueForKey :[NSString stringWithFormat:@"label%i", index]] ; ce qui vous permettrait de les parcourir sans écrire de code répétitif.

0 votes

Bien, je n'y avais pas pensé. L'avantage est que vous n'avez pas besoin de créer un tableau supplémentaire, il faut juste s'assurer que tous les éléments sont nommés correctement. La seule chose à prendre en compte est la fréquence d'accès à ces éléments. Les performances avec [NSString stringWithFormat :] sont plus lentes que la recherche dans un tableau prédéfini.

0 votes

Je doute que l'accès aux éléments de l'interface utilisateur soit un jour un goulot d'étranglement dans une application réelle, mais si c'était le cas, vous pourriez toujours les parcourir en boucle une fois dans viewDidLoad et placer les références dans un tableau pour un accès ultérieur.

6voto

Nick Lockwood Points 23277

Je ne sais pas exactement quand cela a changé, mais depuis Xcode 4.2 au moins, cela ne semble plus être un problème. Les IBOutletCollections préservent désormais l'ordre dans lequel les vues ont été ajoutées dans Interface Builder.

UPDATE :

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

0 votes

Êtes-vous sûr que c'est toujours le cas et que ce n'était pas accidentellement vrai. Parce que dans certains cas, l'ordre était parfait et tant que je ne changeais rien, il restait tel quel.

0 votes

Eh bien, dans tous les cas où je l'ai essayé au cours des derniers mois (c'est-à-dire plusieurs fois), l'IBOutletCollection conserve l'ordre des vues, quel que soit l'ordre dans lequel je les ai connectées dans IB, ce qui est ce que j'attendais d'elle. Je me souviens aussi vaguement que ce n'était pas le cas la première fois que je les ai essayés, il y a environ un an. Donc, soit ça ne marchait pas avant et ça marche maintenant, soit ça a toujours fonctionné de cette façon et je les ai simplement mal connectés lors de mon premier essai, puis j'ai abandonné à tort les IBOutletCollections. Quoi qu'il en soit, le système semble fonctionner correctement maintenant. Avez-vous des preuves que ce n'est pas le cas ?

0 votes

Je ne l'ai pas testé spécifiquement depuis juin dernier. Mais à l'époque, il ne fonctionnait pas du tout et mélangeait les commandes de temps en temps. Peut-être que c'est vraiment réparé. Je vais essayer.

5voto

Jim Points 39574

Pas à ce que je sache.

Comme solution de rechange, vous pourriez attribuer à chacun d'eux une étiquette, de façon séquentielle. Placez les boutons dans la gamme 100, 101, 102, etc. et les étiquettes dans la gamme 200, 201, 202, etc. Ajoutez ensuite 100 à l'étiquette du bouton pour obtenir l'étiquette de l'étiquette correspondante. Vous pouvez ensuite obtenir l'étiquette en utilisant viewForTag: .

Alternativement, vous pouvez regrouper les objets correspondants dans leurs propres UIView Vous n'avez donc qu'un seul bouton et une seule étiquette par vue.

0 votes

Je connaissais déjà la première solution. Le problème est que j'espère vraiment qu'il existe un moyen de le faire dans Interface Builder qui fonctionne de manière répétée et pas seulement avec de la chance. La raison en est que nous essayons de réaliser un projet dans lequel nous apprenons à des écolières à programmer un petit quiz en trois jours. Comme elles ne sont pas très expérimentées, la quantité de codage requise doit être limitée. La solution numéro 2 ne fonctionnera pas, car les éléments ne sont pas directement côte à côte, ou est-il possible d'avoir une grande vue qui recouvre l'autre vue sans avoir de problèmes avec la réception des événements d'entrée ?

5voto

massimobio Points 171

J'ai découvert que Xcode trie 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 ID (en vous assurant qu'ils sont uniques sinon Xcode se 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"/>

Il est utile de commencer par ordonner manuellement vos objets dans le plan du document de l'IB afin qu'ils apparaissent dans l'ordre dans le code xml.

0 votes

Merci. Cela aide avec une certaine version [expurgée] de Xcode qui introduit un bug d'ordonnancement des collections de sortie.

0 votes

Hmm. Est-ce un comportement fiable du runtime ? S'il n'est pas documenté, alors il ne fait pas partie du contrat des frameworks, et pourrait changer à l'avenir. Je n'écrirais pas une application dépendant de ce comportement.

0 votes

S'il vous plaît, ne faites JAMAIS ça. Que cela fonctionne ou non n'a pas d'importance, c'est de l'obscurcissement, cela ressemble à de la magie noire pour les autres développeurs et peut être cassé à tout moment !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X