Formatting UILabel contents in US phone number format - ios

Note: As I've posted this question, it's night time here in my place. If I haven't responded to comments or answers, I'll do it in the morning. Thank you for your understanding :-)
The idea is to format UILabel as phone number (US Format) as the user touches each UIButton.
There is a keypad layout and as the user touches each numeric key, the label displays it and at formats the resulting string on the fly. I have written a piece of code which I am not very proud of. I hope someone could help me in doing this in much better way or different approach.
The image should give you a fair idea of how it looks like. Now, getting to the logic:
In the code below, keyOne is the IBAction to append the numbers to UILabel, keyBack is the one to delete it.
So this is how it works for entering a phone number:
If I enter 1234567890:
after 1,2,3 the label is modified as 123- and gets assigned to string
Now string will be 123- and I continue touching 4567890
after 123-4567, when I touch 8, label is modified as (123)456-78 and gets assigned to string
Now string will be (123)456-7890
If I wish, I could continue entering more numbers.
As per requirement, the number should lose its formatting.
i.e. After (123)456-7890 if I press 1, it should become 12345678901
Now, the trick is to undo the steps in exact same way. Here is the pickle.
If I entered only 1234567890, _phoneNumber.text would be (123)456-7890 and pressing back three times should result in 123-4567. Then pressing back four times should result in 123
However, if I entered 12345678901, _phoneNumber.text would go to (123)456-7890, then lose formatting to become 12345678901 and when I press back once, it should become (123)456-7890 and so on.
If you whip up a new project of type single view application and paste the stuff below, (of course after creating buttons and connecting 0-9 with keyOne, back with keyBack) you would see that it works fine. But as I mentioned above, I have written a piece of code which I am not very proud of. I feel there should be a simpler way to do this.
ViewController.h
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *lengthParam;
#property (weak, nonatomic) IBOutlet UILabel *phoneNumber;
- (IBAction)keyOne:(id)sender;
- (IBAction)keyBack:(id)sender;
- (IBAction)clearAll:(id)sender;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController () {
BOOL isReformatted;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (IBAction)keyOne:(id)sender {
UIButton *btn = (UIButton *)sender;
NSString *str = [NSString stringWithString:self.phoneNumber.text];
str = [str stringByAppendingString:btn.titleLabel.text];
self.phoneNumber.text = str;
if(str.length==4) self.phoneNumber.text = [self insertMinus:str]; //before the 4th number, insert '-' as a first step
if(str.length==9) self.phoneNumber.text = [self insertParenthesis:str]; //remove - before the 4th number, encapsulate first 3 numbers inside () and add - at end of current number
if(str.length>13) self.phoneNumber.text = [self plainFormat:str]; //if user enter more than 10 numbers, remove formatting
self.lengthParam.text = [NSString stringWithFormat:#"%d",self.phoneNumber.text.length];
}
- (IBAction)keyBack:(id)sender {
NSString *str = [NSString stringWithString:self.phoneNumber.text];
NSString *newStr = nil;
if(str.length>0) { //check for empty string
if(str.length>11 && isReformatted==NO) newStr = [str substringToIndex:str.length-1]; //string.length > 10 which means plainFormat was called earlier and reFormat isn't called yet
else if(str.length==11 && isReformatted == NO) { //string length is now 11
if([str characterAtIndex:0]!='(') newStr = [self reFormat:str]; //if entered string is 12345678901, it is not reFormatted. remove last 1 and reFormat it as (123)456-7890
else newStr = [self removeParenthesis:str]; //entered string itself is (123)456-78 so transform it as 123-4567
} else { //we are dealing with a reformatted string of length 11 now.
newStr = [str substringToIndex:str.length-1]; //String is (123)456-78, remove 8 and apply one of the below rules
if(newStr.length==10) { //transform (123)456-7 as 123-4567
newStr = [str substringToIndex:str.length-1];
newStr = [self removeParenthesis:str];
}
if(newStr.length==4&&[newStr characterAtIndex:3]=='-') newStr = [self removeMinus:str]; //transform 123-4 to 123
}
self.phoneNumber.text = newStr;
self.lengthParam.text = [NSString stringWithFormat:#"%d",self.phoneNumber.text.length];
}
}
- (IBAction)clearAll:(id)sender {
self.phoneNumber.text = #"";
self.lengthParam.text = [NSString stringWithFormat:#"%d",self.phoneNumber.text.length];
}
- (NSString *)insertMinus:(NSString *)str {
return [NSString stringWithFormat:#"%#-%#",[str substringToIndex:3],[str substringFromIndex:3]];
}
- (NSString *)insertParenthesis:(NSString *)str {
NSString *c1 = [NSString stringWithFormat:#"(%#)",[str substringToIndex:3]];
NSString *c2 = [NSString stringWithFormat:#"%#-%#",[str substringWithRange:NSMakeRange(4, 3)],[str substringFromIndex:7]];
return [NSString stringWithFormat:#"%#%#",c1,c2];
}
- (NSString *)plainFormat:(NSString *)str {
isReformatted = NO;
str = [str stringByReplacingOccurrencesOfString:#"(" withString:#""];
str = [str stringByReplacingOccurrencesOfString:#")" withString:#""];
str = [str stringByReplacingOccurrencesOfString:#"-" withString:#""];
str = [str stringByReplacingOccurrencesOfString:#" " withString:#""];
return str;
}
- (NSString *)reFormat:(NSString *)str {
isReformatted = YES;
NSString *c1 = [NSString stringWithFormat:#"(%#)",[str substringToIndex:3]];
NSString *c2 = [NSString stringWithFormat:#"%#-%#",[str substringWithRange:NSMakeRange(3, 3)],[str substringWithRange:NSMakeRange(6, 4)]];
return [NSString stringWithFormat:#"%#%#",c1,c2];
}
- (NSString *)removeParenthesis:(NSString *)str {
str = [self plainFormat:str];
NSString *newStr = [NSString stringWithFormat:#"%#-%#",[str substringToIndex:3],[str substringWithRange:NSMakeRange(3, 4)]];
return newStr;
}
- (NSString *)removeMinus:(NSString *)str {
str = [self plainFormat:str];
NSString *newStr = [NSString stringWithFormat:#"%#",[str substringToIndex:3]];
return newStr;
}
#end

If you are eager to go down the road of rolling your own phone number formatter, I would consider doing the follow:
Store the user input without formatting in a string.
After each key press, reformat the display string.
Something like:
#property (nonatomic, copy) NSMutableString *backingString;
#property (weak, nonatomic) IBOutlet UILabel *phoneNumber;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
backingString = [NSMutableString string];
}
- (IBAction)keyOne:(id)sender
{
UIButton *btn = (UIButton *)sender;
[self.backingString appendString:btn.titleLabel.text];
[self formatBackingString];
}
- (IBAction)keyBack:(id)sender
{
[self.backingString deleteCharactersInRange:(NSRange){self.backingString.length - 1,1}];
[self formatBackingString];
}
- (void)formatBackingString
{
// Do your phone formatting here
NSMutableString *formattedString = [NSMutableString string];
NSInteger length = self.backingString.length;
if (length < 3)
{
[formattedString appendString:self.backingString];
}
else if (length == 3)
{
[formattedString appendFormat:#"%#-", self.backingString];
}
else if (length < 8)
{
// etc...
}
self.phoneNumber.text = formattedString;
}
You could also look at NSNumberFormatter.

Related

isEqualToString always returns False

A little background here before I get started, basically we are looking to compare a UDP response with a string stored in Parse's database for our app. This issue is that I can't seem to get the strings to be considered equal by the isEqualToString function. Here's the code I have running now, I have tried a few work-arounds I've seen in other questions but it still doesn't work.
- (BOOL) onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port
{
if(tag == TAG_SINGLE_GRILL)
{
NSString *grillId = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if(grillId.length > 11)
{
grillId = [grillId substringToIndex:11];
}
grillId = [NSString stringWithFormat:#"%#", grillId];
if([grillId hasPrefix:#"GMG"])
{
for(int i = 0; i < [parseGrills count]; i++)
{
NSString *parseGrillId = [[parseGrills objectAtIndex:i] grillId];
parseGrillId = [NSString stringWithFormat:#"%#", parseGrillId];
//If we match the id, add it to found grills
if([grillId isEqualToString:parseGrillId])
{
//do stuff
}
}
}
NSLog(#"Grill ID : %#", grillId);
}
return TRUE;
}
parseGrills is an NSMutableArray with a very basic Grill object, I use synthesize for the properties, otherwise the .m file is essentially empty.
#import <Foundation/Foundation.h>
#interface Grill : NSObject
#property (nonatomic) NSString* grillId;
#property (nonatomic) NSString* ipAddress;
#end
Here's a screen shot of the debugger after it returns false
Any help would be greatly appreciated. Thanks.
I guess that they are of different encoding.
I have run this experiment and see that if the encoding is different, it will return NO. So, try converting parseGrillId to utf8 with the code below.
NSString *s1 = [NSString stringWithCString:"HELLO123" encoding:NSUTF8StringEncoding];
NSString *s2 = [NSString stringWithCString:"HELLO123" encoding:NSUTF16StringEncoding];
NSString *s3 = [NSString stringWithUTF8String:s2.UTF8String];
if ([s1 isEqualToString:s2]) {
NSLog(#"s1 == s2");
}
if ([s1 isEqualToString:s3]) {
NSLog(#"s1 == s3");
}
Will print s1 == s3.

Arrays and addition

What I want to have happen:
Items in array show up in a label. Each item in the array will have it's own label.
I eventually want to add more items.
And make it so that index 0 of thisArray can be added to index 0 of that array; display in a label;
object at index 1 in thisArray can be added to object at index 1 in thatArray and then the result can be displayed in a separate label,
and so on
To appear on a button click.
I'm having a lot of trouble implementing this and I feel like it is a lot simpler than I am making it. Any insight would be appreicated!!
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.steakArray = [[NSArray alloc] initWithObjects: #"800", #"50", nil];
//NSString *name;
NSString *calories = [_steakArray objectAtIndex:0];
NSString *fat = [_steakArray objectAtIndex:1];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//this doesn't work, this is what I want to have happen though.
//click button, show this in label.
- (IBAction)steak:(id)sender {
self.infoLabel.text = calories, fat;
}
#end
You need to look at this documentation https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Strings/Articles/FormatStrings.html
You can use String with Format:
NSString *string1 = [NSString stringWithFormat:#"A string: %#, a float: %1.2f",
#"string", 31415.9265];
// string1 is "A string: string, a float: 31415.93"
NSNumber *number = #1234;
NSDictionary *dictionary = #{#"date": [NSDate date]};
NSString *baseString = #"Base string.";
NSString *string2 = [baseString stringByAppendingFormat:
#" A number: %#, a dictionary: %#", number, dictionary];
// string2 is "Base string. A number: 1234, a dictionary: {date = 2005-10-17 09:02:01 -0700; }"
Even if you are getting calories in string, you can use the following code to add and display in the label
NSString *calories1 = [_steakArray objectAtIndex:0];
NSString *calories2 = [_steakArray2 objectAtIndex:0];
int sum = [calories1 intValue] + [calories2 intValue];
self.infoLabel.text = [NSString stringWithFormat:#"%d",sum];

Calling ViewDidAppear from numerous methods to update NSMutableDictionary data entries

I am fairly new to Objective C and am attempting to develop an app using Xcode5.
I am storing strings (either composed of numbers 1-9 or N/A) in a NSMutableDictionary.
When users get to the "Review your inputed results page" I want them to be able to manually go into a text field, delete the value present and retype their new value if necessary. However, I don't know how to reload this information into the system so that the new values will carry over into the email client, which basically sends the results to whatever email address the user wishes.
Currently, the values are being loaded using ViewDidAppear upon entering the UIView, but I think I need to call it again if, for example, textField1 is updated.
I have methods for all the textFields that are textField(insert correct number here)IsUpdated and inside those I want to store the new value to the NSMutableDictionary (which I believe I can already do).
The issue is I cannot figure out how to get the current version of the dictionary that was loaded upon entering the UIView to update so that the information in ViewDidAppear updates for the email.
Hope that made sense.
As I said, definitely new to Objective C.
Below is the viewDidAppear method.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:(BOOL)animated];
AppDelegate *app = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSMutableDictionary *results = [app results];
NSString *firstResult = [results valueForKey:#"first"];
NSString *secondResult = [results valueForKey:#"second"];
NSString *thirdResult = [results valueForKey:#"third"];
if ([firstResult isEqual: #"N/A"]) {
self.Result1.text = results[#"first"];
} else {
int firstResultInt = [firstResult intValue]; firstResultInt++;
[_Result1 setText:[NSString stringWithFormat:#"%i", firstResultInt]];
}
if ([secondResult isEqual: #"N/A"]) {
self.Result2.text = results[#"second"];
} else {
int secondResultInt = [secondResult intValue]; secondResultInt++;
[_Result2 setText:[NSString stringWithFormat:#"%i", secondResultInt]];
}
if ([thirdResult isEqual: #"N/A"]) {
self.Result3.text = results[#"third"];
} else {
int thirdResultInt = [thirdResult intValue]; thirdResultInt++;
[_Result3 setText:[NSString stringWithFormat:#"%i", thirdResultInt]];
}
self.diningResult.text = results[#"dining"];
self.basementResult.text = results[#"basement"];
self.atticResult.text = results[#"attic"];
self.carResult.text = results[#"car"];
self.hallwayResult.text = results[#"hallway"];
self.garageResult.text = results[#"garage"];
self.other1Result.text = results[#"other"];
self.other2Result.text = results[#"other1"];
self.other1Name.text = results[#"other1name"];
self.other2Name.text = results[#"other2name"];
NSMutableString * str = [NSMutableString new];
[str appendString:#"Bedroom: "];
if ([firstResult isEqual: #"N/A"]) {
[str appendString: firstResult];
} else {
int firstResultInt = [firstResult intValue]; firstResultInt++;
NSString *firstResultString = [NSString stringWithFormat:#"%d",firstResultInt];
[str appendString: firstResultString];
}
[str appendString:#"\n"];
[str appendString:#"Living Room: "];
if ([secondResult isEqual: #"N/A"]) {
[str appendString: secondResult];
} else {
int secondResultInt = [secondResult intValue]; secondResultInt++;
NSString *secondResultString = [NSString stringWithFormat:#"%d",secondResultInt];
[str appendString: secondResultString];
}
[str appendString:#"\n"];
[str appendString:#"Kitchen: "];
if ([thirdResult isEqual: #"N/A"]) {
[str appendString: thirdResult];
} else {
int thirdResultInt = [thirdResult intValue]; thirdResultInt++;
NSString *thirdResultString = [NSString stringWithFormat:#"%d",thirdResultInt];
[str appendString: thirdResultString];
}
[str appendString:#"\n"];
[str appendString:#"Dining:"];
[str appendString:self.diningResult.text];
[str appendString:#"\n"];
//Code goes on to do the same with all other fields. all strings led by "str" get transferred over to the email
self.emailString = [NSString stringWithString:str];
}
The code should not call viewDidAppear, it's the responsibility of the framework to call viewDidAppear at the appropriate times. Instead, you should make a separate methods, e.g. UpdateMailContents and UpdateTextFields. Then call those methods from viewDidAppear, and call UpdateMailContents from the textFieldDidEndEditing method of the UITextFieldDelegate protocol.

How to randomize letters correctly from an NSString

I am creating a word scrambler and I am having issues randomizing the letters. When the letters get randomized, it doesn't make sense.
For example, the word PARK shows as AAPA. So, as you can tell it won't make sense for the user when it is time to unscramble.
Just so you know, I am using a .plist file to hold the words.
This is the code I am using to randomize the letters:
_words = [NSMutableArray arrayWithCapacity:scramblelength];
for (int i=0;i<scramblelength;i++) {
NSString *letter = [scramble substringWithRange:[scramble rangeOfComposedCharacterSequenceAtIndex:arc4random()%[scramble length]]];
Then, I am creating UIImageViews to display the scrambled words:
if (![letter isEqualToString:#""]) {
GameView *boxes = [[GameView alloc] initWithLetter:letter andSideLength:boxSide];
boxes.center = CGPointMake(xOffset + i*(boxSide + kTileMargin), kScreenHeight/4*3);
[self.scrambleView addSubview:boxes];
[_words addObject:boxes];
What am I doing wrong here? I would like for the letters in the scrambled words to make sense.
Please help, I am stuck on this one!
Thanks!
As long as your string length will fit in 32 bits, this should be fine. If not, I would replace arc4random_uniform with a uniform random number generator in C++ and compile this as an Objective-C++ module.
The code simply iterates through the string, and swaps each composed character sequence with some random composed character sequence from the same string.
Sorry, that's what happens when you are arrogant and just type out code. Let me know if you have trouble with this one...
For much larger strings, there is a more efficient way, but this seems to do the trick.
NSMutableString category...
#interface NSMutableString (Scramble)
- (void)scramble;
#end
#implementation NSMutableString (Scramble)
static void
swapRanges(NSMutableString *string, NSRange iRange, NSRange jRange)
{
// Need to replace the "trailing" component first
if (NSEqualRanges(iRange, jRange)) return;
if (iRange.location > jRange.location) {
NSRange tmpRange = iRange;
iRange = jRange;
jRange = tmpRange;
}
NSString *iString = [self substringWithRange:iRange];
NSString *jString = [self substringWithRange:jRange];
[string replaceCharactersInRange:jRange withString:iString];
[string replaceCharactersInRange:iRange withString:jString];
}
- (void)scramble
{
for (NSUInteger i = 0; i < self.length; ++i) {
NSRange iRange = [self rangeOfComposedCharacterSequenceAtIndex:i];
NSUInteger j = arc4random_uniform(self.length);
NSRange jRange = [self rangeOfComposedCharacterSequenceAtIndex:j];
swapRanges(self, iRange, jRange);
}
}
#end
NSString category...
#interface NSString (Scramble)
- (NSString*)scrambledString;
#end
#implementation NSString (Scramble)
- (NSString *)scrambledString
{
NSMutableString *result = [self mutableCopy];
[result scramble];
return [result copy];
}
#end
Sample use...
[someMutableString scramble];
NSString *mixedUp = [someString scrambledString];
Or, if you are comfortable with C++, convert to a std::wstring, call std::random_shuffle, then convert that to a NSString. Lots less bugs when using proven, well tested code.
When you are getting a random letter, you need to do something to remove that letter from your NSMutableArray (ie the word's letters when in order). So as you iterate through the word, each time there are fewer characters remaining. Right now, from your limited code block (the first one), it appears you might not be doing that. You want something like "[_words removeObjectAtIndex:letterIndex]" and you would also want to iterate from number of letters down to zero as you remove items from the array also: for (int i=[_words count]; i > [_words count]; i--) because you need to go from 4 letters down to 0 letters left.
So, I'm sure there are more efficient ways to do this, but I go by the rule of not optimizing until you need to. With that in mind, this code appears to work correctly:
- (NSString *)scrambleWord:(NSString *)word {
NSMutableArray *letterArray = [self letterArrayFromWord:word];
NSMutableString *returnValue = [[NSMutableString alloc] init];
do {
int randomIndex = arc4random() % letterArray.count;
[returnValue appendString:letterArray[randomIndex]];
[letterArray removeObjectAtIndex:randomIndex];
if (letterArray.count == 1) {
[returnValue appendString:letterArray[0]];
break;
}
} while (YES);
if ([[returnValue copy] isEqualToString:word]) {
return [self scrambleWord:word];
} else {
return [returnValue copy];
}
}
- (NSMutableArray *)letterArrayFromWord:(NSString *)word {
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < word.length; i = i + 1) {
[array addObject:[NSString stringWithFormat:#"%C", [word characterAtIndex:i]]];
}
return array;
}

Split NSString and Limit the response

I have a string Hello-World-Test, I want to split this string by the first dash only.
String 1:Hello
String 2:World-Test
What is the best way to do this? What I am doing right now is use componentsSeparatedByString, get the first object in the array and set it as String 1 then perform substring using the length of String 1 as the start index.
Thanks!
I added a category on NSString to split on the first occurrence of a given string. It may not be ideal to return the results in an array, but otherwise it seems fine. It just uses the NSString method rangeOfString:, which takes an NSString(B) and returns an NSRange showing where that string(B) is located.
#interface NSString (Split)
- (NSArray *)stringsBySplittingOnString:(NSString *)splitString;
#end
#implementation NSString (Split)
- (NSArray *)stringsBySplittingOnString:(NSString *)splitString
{
NSRange range = [self rangeOfString:splitString];
if (range.location == NSNotFound) {
return nil;
} else {
NSLog(#"%li",range.location);
NSLog(#"%li",range.length);
NSString *string1 = [self substringToIndex:range.location];
NSString *string2 = [self substringFromIndex:range.location+range.length];
NSLog(#"String1 = %#",string1);
NSLog(#"String2 = %#",string2);
return #[string1, string2];
}
}
#end
Use rangeOfString to find if split string exits and then use substringWithRange to create new string on bases of NSRange.
For Example :
NSString *strMain = #"Hello-World-Test";
NSRange match = [strMain rangeOfString:#"-"];
if(match.location != NSNotFound)
{
NSString *str1 = [strMain substringWithRange: NSMakeRange (0, match.location)];
NSLog(#"%#",str1);
NSString *str2 = [strMain substringWithRange: NSMakeRange (match.location+match.length,(strMain.length-match.location)-match.length)];
NSLog(#"%#",str2);
}

Resources