IBOutletCollectionsを使用して、類似したUI要素のいくつかのインスタンスをグループ化しています。特に、多数のUIButton(クイズゲームのブザーに似ています)とUILabelsのグループ(スコアを表示)をグループ化します。ボタンの真上のラベルがスコアを更新することを確認します。インデックスでアクセスするのが最も簡単だと思った。残念ながら、同じ順序で追加しても、常に同じインデックスを持つとは限りません。正しい順序を設定する方法がInterface Builderにありますか?.
編集:いくつかのコメンターは、Xcodeのより新しいバージョンが接続が行われた順序でIBOutletCollections
を返すと主張しています。他の人たちは、このアプローチはストーリーボードではうまくいかなかったと主張しています。私自身はこれをテストしていませんが、文書化されていない動作に依存する場合は、以下で提案した明示的な並べ替えが不要であることがわかるでしょう。
残念ながら、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)];
}];
}
私はcduhnの答えで実行し、このNSArrayカテゴリを作成しました。 xcodeが設計時の順序を実際に維持する場合、このコードは実際には必要ありませんが、IBで大規模なコレクションを作成/再作成する必要があり、これを台無しにすることを心配しない場合(実行時に)役立つことがあります。また、注:オブジェクトがコレクションに追加された順序は、[IDインスペクター]タブにある[オブジェクトID]と何らかの関係があり、インターフェイスを編集して新しいオブジェクトを後でコレクション。
.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
次に、名前を付けることを選択したヘッダーファイルを含めます。コードは次のようになります。
- (void)viewDidLoad
{
[super viewDidLoad];
// Order the labels based on their y position
self.labelsArray = [self.labelsArray sortByUIViewOriginY];
}
これが正確にいつ変更されたかはわかりませんが、少なくともXcode 4.2以降では、これは問題ではなくなっているようです。 IBOutletCollectionsは、Interface Builderでビューが追加された順序を保持するようになりました。
更新:
これが事実であることを確認するためにテストプロジェクトを作成しました: IBOutletCollectionTest
私の知る限りではありません。
回避策として、それぞれにタグを順番に割り当てることができます。ボタンの範囲を100、101、102など、ラベルを200、201、202などにします。次に、ボタンのタグに100を追加して、対応するラベルのタグを取得します。次に、viewForTag:
を使用してラベルを取得できます。
または、対応するオブジェクトを独自のUIView
にグループ化して、ビューごとに1つのボタンと1つのラベルのみを使用することもできます。
Xcodeが接続のIDを使用してコレクションをアルファベット順にソートすることを発見しました。 nibファイルでバージョンエディターを開くと、IDを簡単に編集できます(IDが一意であることを確認してください。そうしないと、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コードに順番に表示されるようにすると便利です。
IBOutletCollectionの順序は非常にランダムです。 Nick Lockwoodの方法を正しく理解していないのかもしれませんが、新しいプロジェクトを作成し、UILabelの束を追加して、ビューに追加された順序でそれらをコレクションに接続しました。
ロギングした後、私はランダムな順序を得ました。とてもイライラしました。
私の回避策は、IBでタグを設定し、コレクションを次のように並べ替えることでした。
[self setResultRow1:[self sortCollection: [self resultRow1]]];
ここで、resultRow1は、約7つのラベルのIBOutletCollectionであり、IBを介してタグが設定されています。これがsortメソッドです:
-(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]
などを使用してオブジェクトにアクセスできるようになりました。これにより、要素にアクセスする必要があるたびにタグを反復処理して比較する必要があるオーバーヘッドが節約されます。
キーボードの「次へ」ボタンがつながる場所を設定するために、UITextFieldオブジェクトのコレクションにこの順序付けが必要でした(フィールドタブ)。これは国際的なアプリになるので、言語の方向を曖昧にしたかったのです。
.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によって提案された拡張機能は素晴らしく、期待どおりの順序で表示されない[UIButtons]のコレクションなどの問題を解決します。以下のコードは、Swift 5.のために単に更新されています。このために本当にスコットに感謝します!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
}
}
}
tag
で並べ替えるためにArray<UIView>
で作成した拡張機能は次のとおりです。たとえば、IBOutletCollection
sで作業するときに便利です。
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
}
}
}