IBOutletCollection set ordering in Interface Builder

Viewed 18230

I am using IBOutletCollections to group several Instances of similar UI Elements. In particular I group a number of UIButtons (which are similar to buzzers in a quiz game) and a group of UILabels (which display the score). I want to make sure that the label directly over the button updates the score. I figured that it is easiest to access them by index. Unfortunately even if I add them in the same order, they do not always have the same indexes. Is there a way in Interface Builder to set the correct ordering.

10 Answers

I ran with cduhn's answer and made this NSArray category. If now xcode really preserves the design-time order this code is not really needed, but if you find yourself having to create/recreate large collections in IB and don't want to worry about messing up this could help (at run time). Also a note: most likely the order in which the objects were added to the collection had something to do with the "Object ID" you find in the Identity Inspector tab, which can get sporadic as you edit the interface and introduce new objects to the collection at a later time.

.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

Then include the header file as you chose to name it and the code can be:

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

The extension proposed by @scott-gardner is great & solves problems such as a collection of [UIButtons] not showing in the expected order. The below code is simply updated for Swift 5. Thanks really goes to Scott for this! 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
    }
  }

}

I used the extension proposed by @scott-gardner to order Image Views in order to display counters using individual png images of dot-matrix digits. It worked like a charm in 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!]
        }
    }
Related