我正在使用IBOutletCollections将多个相似的UI元素分组。特别是将一些UIButtons(类似于问答游戏中的蜂鸣器)和一组UILabels(用于显示得分)分组。我想确保直接在按钮上方的标签更新得分。我认为通过索引访问它们最容易。不幸的是,即使我按照相同的顺序添加它们,它们也不总是具有相同的索引。是否有一种方法可以在界面构建器中设置正确的排序。
我正在使用IBOutletCollections将多个相似的UI元素分组。特别是将一些UIButtons(类似于问答游戏中的蜂鸣器)和一组UILabels(用于显示得分)分组。我想确保直接在按钮上方的标签更新得分。我认为通过索引访问它们最容易。不幸的是,即使我按照相同的顺序添加它们,它们也不总是具有相同的索引。是否有一种方法可以在界面构建器中设置正确的排序。
编辑:多位评论者声称较新版本的Xcode会按照连接的顺序返回IBOutletCollections
。其他人则声称,在storyboards中这种方法不起作用。我自己没有测试过,但如果你愿意依赖未记录的行为,那么你可能会发现下面提出的显式排序已经不再必要。
很遗憾,在IB中似乎没有办法控制IBOutletCollection
的顺序,所以你需要在加载完数组后根据视图的某些属性对其进行排序。你可以根据它们的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)];
}];
}
@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中,IBOutletCollection似乎不再是一个问题。现在IBOutletCollections会保留在Interface Builder中添加视图的顺序。
更新:
我创建了一个测试项目来验证这一情况:IBOutletCollectionTest
<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代码中按顺序显示。
viewForTag:
来获取标签。或者,您可以将相应的对象分组到它们自己的UIView
中,这样每个视图只有一个按钮和一个标签。@scott-gardner 提出的扩展很好,解决了诸如未按预期顺序显示 [UIButtons] 集合等问题。下面的代码已经简单更新为 Swift 5。真正的感谢应该送给 Scott!
扩展如下:
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
}
}
}
IBOutletCollection的排序方式看起来非常随机。也许我没有正确理解Nick Lockwood的方法 - 但是我也创建了一个新项目,在视图中添加了大量UILabel,并将它们按照添加到视图中的顺序连接到集合中。
在记录日志后,我得到了一个随机的顺序。这非常令人沮丧。
我的解决方法是在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
进行排序,例如,在使用 IBOutletCollection
时非常有用。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
}
}
}
.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
使用方法(布局后)
- (void)viewDidAppear:(BOOL)animated {
_availableTextFields = [_availableTextFields sortByUIViewOrigin];
UITextField *previousField;
for (UITextField *field in _availableTextFields) {
if (previousField) {
previousField.nextTextField = field;
}
previousField = field;
}
}
我使用@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!]
}
}