IBOutletCollection задает порядок в интерфейсе Builder

Я использую IBOutletCollections для группировки нескольких экземпляров аналогичных элементов интерфейса. В частности, я группирую несколько UIButtons (которые похожи на зуммеры в викторине) и группу UILabels (которые показывают оценку). Я хочу убедиться, что ярлык непосредственно над кнопкой обновляет оценку. Я понял, что доступ к ним проще всего по индексу. К сожалению, даже если я добавляю их в том же порядке, они не всегда имеют одинаковые индексы. Есть ли способ в Interface Builder установить правильное упорядочение.

Ответ 1

EDIT: Несколько комментаторов заявили, что более поздние версии Xcode возвращают IBOutletCollections в порядке, в котором сделаны соединения. Другие утверждали, что этот подход не сработал для них в раскадровки. Я не тестировал это сам, но если вы готовы полагаться на недокументированное поведение, то вы можете обнаружить, что явная сортировка, предложенная ниже, больше не нужна.


К сожалению, похоже, что нет никакого способа контролировать порядок IBOutletCollection в IB, поэтому вам нужно будет отсортировать массив после его загрузки на основе некоторого свойства представлений. Вы можете сортировать представления на основе их свойства tag, но вручную установить теги в IB может быть довольно утомительным.

К счастью, мы склонны выкладывать наши представления в том порядке, в котором хотим получить к ним доступ, поэтому часто бывает достаточно отсортировать массив на основе x или y позиции следующим образом:

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

Ответ 2

Я побежал с ответом cduhn и сделал эту категорию NSArray. Если теперь xcode действительно сохраняет порядок времени разработки, этот код действительно не нужен, но если вам нужно создавать/воссоздавать большие коллекции в IB, и вы не хотите беспокоиться о том, что это испортить, это может помочь (во время выполнения). Также примечание: скорее всего, порядок, в котором объекты были добавлены в коллекцию, имел какое-то отношение к "Идентификатору объекта", который вы находите на вкладке "Проверка идентификатора", который может стать спорадическим при редактировании интерфейса и введении новых объектов в сбор позднее.

.час

@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

Затем включите заголовочный файл, как вы его назвали, и код может быть:

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

Ответ 3

Не уверен, когда это изменилось точно, но, по крайней мере, по Xcode 4.2, это больше не кажется проблемой. Теперь IBOutletCollections сохраняют порядок, в котором представления добавлены в Interface Builder.

ОБНОВИТЬ:

Я сделал тестовый проект, чтобы убедиться, что это так: IBOutletCollectionTest

Ответ 4

Не настолько, насколько мне известно.

В качестве обходного пути вы можете последовательно назначать каждому из них тег. У вас есть кнопки 100, 101, 102 и т.д., А также метки 200, 201, 202 и т.д. Затем добавьте 100 к тегу кнопки, чтобы получить соответствующий тег метки. Затем вы можете получить ярлык с помощью viewForTag:

Кроме того, вы можете сгруппировать соответствующие объекты в свой собственный UIView, поэтому у вас есть только одна кнопка и одна метка для каждого представления.

Ответ 5

Я обнаружил, что Xcode сортирует коллекцию в алфавитном порядке, используя идентификатор соединения. Если вы откроете редактор версий вашего файла nib, вы можете легко отредактировать идентификатор (убедившись, что они уникальны, иначе произойдет сбой Xcode).

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

Это помогает, если вы сначала закажете свой объект вручную в структуре документа IB, чтобы они последовательно отображались в коде xml.

Ответ 6

Кажется очень случайным, как упорядочивается IBOutletCollection. Возможно, я неправильно понимаю методологию Ника Локвуда, но я также сделал новый проект, добавил кучу UILabels и связал их с коллекцией в том порядке, в котором они были добавлены в представление.

После регистрации я получил случайный порядок. Это было очень неприятно.

Мое обходное решение заключалось в установке тегов в IB, а затем сортировке таких коллекций:

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

Здесь resultRow1 представляет собой IBOutletCollection из примерно 7 меток с тегами, установленными через IB. Вот способ сортировки:

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

Сделав это, я теперь могу обращаться к объектам с помощью [resultRow1 objectAtIndex: i] или такого. Это экономит накладные расходы, связанные с необходимостью повторять и сравнивать теги каждый раз, когда мне нужно получить доступ к элементу.

Ответ 7

Мне понадобился этот заказ для коллекции объектов UITextField для установки, где клавиша "Далее" на клавиатуре приведет к (табуляции полей). Это будет международное приложение, поэтому я хотел, чтобы языковое направление было неоднозначным.

.час

#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

Использование (после макета)

- (void)viewDidAppear:(BOOL)animated {
    _availableTextFields = [_availableTextFields sortByUIViewOrigin];

    UITextField *previousField;
    for (UITextField *field in _availableTextFields) {
        if (previousField) {
            previousField.nextTextField = field;
        }
        previousField = field;
    }
}

Ответ 8

Здесь расширение, которое я создал на Array<UIView> сортировать по tag, например, полезно при работе с IBOutletCollection s.

extension Array where Element: UIView {

  /**
   Sorts an array of 'UIView 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 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
    }
  }

}