NSTextField continuous update - objective-c

I can't figure out how to get an NSTextfield to update automatically, without having to press "Return" or click another text field.
My goal is to input a number into a field and have the other fields update simultaneously. I tried clicking "Continuous" in the text field attributes but it doesn't seem to do anything.
Here is my interface file:
#import <Foundation/Foundation.h>
#interface InchController : NSObject {
IBOutlet NSTextField *centimetersTextField;
IBOutlet NSTextField *inchesTextField;
IBOutlet NSTextField *feetTextField;
}
-(IBAction)convert:(id)sender;
#end
Here is my implementation file:
#import "InchController.h"
#implementation InchController
- (IBAction)convert:(id)sender {
if (sender == inchesTextField) {
float inches = [inchesTextField floatValue];
[feetTextField setFloatValue:(inches * 0.0833)];
[centimetersTextField setFloatValue:(inches * 2.54)];
}
else if (sender == feetTextField) {
float feet = [feetTextField floatValue];
[inchesTextField setFloatValue:(feet * 12)];
[centimetersTextField setFloatValue:(feet * 30.48)];
}
else if (sender == centimetersTextField) {
float centimeters = [centimetersTextField floatValue];
[inchesTextField setFloatValue:(centimeters * 0.394)];
[feetTextField setFloatValue:(centimeters * 0.033)];
}
}
#end
So here is the updated implementation file per Josh's solution. Commented out the IBAction since it is no longer needed in the implementation and interface files.
#import "LengthController.h"
#implementation LengthController
//- (IBAction) convert: (id)sender {
//}
-(void) controlTextDidChange:(NSNotification *) note {
NSTextField *changedField = [note object];
if (changedField == inchesTextField) {
float inches = [inchesTextField floatValue];
[feetTextField setFloatValue: (inches * 0.0833)];
[centimetersTextField setFloatValue: (inches * 2.54)];
}
if (changedField == centimetersTextField) {
float centimeters = [centimetersTextField floatValue];
[inchesTextField setFloatValue:(centimeters * 0.394)];
[feetTextField setFloatValue:(centimeters * 0.033)];
}
if (changedField == feetTextField) {
float feet = [feetTextField floatValue];
[inchesTextField setFloatValue:(feet * 12)];
[centimetersTextField setFloatValue:(feet * 30.48)];
}
}
#end

Make your controller the delegate of the text fields; you can set this in Interface Builder by Ctrl-dragging from the text fields to the controller.
In your controller, implement the "NSControl Delegate" method controlTextDidChange:, which will be called (as its name suggests) whenever the field's text changes. In that method, you can validate the text and, if appropriate, update the contents of the other fields.
The argument that is passed in can give you the text field which changed; you can then pass that on to your existing convert: method to reuse the code:
- (void) controlTextDidChange: (NSNotification *)note {
NSTextField * changedField = [note object];
[self convert:changedField];
}
There's nothing special about action methods. The IBAction return type evaluates to void; it's only used by Xcode to expose the method for use in Interface Builder. You can, therefore, call them just like any other method. Here, you get the appropriate field and pass it in as the sender parameter, as if the field had called the action method itself.

Depending on the complexity of the problem, bindings may be a viable solution, too.
You can define properties on a model or model controller object and hook them up to the corresponding text fields. Then, changes in the text field are immediately reflected in the properties, which can then trigger changes to other properties.
Text fields bound to these "derived" properties are then updated automatically.
Remember to "bracket" your changes to the derived properties with willChangeValueForKey: and didChangeValueForKey: so the changes are sent to observers. More here.
Of course, if you have loops in the dependencies, it gets ugly; in that case the controlTextDidChange: method mentioned in the other answers is probably better.

Related

OBJ C - How to get the textfield name from a sender

