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:#""])
Related
My Mac app gets 2 string values from another app via scripting. Under certain conditions, the sender supplies "0-1". I need to detect this and blank the text box that displays it. The following, which only shows code for the second string, works in the debugger, but not when run outside it.
- (void)controlTextDidChange:(NSNotification *)notification
{
// there was a change in a text control
int tmpInt2 = 0;
NSMutableString *tmp2 = [NSMutableString stringWithString:[inputTextField2 stringValue]];
//NSLog(#"text box changed. value: %i", val);
if ([tmp2 length] > 3)
{
tmp2 = [NSMutableString stringWithString:[tmp2 substringToIndex:[tmp2 length] - 1]];
[inputTextField2 setStringValue:tmp2];
}
if ([tmp2 length] == 3)
{
tmpInt2 = [tmp2 intValue];
if (tmpInt2 > 360 || tmpInt2 < 0 || [tmp2 isEqualToString:#"0-1"])
{
//[self showAlert:#"Heading must be between 000 and 360"];
[inputTextField2 setStringValue:#""];
//[inputTextField2 setBackgroundColor:[NSColor yellowColor]];
[tmp2 setString:#""];
}
}
if ([[inputTextField2 stringValue] rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound)
{
NSLog(#"This is not a positive integer");
//NSMutableString *strippedString = [NSMutableString stringWithCapacity:tmp.length];
[inputTextField2 setStringValue:#""];
//[[inputTextField2 cell] setBackgroundColor:[NSColor yellowColor]];
[tmp2 setString:#""];
}
/*
if ([tmp2 isEqualToString:#"0-1"])
{
[inputTextField2 setStringValue:#""];
[tmp2 setString:#""];
}
*/
if ([tmp2 rangeOfString:#"-"].location == NSNotFound) {
NSLog(#"string does not contain 0-1");
} else {
NSLog(#"string contains 0-1!");
[inputTextField2 setStringValue:#""];
[tmp2 setString:#""];
}
}
You should look into #trojanfoe's suggestion of using NSFormatter or one of its pre-defined subclasses. However you appear to misunderstand the purpose of NSMutableString, so I offer the following version of your code with some comments embedded. The text field used for the test was given a placeholder value of "Enter Heading", and it is assumed ARC is enabled. Modern property access syntax is used (object.property). HTH.
- (void)controlTextDidChange:(NSNotification *)notification
{
// there was a change in a text control
NSTextField *inputTextField = notification.object; // get the field
NSTextFieldCell *fieldCell = inputTextField.cell; // and its cell - we use the placeholder text for feedback in this sample
fieldCell.placeholderString = #"Enter heading"; // user has typed, restore default message
NSString *contents = inputTextField.stringValue; // an NSMutableString is not required, you never mutate this string
NSUInteger length = contents.length;
if (length > 3)
{
// remove last character - did you mean to truncate to three characters?
inputTextField.stringValue = [contents substringToIndex:length - 1];
}
else if (length == 3)
{
int tmpInt = contents.intValue;
if (tmpInt > 360 || tmpInt < 0 || [contents isEqualToString:#"0-1"])
{
fieldCell.placeholderString = #"Heading must be between 000 and 360"; // inform user why field was blanked
inputTextField.stringValue = #"";
}
}
else if ([contents rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound)
{
// you might want different logic here
// if a user types "12Y" you delete everything, deleting just the "Y" might be more friendly
// ("Y" picked as an example as it could be a miss hit for the 6 or 7 keys)
fieldCell.placeholderString = #"Enter a positive integer"; // inform user why field was blanked
inputTextField.stringValue = #"";
}
}
Addendum - Comment Followup
Exactly what inputs you are expecting and what you wish to do with them is unclear. The first if just removes the last character from strings longer than 3 without doing any other checks. However I may have misinterpreted your intentions here, you have have intended to continue processing after that first if, e.g. something like:
...
if (length > 3)
{
// remove last character - did you mean to truncate to three characters?
contents = [contents substringToIndex:length - 1];
length -= 1;
}
if (length == 3)
{
...
Which means if your input is longer than 3 characters you remove the last (did you not want to simply truncate to 3? If so just change those two lines of code to do so) and then you continue with the following if/else.
I want to get the most recent word entered by the user from the UITextView.
The user can enter a word anywhere in the UITextView, in the middle or in the end or in the beginning. I would consider it a word when the user finishes typing it and presses a space and does any corrections using the "Suggestions from the UIMenuController".
Example: User types in "kimd" in the UITextView somewhere in the middle of text, he gets a popup for autocorrection "kind" which he does. After he does that, I want to capture "kind" and use it in my application.
I searched a lot on the internet but found solutions that talk about when the user enters text in the end. I also tried detecting a space and then doing a backward search until another space after some text is found, so that i can qualify it as a word. But I think there may be better ways to do this.
I have read somewhere that iOS caches the recent text that we enter in a text field or text view. If I can pop off the top one , that's all I want. I just need handle to that object.
I would really appreciate the help.
Note: The user can enter text anywhere in UItextview. I need the most recent entered word
Thanks.
//This method looks for the recent string entered by user and then takes appropriate action.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
//Look for Space or any specific string such as a space
if ([text isEqualToString:#" "]) {
NSMutableCharacterSet *workingSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
NSRange newRange = [self.myTextView.text rangeOfCharacterFromSet:workingSet
options:NSBackwardsSearch
range:NSMakeRange(0, (currentLocation - 1))];
//The below code could be done in a better way...
UITextPosition *beginning = myTextView.beginningOfDocument;
UITextPosition *start = [myTextView positionFromPosition:beginning offset:currentLocation];
UITextPosition *end = [myTextView positionFromPosition:beginning offset:newRangeLocation+1];
UITextRange *textRange = [myTextView textRangeFromPosition:end toPosition:start];
NSString* str = [self.myTextView textInRange:textRange];
}
}
Here is what I would suggest doing, might seem a little hacky but it would work just fine:
First in .h conform to the UITextViewDelegate and set your text view's delegate to self like this:
myTextView.delegate = self;
and use this code:
- (void)textViewDidChange:(UITextView *)textView { // Delegate method called when any text is modified
if ([textView.text substringFromIndex: [textView.text length] - 1]) { // Gets last character of the text view's text
NSArray *allWords = [[textView text] componentsSeparatedByString: #" "]; // Gets the text view's text and fills an array with all strings seperated by a space in text view's text, basically all the words
NSString *mostRecentWord = [allWords lastObject]; // The most recent word!
}
}
I use this code to get the word behind the #-sign:
- (void)textViewDidChange:(UITextView *)textView {
NSRange rangeOfLastInsertedCharacter = textView.selectedRange;
rangeOfLastInsertedCharacter.location = MAX(rangeOfLastInsertedCharacter.location - 1,0);
rangeOfLastInsertedCharacter.length = 1;
NSString *lastInsertedSubstring;
NSString *mentionSubString;
if (![textView.text isEqualToString:#""]) {
lastInsertedSubstring = [textView.text substringWithRange:rangeOfLastInsertedCharacter];
if (self.startOfMention > 0 || self.startOfHashtag > 0) {
if ([lastInsertedSubstring isEqualToString:#" "] || (self.startOfMention > textView.selectedRange.location || self.startOfHashtag > textView.selectedRange.location)) {
self.startOfMention = 0;
self.lenthOfMentionSubstring = 0;
}
}
if (self.startOfMention > 0) {
self.lenthOfMentionSubstring = textView.selectedRange.location - self.startOfMention;
NSRange rangeOfMentionSubstring = {self.startOfMention, textView.selectedRange.location - self.startOfMention};
mentionSubString = [textView.text substringWithRange:rangeOfMentionSubstring];
dhDebug(#"mentionSubString: %#", mentionSubString);
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
}
}
Simple extension for UITextView:
extension UITextView {
func editedWord() -> String {
let cursorPosition = selectedRange.location
let separationCharacters = NSCharacterSet(charactersInString: " ")
let beginRange = Range(start: text.startIndex.advancedBy(0), end: text.startIndex.advancedBy(cursorPosition))
let endRange = Range(start: text.startIndex.advancedBy(cursorPosition), end: text.startIndex.advancedBy(text.characters.count))
let beginPhrase = text.substringWithRange(beginRange)
let endPhrase = text.substringWithRange(endRange)
let beginWords = beginPhrase.componentsSeparatedByCharactersInSet(separationCharacters)
let endWords = endPhrase.componentsSeparatedByCharactersInSet(separationCharacters)
return beginWords.last! + endWords.first!
}
}
I have a UITextView and need to make a specific portion un-deletable. Its the first 10 characters of the views text.
I just want it so that if the user is tapping the delete key on the keyboard it simply stops when it reaches say the 10th character in.
Edit
Let me go into a bit more detail.
Let's say the prefix is '123456789:'. I want to be able to type anywhere after this prefix, it can't be editable at all though, so '123456789:' shouldn't not be altered at all. Fichek's answer does this perfectly, however the prefix isn't always there, so how can I detect when it isn't in the textview? I thought the if statement did this but it seems not to.
You can use the delegate method textView:shouldChangeTextInRange:replacementText: To tell the text view whether to accept the delete or not.
As the documentation says:
range : The current selection range. If the length of the range is 0, range reflects the current insertion point. If the user presses the Delete key, the length of the range is 1 and an empty string object replaces that single character.
Edit
Here is an implementation where the user can't delete the the first ten characters. But he will be able to insert characters there.
- (BOOL)textView:(UITextView *)textView shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (range.length==1 && string.length == 0) {
// Deleting text
if (range.location <= 9) {
return NO;
}
}
return YES;
}
Here is an implementation where he can't modify the first ten characters at all.
- (BOOL)textView:(UITextView *)textView shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (range.location <= 9) {
return NO;
}
return YES;
}
sch's last edit makes a decent answer, but I want to offer a slightly more flexible approach.
You have to keep in mind the copy/paste system. User might select all the text in text field and try to paste in the entire value which might be perfectly acceptable, but if (range.location <= 9) { return NO; } will reject it. The way I'd do it is put together a string that would be a result of successful edit and then check if that string would start with your desired prefix.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *resultString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSLog(#"resulting string would be: %#", resultString);
NSString *prefixString = #"blabla";
NSRange prefixStringRange = [resultString rangeOfString:prefixString];
if (prefixStringRange.location == 0) {
// prefix found at the beginning of result string
return YES;
}
return NO;
}
Edit: if you want to check if the current string in text field starts with the prefix, you can use rangeOfString: the same way:
NSRange prefixRange = [textField.text rangeOfString:prefixString];
if (prefixRange.location == 0) {
// prefix found at the beginning of text field
}
for a complete solution you need to handle several cases, including the cut and paste operations that may start in the uneditable part and extend into the part which the user can edit. I added a variable to control whether or not an operation that includes the uneditable part but extends into the editable part, is valid or not. If valid, the range is adjusted to only affect the editable part.
// if a nil is returned, the change is NOT allowed
- (NSString *)allowChangesToTextView:(UITextView *)textView inRange:(NSRange)changeRange withReplacementText:(NSString *)text
immutableUpTo:(NSInteger)lastReadOnlyChar adjustRangeForEdits:(BOOL)adjustRangeForEdits;
{
NSString *resultString = #"";
NSString *currentText = textView.text;
NSInteger textLength = [currentText length];
// if trying to edit the first part, possibly prevent it.
if (changeRange.location <= lastReadOnlyChar)
{
// handle typing or backspace in protected range.
if (changeRange.length <= 1)
{
return nil;
}
// handle all edits solely in protected range
if ( (changeRange.location + changeRange.length) <= lastReadOnlyChar)
{
return nil;
}
// if the user wants to completely prevent edits that extend into the
// read only substring, return no
if (!adjustRangeForEdits)
{
return nil;
}
// the range includes read only part but extends into editable part.
// adjust the range so that it does not include the read only portion.
NSInteger prevLastChar = changeRange.location + changeRange.length - 1;
NSRange newRange = NSMakeRange(lastReadOnlyChar + 1, prevLastChar - (lastReadOnlyChar + 1) + 1);
resultString = [textView.text stringByReplacingCharactersInRange:newRange withString:text];
return resultString;
}
// the range does not include the immutable part. Make the change and return the string
resultString = [currentText stringByReplacingCharactersInRange:changeRange withString:text];
return resultString;
}
and this is how it gets called from the text view delegate method:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
// did the user press enter?
if ([text isEqualToString:#"\n"])
{
[textView resignFirstResponder];
return NO;
}
NSInteger endOfReadOnlyText = [self.spotTextLastSet length] - 1;
NSString *newText = [self allowChangesToTextView:textView inRange:range withReplacementText:text
immutableUpTo:endOfReadOnlyText adjustRangeForEdits:YES];
if (newText == nil)
{
// do not allow!
[TipScreen showTipTitle:#"Information" message:#"The first part of the text is not editable. Please add your comments at the end."
ForScreen:#"editWarning"];
return NO;
}
// lets handle the edits ourselves since we got the result string.
textView.scrollEnabled = NO;
textView.text = newText;
// move the cursor to change range start + length of replacement text
NSInteger newCursorPos = range.location + [text length];
textView.selectedRange = NSMakeRange(newCursorPos, 0);
textView.scrollEnabled = YES;
return NO;
}
Goodday,
I got the following problem with this code:
-(void)textpopup:(UISegmentedControl *)sender {
int nummer = sender.tag;
if (sender.tag) {
if(sender.selectedSegmentIndex == 0 || sender.selectedSegmentIndex == 1){
beoordeling = [[UITextField alloc] init];
beoordeling.frame = CGRectMake(50 , nummer * 117 + 275 , scrollView.frame.size.width - 100 , 35);
beoordeling.autoresizingMask = (UIViewAutoresizingFlexibleWidth);
beoordeling.borderStyle = UITextBorderStyleLine;
beoordeling.tag = nummer;
[scrollView addSubview:beoordeling];
}
if(sender.selectedSegmentIndex == 2 || sender.selectedSegmentIndex == 3){
if (beoordeling.tag == sender.tag) {
[beoordeling removeFromSuperview];
}
}
}
}
i shall explain the scenario. I got some dynamic UISegmentedControls. At the moment there are 12 of them. At the first 2 segments chosen, a textfield needs to popup. This goes well. But after choosing the first 2 segments for a while and when i go to segments 2 and 3, sometimes the textfields won't remove.
I expected that the textfields which are written when i push segment 0 and 1 are removed when segment 2 and 3 are pushed.
Am i missing something?
EDIT:
At first i want to say, that i never know in advance how many UITextFields i got. When segments 0 and 1 are chosen, a UITextField needs to popup to that corresponding UISegmentedControl. And when segments 2 and 3 are chosen, the UITextField needs to stay away. But i got that fixed now in the following way.
-(void)textpopup:(UISegmentedControl *)sender {
int nummer = sender.tag;
if(sender.selectedSegmentIndex == 0 || sender.selectedSegmentIndex == 1){
// Before i add a new UITextField, the old one has to be removed.
UITextField *text = (UITextField *)[beoordeling viewWithTag:sender.tag];
[text removeFromSuperView];
beoordeling = [[UITextField alloc] init];
beoordeling.frame = CGRectMake(50 , nummer * 117 + 275 , scrollView.frame.size.width - 100 , 35);
beoordeling.autoresizingMask = (UIViewAutoresizingFlexibleWidth);
beoordeling.borderStyle = UITextBorderStyleLine;
beoordeling.tag = nummer;
[scrollView addSubview:beoordeling];
}
else if(sender.selectedSegmentIndex == 2 || sender.selectedSegmentIndex == 3) {
UITextField *tf = (UITextField *)[beoordeling viewWithTag:sender.tag];
tf.text = nil;
[tf removeFromSuperview];
}
}
The second if should be an else if, because it should execute only if the first one doesn't (the selected index can't be both 0 and 2).
What you're trying to do depends on how you've defined beoordeling. I suggest having it as an instance variable, possibly even an IBOutlet. Release in your class's dealloc. Then, in the first if clause, write
if (!beoordeling) {
beoordeling = [[UITextField alloc] init];
// Other setup
}
[scrollView addSubview:beoordeling];
I don't quite understand why you're checking for the tag, but to remove in the second if, just call removeFromSubview.
Do all your segmented controls invoke the same behavior? If you have only one beorrdeling that you're setting the tag on, then you don't need to bother checking for the tag. Just add/remove it, using removeFromSubview. If you have as many of text fields as segmented controls, maybe KVC would be what you want. If your segmented controls' tags go from 0–11, you might have beoordeling0, beoordeling1, beoordeling2, and so on. Then, to get the one you want, use something like this:
beoordeling = (UITextField *)[self valueForKey:[NSString stringWithFormat:#"beoordeling%d", sender.tag]];
Not really sure what you are trying to achieve here, but you do need to release your UITextField object beoordeling or you will leak. AddSubview adds one to the retain count, so should be safe to release straight away:
[scrollView addSubview:beoordeling];
[beoordeling release];
I have an NSTokenField which allows the user to select contacts (Just like in Mail.app). So the NSTextField is bound to an array in my model.recipient instance variable.
The user can now select an entry from the auto completion list e.g. Joe Bloggs: joe#blogs.com and as soon as he hits Enter the token (Joe Bloggs) is displayed and model.recipients now contains a BBContact entry.
Now if the user starts to type some keys (so the suggestions are shown) and then hits Tab instead of Enter the token with the value of the completion text (Joe Bloggs: joe#bloggs.com) is created and the NSTokenFieldDelegate methods did not get called, so that I could respond to this event. The model.recipient entry now contains an NSString instead of a BBContact entry.
Curiously the delegate method tokenField:shouldAddObjects:atIndex: does not get called, which is what I would expect when the user tabs out of the token field.
Pressing tab calls isValidObject on the delegate so return NO for NSTokenField in it however you want to return YES if there are no alphanumeric characters in it otherwise the user won't be able to focus away from the field (the string contains invisible unicode characters based on how many tokens exist)
The less fragile implementation i could come up with is:
- (BOOL)control:(NSControl *)control isValidObject:(id)token
{
if ([control isKindOfClass:[NSTokenField class]] && [token isKindOfClass:[NSString class]])
{
if ([token rangeOfCharacterFromSet:[NSCharacterSet alphanumericCharacterSet]].location == NSNotFound) return YES;
return NO;
}
return YES;
}
I was able to solve the problem using #valexa's suggestions. In case of a blur with TAB I have to go through all entries and look up my contact-objects for any strings.
- (BOOL)control:(NSControl *)control isValidObject:(id)token{
if ([control isKindOfClass:[NSTokenField class]] && [token isKindOfClass:[NSString class]])
{
NSTokenField *tf = (NSTokenField *)control;
if ([token rangeOfCharacterFromSet:[NSCharacterSet alphanumericCharacterSet]].location == NSNotFound){
return YES;
} else {
// We get here if the user Tabs away with an entry "pre-selected"
NSMutableArray *set = #[].mutableCopy;
for(NSObject *entry in tf.objectValue){
GSContact *c;
if([entry isKindOfClass:GSContact.class]){
c = (GSContact *)entry;
}
if([entry isKindOfClass:NSString.class]){
NSString *number = [[(NSString *)entry stringByReplacingOccurrencesOfString:#">" withString:#""]
componentsSeparatedByString:#"<"][1];
c = [self findContactByNumber:number];
}
if(c) [set addObject:c];
}
[control setObjectValue:set];
}
}
return YES;
}
This could be because the "enter" key might send the event of the token field to it's action where the "tab" key just adds text to it. You could try to set the -isContinuous property to YES and see if you get the desired results.