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

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?

Related

NSTokenField not firing action

I have an NSTokenField to add tags to an object (a document). I would like to update the object with new tags the moment a token is added to the token field (when a tokenising character is typed). Unfortunately this does not seem to work.
The NSTokenField is connected to an action in my controller but this action method is never called.
I also have a NSTextField connected in the same way to the controller and its action method in the controller is called.
I've also tried this with key value observing:
- (void) awakeFromNib {
[tokenField addObserver:self forKeyPath:#"objectValue" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if([object isEqual:tokenField]){
NSLog(#"Tokens changed");
}
}
but this action is only called when I programatically change the tokens.
How can I be notified when the tokens in the tokenField are changed?
The NSTokenField action selector isn't called the moment a new tag is created. Depending on the setting you've gone with in Interface Builder, it's called either when you hit enter to end editing (Send On Enter Only) , or when you end editing some other way (Send On End Editing). To get the fine control you're after you'll need another approach.
The blue tags that appear when a tokenising character is added to the token field are called text attachments (instances of NSTextAttachment). One way of working out when tags are being added/removed from your token field is to track changes to the number of these objects contained in the token field's underlying attributed string.
To get access to the relevant attributed string you need to get hold of the fieldEditor's layoutManager - the object which ultimately supplies the string that appears in the text-view. Once you've got it, each time you get a controlTextDidChange: message, count up the number of text attachments in the string representation of its attributedString. If the number this time around is greater than the number recorded in the previous count, a tag has just been added.
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (weak) NSLayoutManager *lm;
#property (nonatomic) NSUInteger tokenCount;
#end
#implementation AppDelegate
// The text in the fieldEditor has changed. If the number of attachments in the
// layoutManager's attributedString has changed, either a new tag has been added,
// or an existing tag has been deleted.
-(void)controlTextDidChange:(NSNotification *)obj {
NSUInteger updatedCount = [self countAttachmentsInAttributedString:self.lm.attributedString];
if (updatedCount > self.tokenCount) {
NSLog(#"ADDED");
self.tokenCount = updatedCount;
} else if (updatedCount < self.tokenCount) {
NSLog(#"REMOVED");
self.tokenCount = updatedCount;
}
}
// About to start editing - get access to the fieldEditor's layoutManager
-(BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor {
self.lm = [(NSTextView *)fieldEditor layoutManager];
return YES;
}
// Iterate through the characters in an attributed string looking for occurrences of
// the NSAttachmentCharacter.
- (NSInteger)countAttachmentsInAttributedString:(NSAttributedString *)attributedString {
NSString *string = [attributedString string];
NSUInteger maxIndex = string.length - 1;
NSUInteger counter = 0;
for (int i = 0; i < maxIndex + 1; i++) {
if ([string characterAtIndex:i] == NSAttachmentCharacter) {
counter++;
}
}
return counter;
}
#end
A port of #paul-patterson's code to Swift 3:
override func controlTextDidChange(_ obj: Notification) {
guard let fieldEditor = self.tokenField.currentEditor() as? NSTextView,
let layoutManager = fieldEditor.layoutManager
else { return }
func countAttachments(attributedString: NSAttributedString) -> Int {
let string = attributedString.string as NSString
let maxIndex = string.length - 1
var counter = 0
for i in 0..<maxIndex {
if string.character(at: i) == unichar(NSAttachmentCharacter) {
counter += 1
}
}
return counter
}
let currentCount = countAttachments(attributedString: layoutManager.attributedString())
// cache count or act on it directly
}
Oddly enough, the following does not produce the expected outcome in Swift:
layoutManager.attributedString().string
.split(by: Character(UnicodeScalar(NSAttachmentCharacter)!)).count
Instead, it returns 0 when the user is not typing and 1 when a token is being edited.
let isEditing = layoutManager.attributedString().string
.split(by: Character(UnicodeScalar(NSAttachmentCharacter)!)).count == 1
With the combination of both approaches, you could write a custom "did add/remove token" callback using a state machine. (I don't think this is a very safe way to implement that, though.)
Track the count of tokens with countAttachments(attributedString:).
Use isEditing to check ...
if the user started adding a new note (new count > old count && isEditing == true)
if the user started editing an existing note (new count == old count && isEditing == true)
if the user finished a token (oldIsEditing == true && newIsEditing == false)

Cocoa binding: NSTextField with empty string for zero value

I have NSTextField with placeholder. And it's binded to some integer property. So I want to display empty text in the field (with placeholder shown) when binded integer is zero.
Is it possible to do it?
(Update)
I discovered that this can be done through NSNumberFormatter - it has —(void) setZeroSymbol: (NSString*) string method. Not tried yet this in practice...
You could use an NSValueTransformer.
(Just in case)Create a new class, subclass from NSValueTransformer. In the implementation, add something like this:
+(Class)transformedValueClass {
return [NSString class];
}
-(id)transformedValue:(id)value {
if (value == nil) {
return nil;
} else {
if ([value integerValue] == 0) {
return #"";
} else {
return [NSString stringWithFormat:#"%d", [value stringValue]];
}
}
}
In Interface Builder, select your field, go to the bindings tab, and in the Value Transformer drop down, either select or type in your class name you made. This should prevent you from having to worry about modifying it elsewhere. I'm not 100% positive about it showing the placeholder (I don't have a Mac available right now).
EDIT:
I can confirm that this does indeed work. Here is a link to a github project I made to show how to use it: https://github.com/macandyp/ZeroTransformer
check the integer value before binding, if you are binding at runtime. Try
int i;
if (i == 0)
{
txt.placeholder = #"text";
}
else
{
[txt setStringValue:[NSString stringWithFormat:#"%d",i]];
}
You can not do conditional binding.
You need to create another property that will hold the value based on condition and use that property and bind to textfield.
I am using bindedString and bindedInteger. bindedString is bound to text field.
Whenever some action is performed it is updated.
- (id)init{
self = [super init];
if (self) {
self.bindedString=#"place holder string";
}
return self;
}
- (IBAction)button:(id)sender {
if (self.bindedInteger==0) {
self.bindedString=#"place holder string";
}
else{
self.bindedString=[NSString stringWithFormat:#"%ld",self.bindedInteger];
}
}

How could I get the property name of the current UITextField a user is in as a string?

I am using the following method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
Before, in order to find the current text field I was in, I was able to write something like this:
if (textField == self.textPlaceID)
{
//Do something
}
Is there a way to grab the name of my textField as a string? I am not asking to take what the user has typed in that textField, I'd like to be able to just get the property name of if. I need it to then concatenate it with some other strings. I am working on making a dynamic method call.
UITextFields do not have "names". You can give your text field a tag and use that.
Also, note that (pointer == pointer) will only return true if you are referencing the same objects, not equivalent values.
Here is how to use the tag: in Interface Builder, give each text field a tag, or if you create your text fields programmatically, set textField.tag = someInt; I usually use macros to make the code more readable:
#define kNameTextField 2
#define kAddressTextField 3
...
if (textField.tag == kNameTextField) ...
With lots of fields like that, I prefer enums:
typedef enum {
kNameTextField = 2,
kAddressTextField,
kPhoneTextField // etc
} Fields;
You can get name of property (in your case textField property) using this code:
-(NSString *)propertyName:(id)property {
unsigned int numIvars = 0;
NSString *key=nil;
Ivar * ivars = class_copyIvarList([self class], &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
if ((object_getIvar(self, thisIvar) == property)) {
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
break;
}
}
free(ivars);
return key;
}
Remember to import:
#import <objc/runtime.h>
Just call:
NSLog(#"name = %#", [self propertyName:self.textField]);
source
Primarily assign unique tags to each of the textFields (eg 1,2,3.....) avoid 0 for a textField tag
-(void)viewDidLoad{
NSDictionary *dictionary ; //declare it in .h interface
dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"textPlaceID",[NSNumber numberWithInt:1], #"textPlaceID2",[NSNumber numberWithInt:2],nil]; // add textField as object and tag as keys
}
further..
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string{
NSString *textFieldPropertyName = [dictionary objectForKey:[NSNumber numberWithInt:textField.tag]];
}

Dynamically created textfield validation

I'm trying to validate dynamically created text fields. The total number of textfields may vary.
The idea is to populate the empty fields with string like player 1, player 2 etc.. Here is what I try
-(IBAction)validateTextFields:sender
{
self.howManyPlayers = 3;
int emptyFieldCounter = 1;
NSMutableArray *playersNames = [NSMutableArray arrayWithCapacity:self.howManyPlayers];
while (self.howManyPlayers > 1)
{
self.howManyPlayers--;
UITextField *tmp = (UITextField *) [self.view viewWithTag:self.howManyPlayers];
if (tmp.text == nil)
{
[tmp setText:[NSString stringWithFormat:#"Player %d", emptyFieldCounter]];
emptyFieldCounter++;
}
[playersNames addObject:tmp.text];
}
}
The problems is that if I touch the button which invoke validateTextFields method. The first and the second textfield are populated with text Player 1 and Player 2, but the third field is not populated.
I notice also that if I type a text let's say in the second field touch the button then remove the text and again touch the button that field is not populated with text Player X.
How to make all that things to work correctly ?
change your code for two lines like this:
while (self.howManyPlayers >= 1) //edited line
{
UITextField *tmp = (UITextField *) [self.view viewWithTag:self.howManyPlayers];
if (tmp.text == nil)
{
[tmp setText:[NSString stringWithFormat:#"Player %d", emptyFieldCounter]];
emptyFieldCounter++;
}
[playersNames addObject:tmp.text];
self.howManyPlayers--; // moved line
}
I forgot ur second question, so edited my answer.
For that try with this. Change if (tmp.text == nil) with if (tmp.text == nil || [tmp.txt isEqualToString:#""])
The reason only two fields are populated is that you are only going through the while loop twice. It should be
while (self.howManyPlayers >= 1)
You should also move the decrement to the end of your while loop
while (self.howManyPlayers >= 1)
{
// other code here
self.howManyPlayers--;
}
For the second part of your question, I think when you delete the text from the control, it stops being nil and now becomes an empty string. So you need to check for an empty string as well as nil in your code.
if (tmp.text == nil || [tmp.txt isEqualToString:#""])

NSTextField continuous update

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.