IBOutletCollection устанавливает порядок в Интерфейсном Разработчике

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

8 ответов

Решение

РЕДАКТИРОВАТЬ: Несколько комментаторов утверждают, что более поздние версии 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)];
    }];
}

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

.час

@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];
}

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

ОБНОВИТЬ:

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

Не настолько, насколько я знаю.

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

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

Я обнаружил, что 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.

Расширение, предложенное @scott-gardner, великолепно и решает такие проблемы, как набор [UIButtons], которые не отображаются в ожидаемом порядке. Приведенный ниже код просто обновлен для Swift 5. Большое спасибо Скотту за это! extension Массив, где Элемент: 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
    }
  }

}

Кажется очень случайным, как упорядочено 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] или такой. Это избавляет от необходимости перебирать и сравнивать теги каждый раз, когда мне нужен доступ к элементу.

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

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

}

Я использовал расширение, предложенное @scott-gardner, чтобы упорядочить просмотры изображений, чтобы отображать счетчики с использованием отдельных изображений в формате PNG с матричными цифрами. В Swift 5 это прекрасно сработало.

self.dayDigits.sortUIViewsInPlaceByTag()

func updateDayDigits(countString: String){
        for i in 0...4 {
            dayDigits[i].image = offDigitImage
        }
        let length = countString.count - 1
        for i in 0...length {
            let char = Array(countString)[length-i]
            dayDigits[i].image = digitImages[char.wholeNumberValue!]
        }
    }

Мне нужно было упорядочить коллекцию объектов 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;
    }
}
Другие вопросы по тегам