I have several textfields, each with tags, that I want to individually add to an array. I need to figure out which field it is coming from before I add it. I would like to use the same method for all of them rather than have a method for each textfield.
Is it possible to get the variable name of the textfield from the sender? If they were button I could use [sender currentTitle], but I don't know how to get an identifier from the textfield.
I am thinking of something like this:
- (void)makeItSo:(id)sender
{
NSString * senderName = (UITextField*)[sender stringValue] ;
if ([senderName isEqual: #"name"] )
-- add name to array
else if ([senderName isEqual: #"address"] )
-- add address to array
}
If you give each text field a tag, then use the tag:
- (void)makeItSo:(UITextField *)sender {
if (sender.tag == 1) {
// the name text field
} else if (sender.tag == 2) {
// the address text field
}
}
This assumes you have set the tag property for each text field either in IB or in code.
It could be useful to define constants for each tag so you end up with something that is easier to read:
#define kNameTextField 1
#define kAddressTextField 2
- (void)makeItSo:(UITextField *)sender {
if (sender.tag == kNameTextField) {
// the name text field
} else if (sender.tag == kAddressTextField) {
// the address text field
}
}
If you have outlets or instance variables then you can do:
- (void)makeItSo:(UITextField *)sender {
if (sender == _nameTextField) {
// the name text field
} else if (sender == _addressTextField) {
// the address text field
}
}
where _nameTextField and _addressTextFields are the ivars for the text fields.
Is it possible to get the variable name of the textfield from the sender?
No, unless it's an instance variable, in which case you can, but you better don't.
I don't know how to get an identifier from the textfield
As always, it's enough to read the documentation as use the tag property of UIView:
if ([sender tag] == SOME_CUSTOM_PRESET_VALUE) {
// do stuff
}
For example you may have these text fields as ivars:
#property (weak) UITextField* textField1; // tag=1
#property (weak) UITextField* textField2; // tag=2
...
#property (weak) UITextField* textFieldN; // tag=N
When you receive an action you simply do:
- (void)makeItSo:(id)sender
{
// This is the searched text field
UITextField* textField= [self valueForKey: [NSString stringWithFormat: #"textField%d",sender.tag] ];
}
But at this point why not using a single property which is an array with N text fields, instead of N properties?

Cocos2D scoring game [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Scoring System In Cocos2D
I got a reply from a question I asked earlier but I am new to coding and have no idea how to do it. Here is the reply:
"#synthesize a "score" property of type int, and a "scoreLabel" property of type CCLabelTTF.
initialize your score property to "0" in -(void)init
On line 126, increment your "score" property by 1, and set that value into your CCLabelTTF."
Can you tell me how to do this? plz. link to my other post
----- Scoring System In Cocos2D
When you synthesize a private variable (other classes cannot see it) you allow a way for other classes to see and/or modify the value of that variable.
First, you want to create the variable:
NSMutableArray *_targets;
NSMutableArray *_projectiles;
int _score;
CCLabelTTF *_scoreLabel;
Then in your init method to set the _score to 0:
-(id) init
{
if( (self=[super init] )) {
[self schedule:#selector(update:)];
_score = 0;
Then increment (add 1 to) your _score variable and set the string (the text content) of your _scoreLabel to that value.
if (CGRectIntersectsRect(projectileRect, targetRect)) {
[targetsToDelete addObject:target];
_score++;
[_scoreLabel setString:[NSString stringWithFormat:#"%d", _score]];
}
The line [_scoreLabel setString:[NSString stringWithFormat:#"%d", _score]]; is a way to convert the integer of _score to a string (NSString). It's an old C way of doing it, the %d means that whatever is going to be there should be displayed as an integer as opposed to a float (having decimal points).
It also looks like you need to "instantiate" your label and add it as a child to the layer. Instantiation is just a fancy term for creating a instance of something. Think of a "class" as a blueprint for a chair, and an "instance" as a chair created from that blueprint. Once you have the chair created (an instance), you can modify it (paint it, add/remove legs, etc).
So, to instantiate your label and add it to the layer (itself):
-(id) init
{
if( (self=[super init] )) {
[self schedule:#selector(update:)];
_score = 0;
//Create label
_scoreLabel = [CCLabelTTF labelWithString:#"0" fontName:#"Marker Felt" fontSize:16];
//Add it to a layer (itself)
[self addChild:_scoreLabel];
Create a score property in HelloWorldLayer.h after the interface declaration, like
#property (nonatomic, retain) int score;
Then synthesize it in your .m file just after the #implementation HelloWorldLayer line.
Create methods for setting and getting scores:
-(int)getScore {
return self.score;
}
-(void)setScore:(int)newScore {
self.score = newScore;
}
In the init method, set the value of the property to zero,
if( (self=[super init] )) {
//... other stuff
[self setScore:0]
}
You can update the score with the setScore method, but I suggest having another method for this that calls setScore so that you can use it at different places with a single line call, and make any changes like assigning more score in certain situations, like two collisions within half-a-second etc..
-(void)updateScore:(int)increment {
int currentScore = [self getScore];
[self setScore:(currentScore + increment)];
}
Similarly, for label,
#property (nonatomic, retain) CCLabelTTF scoreLabel; // in header
and
#synthesize scoreLabel; // in .m file
Again, in your init method, initialize the label with position, layer and initial text etc. Then you can update that text in the updateScore method.
-(void)updateScore:(int)increment {
int currentScore = [self getScore];
[self setScore:(currentScore + increment)];
[scoreLabel setString:[NSString stringWithFormat:#"Score: %i", [self getScore]]];
}
Make sure you read through the tutorial before going ahead in order to avoid confusion regarding common tasks.

NSTextField with Number Formatter not allowing decimal input

I'm trying to make a simple Cocoa application where a number is input into a textfield. When a number is typed into one textfield the other textfield will automatically update with another number (and vice versa). The textfield that I've dragged into the window is the "Text Field with Number Formatter" from interface builder. I have the textfields set as delegate which allows them to automatically update. However, the formatting does not seem to work. I can't input decimal numbers. Please help!!!
Header file here:
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSTextField *number1;
IBOutlet NSTextField *number2;
}
#property (assign) IBOutlet NSWindow *window;
#end
Implementation file here:
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
-(void)controlTextDidChange:(NSNotification *) note {
NSTextField *changedField = [note object];
if (changedField == number1) {
float num1 = [number1 floatValue];
[number2 setFloatValue: (num1*2.0)];
}
if (changedField == number2) {
float num2 = [number2 floatValue];
[number1 setFloatValue: (num2/2.0)];
}
}
#end
This is a bit obscure, but what you are trying to do cannot really work but there is an alternative.
In your code you have:
float num1 = [number1 floatValue];
[number2 setFloatValue: (num1*2.0)];
The first line requests the current value of number1. As you have a number formatter attached to number1 it is called to format the text... and if you've entered, say, 123. at this point the formatted result is 123... and your decimal point is vanished. You'll notice you cannot enter commas (thousands separators) either.
So the fundamental problem is that after every character is typed your code is forcing it to be a valid number at that point.
The alternative is to only do your action when the user has entered a complete number. You can either write code yourself to analyze the input as each character is typed, or you can use the quick and easy method of waiting until the user hits and responding to the action.
To do the latter change your code to:
- (IBAction) textDidChange:(id)sender
{
if (sender == number1)
{
float num1 = [number1 floatValue];
[number2 setFloatValue: (num1*2.0)];
}
else if (sender == number2)
{
float num2 = [number2 floatValue];
[number1 setFloatValue: (num2/2.0)];
}
}
and connect it to the control as the "selector" and remove the "delegate" connection you already have.
This solution is less "dynamic" than what you tried, but it does work. To get the dynamic response back you'll need to write more involved code as stated above.
I know I'm very late to answer but I just ran into the exact same problem and found a nice workaround. Building upon #CRD's answer.
How I accomplished the problem is:
Store the formatter
Wipe the formatter from the textfield
read the value from the textfield
Reapply the formatter to the textfield
An example of how I did it is below:
-(void)controlTextDidChange:(NSNotification *)obj
{
if ([obj object])
{
NSTextField *field = [obj object];
NSNumberFormatter *formatter = [field formatter];
[field setFormatter:nil];
double data = [field doubleValue];
[field setFormatter:formatter];
// ...
// Do other meaningful stuff now
}
}
Not sure if it's the proper way to do it but it worked well for me.
A bit late to the party, but I just ran into this issue myself, and I think I found a concise solution that maintains dynamic updating, yet avoids the formatter kicking in too early, thus preventing the user from entering fractions:
- (void)controlTextDidChange:(NSNotification *)aNotification
{
NSTextView *fieldEditor = [aNotification.userInfo valueForKey:#"NSFieldEditor"];
CGFloat floatValue = fieldEditor.textStorage.string.floatValue;
// Getting the new value from the field editor avoids the formatter kicking in here.
// You can now use the value as you wish. Probably something like:
if (sender == number1)
{
[number2 setFloatValue: (floatValue*2.0)];
}
else if (sender == number2)
{
[number1 setFloatValue: (floatValue/2.0)];
}
}
(I typed this into the browser, so it may not be completely correct, but should be close enough. The essential parts are the first two lines: extract the field editor and extract the new value from it.

How to add a binding programmatically for a NSTabView?

My application contains an NSTabView with two tabs. Further, the application itself has a playState which is an enum. The playState is kept in a Singleton.
typedef enum {
kMyAppPlayStatePlaying,
kMyAppPlayStatePaused
} MyAppPlayState;
The playState gets synthesized here.
#property (readwrite) MyAppPlayState playState;
I want to switch the NSTabView every time the playState changes. Therefore, I prepared an IBOutlet to add a binding similar to this one.
[self.playPauseTabView bind:#"selectedItemIdentifier" toObject:[MyAppState sharedState] withKeyPath:#"playState" options:nil];
I already recognized the identifier must be NSString. This does not match with my enum which is an int. I could maybe use an NSValueTransformer to fix this.
Further, selectedItemIdentifier does not exists. NSTabView only offers selectedTabViewItem which then allows to access identifier or label. Though, I cannot find a way to switch the item itself based on the identifer.
In situations like that, I find myself doing one of two things:
1) Register self (or some other object) as an observer of the property in question, and set the selected tab accordingly in -observeValueForKeyPath:ofObject:change:context:. It could look like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ( context == PlayStateChange )
{
if ( [[change objectForKey: NSKeyValueChangeKindKey] integerValue] == NSKeyValueChangeSetting )
{
NSNumber *oldValue = [change objectForKey: NSKeyValueChangeOldKey];
NSNumber *newValue = [change objectForKey: NSKeyValueChangeNewKey];
NSInteger oldInteger = [oldValue integerValue];
NSInteger newInteger = [newValue integerValue];
NSLog(#"Old play state: %ld, new play state: %ld", (long)oldInteger, (long)newInteger);
// Do something useful with the integers here
}
return;
}
}
2) declare a readonly NSString * property and declare that its value is affected by your playState property. Something like this:
#property (readonly) NSString *playStateStr;
// Accessor
-(NSString *)playStateStr
{
return playState == kMyAppPlayStatePlaying ? #"playing" : "paused";
}
+(NSSet *)keyPathsForValuesAffectingPlayStateStr
{
return [NSSet setWithObject: #"playState"];
}
Now you have an NSString-typed property that you can bind your tab view's selection.
I forgot to connect the NSTabView with its IBOutlet in the Interface Builder.
The following works for me.
NSDictionary* playStateOptions = [NSDictionary dictionaryWithObject:[[PlayStateValueTransformer alloc] init] forKey:NSValueTransformerBindingOption];
[self.playPauseTabView bind:#"selectedLabel" toObject:[MyAppState sharedState] withKeyPath:#"playState" options:playStateOptions];
In the NSValueTransformer I return an NSString which must be set in Interface Builder for each tab!

IBOutletCollection set ordering in Interface Builder

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.
EDIT: Several commenters have claimed that more recent versions of Xcode return IBOutletCollections in the order the connections are made. Others have claimed that this approach didn't work for them in storyboards. I haven't tested this myself, but if you're willing to rely on undocumented behavior, then you may find that the explicit sorting I've proposed below is no longer necessary.
Unfortunately there doesn't seem to be any way to control the order of an IBOutletCollection in IB, so you'll need to sort the array after it's been loaded based on some property of the views. You could sort the views based on their tag property, but manually setting tags in IB can be rather tedious.
Fortunately we tend to lay out our views in the order we want to access them, so it's often sufficient to sort the array based on x or y position like this:
- (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)];
}];
}
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];
}
Not sure when this changed exactly, but as of Xcode 4.2 at least, this no longer seems to be a problem. IBOutletCollections now preserve the order in which the views were added in Interface Builder.
UPDATE:
I made a test project to verify that this is the case: IBOutletCollectionTest
Not as far as I am aware.
As a workaround, you could assign each of them a tag, sequentially. Have the buttons range 100, 101, 102, etc. and the labels 200, 201, 202, etc. Then add 100 to the button's tag to get its corresponding label's tag. You can then get the label by using viewForTag:.
Alternatively, you could group the corresponding objects into their own UIView, so you only have one button and one label per view.
I found that Xcode sorts the collection alphabetically using the ID of the connection.
If you open the version editor on your nib file you can easily edit the id's (making sure they are unique otherwise Xcode will crash).
<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"/>
It helps if you first order your object manually in the Document Outline of IB so they show up in sequence in the the xml code.
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
}
}
}
It seems very random how IBOutletCollection is ordered. Maybe I am not understanding Nick Lockwood's methodology correctly - but I as well made a new project, added a bunch of UILabels, and connected them to a collection in the order they were added to the view.
After logging, I got a random order. It was very frustrating.
My workaround was setting tags in IB and then sorting the collections like so:
[self setResultRow1:[self sortCollection: [self resultRow1]]];
Here, resultRow1 is an IBOutletCollection of about 7 labels, with tags set through IB. Here is the sort method:
-(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;
}
Doing this, I can now access objects by using [resultRow1 objectAtIndex: i] or such. This saves overhead of having to iterate through and compare tags every time I need to access an element.
I needed this ordering for a collection of UITextField objects for setting where the "Next" button on the keyboard would lead to (field tabbing). This is going to be an international app so I wanted the language direction to be ambiguous.
.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
Usage (after layout)
- (void)viewDidAppear:(BOOL)animated {
_availableTextFields = [_availableTextFields sortByUIViewOrigin];
UITextField *previousField;
for (UITextField *field in _availableTextFields) {
if (previousField) {
previousField.nextTextField = field;
}
previousField = field;
}
}
Here's an extension I created on Array<UIView> to sort by tag, e.g., useful when working w/ 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
}
}
}
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!]
}
}