I'm trying to sort my arrays with a particular sequence using an NSComparisonResult. I'm unable to figure out how to achieve the sequence I'm wanting.
I'm trying to weight Emojis towards the top (sequence of Emojis doesn't matter), followed by A-Z letters with giving a weight towards lowercase before uppercase, followed by numbers, followed by punctuation, then by symbols, and whatever else after that I dont care about at this point. I've gotten pretty close so far, but am still coming up short with what I want.
The sequence I'm trying to achieve would look like this as the output:
("\Ud83d\Ude03",
a,
A,
aa,
aA,
ab,
aB,
a1,
A1,
1,
01,
11,
001,
0001,
1001,
"#",
"#a",
"#1",
"$12",
"$0012")
Based upon this array as the input:
#[ #"a", #"aA", #"aa", #"A", #"aB", #"11", #"1001", #"ab", #"001", #"01",
#"a1", #"A1", #"π", #"0001", #"1", #"#", #"$12", #"$0012", #"#a", #"#1" ];
But this is the output I'm getting:
("\Ud83d\Ude03",
a,
A,
aA,
aa,
aB,
ab,
a1,
A1,
0001,
001,
01,
1,
1001,
11,
"#a",
"#1",
"$0012",
"$12",
"#")
Code:
- (NSArray *)sortedArray:(NSArray *)input
{
NSArray *newArray = [input sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2)
{
NSString *nameOne = obj1;
NSString *nameTwo = obj2;
NSString *startOne;
NSString *startTwo;
NSInteger currentIndex = 0;
NSInteger maxIndex = (nameOne.length < nameTwo.length) ? nameOne.length : nameTwo.length;
NSCharacterSet *decimalDigitCharSet = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *punctuationCharSet = [NSCharacterSet punctuationCharacterSet];
NSCharacterSet *symbolCharSet = [NSCharacterSet symbolCharacterSet];
NSMutableCharacterSet *nonPriorityCharSet = [[NSMutableCharacterSet alloc]init];
[nonPriorityCharSet formUnionWithCharacterSet:punctuationCharSet];
[nonPriorityCharSet formUnionWithCharacterSet:symbolCharSet];
do
{
if (currentIndex < maxIndex)
{
startOne = [nameOne substringWithRange:NSMakeRange(currentIndex, 1)];
startTwo = [nameTwo substringWithRange:NSMakeRange(currentIndex, 1)];
currentIndex++;
}
else
{
if (nameOne.length == nameTwo.length)
{
return NSOrderedSame;
}
else
{
return (nameOne.length < nameTwo.length) ? NSOrderedAscending : NSOrderedDescending;
}
}
}
while ([startOne isEqualToString:startTwo]);
{
NSRange rangeOne = [startOne rangeOfCharacterFromSet:nonPriorityCharSet];
NSRange rangeTwo = [startTwo rangeOfCharacterFromSet:nonPriorityCharSet];
if (rangeOne.length > 0 || rangeTwo.length > 0)
{
return (rangeOne.length > 0) ? NSOrderedDescending : NSOrderedAscending;
}
NSRange decimalRangeOne = [startOne rangeOfCharacterFromSet:decimalDigitCharSet];
NSRange decimalRangeTwo = [startTwo rangeOfCharacterFromSet:decimalDigitCharSet];
if (decimalRangeOne.length > 0 || decimalRangeTwo.length > 0)
{
if (decimalRangeOne.length == decimalRangeTwo.length)
{
return (startOne.intValue > startTwo.intValue) ? NSOrderedDescending : NSOrderedAscending;
}
else if (decimalRangeOne.length > decimalRangeTwo.length)
{
return NSOrderedDescending;
}
else if (decimalRangeTwo.length > decimalRangeOne.length)
{
return NSOrderedAscending;
}
}
}
return [nameOne localizedCaseInsensitiveCompare:nameTwo];
}];
return newArray;
}
You started well. But you didn't properly check for all the rules that you have set. I have created some categories based on you rules, and sort using them.
- (NSArray *)sortedArray:(NSArray *)input
{
__block id blocksafeSelf = self;
NSArray *newArray = [input sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *nameOne = obj1;
NSString *nameTwo = obj2;
NSInteger currentIndex = 0;
unichar charOne = [nameOne characterAtIndex:0];
unichar charTwo = [nameTwo characterAtIndex:0];
short maxLength = MIN(nameOne.length, nameTwo.length);
do {
charOne = [nameOne characterAtIndex:currentIndex];
charTwo = [nameTwo characterAtIndex:currentIndex];
currentIndex ++;
}
while (charOne == charTwo && currentIndex < maxLength);
short oneCategory = [blocksafeSelf getCharCategory:charOne];
short twoCategory = [blocksafeSelf getCharCategory:charTwo];
if (oneCategory != twoCategory) {
return oneCategory > twoCategory;
}
else if (oneCategory != 1) {
if (nameOne.length != nameTwo.length) {
return nameOne.length > nameTwo.length;
}
else {
return charOne > charTwo;
}
}
else {
if (nameOne.length != nameTwo.length) {
return nameOne.length > nameTwo.length;
}
else {
oneCategory = [blocksafeSelf getLetterCategory:charOne];
twoCategory = [blocksafeSelf getLetterCategory:charTwo];
if (oneCategory == twoCategory) {
return charOne > charTwo;
}
else {
unichar tempCharOne = oneCategory == 7 ? charOne + 32 : charOne;
unichar tempCharTwo = twoCategory == 7 ? charTwo + 32 : charTwo;
if (tempCharOne != tempCharTwo) {
return tempCharOne > tempCharTwo;
}
else {
return oneCategory > twoCategory;
}
}
}
}
return [nameOne localizedCaseInsensitiveCompare:nameTwo];
}];
return newArray;
}
- (short)getCharCategory:(unichar)character {
if (character > 255) { // emoji
return 0;
}
NSCharacterSet *letterCaseCharSet = [NSCharacterSet letterCharacterSet];
if ([letterCaseCharSet characterIsMember:character]) return 1;
NSCharacterSet *decimalDigitCharSet = [NSCharacterSet decimalDigitCharacterSet];
if ([decimalDigitCharSet characterIsMember:character]) return 2;
NSCharacterSet *punctuationCharSet = [NSCharacterSet punctuationCharacterSet];
if ([punctuationCharSet characterIsMember:character]) return 3;
NSCharacterSet *symbolCharSet = [NSCharacterSet symbolCharacterSet];
if ([symbolCharSet characterIsMember:character]) return 4;
return 5;
}
- (short)getLetterCategory:(unichar)character {
NSCharacterSet *lowerCaseCharSet = [NSCharacterSet lowercaseLetterCharacterSet];
if ([lowerCaseCharSet characterIsMember:character]) return 6;
return 7;
}
Related
I have array containing dictionary like below:
(
{
"act_priority" = B1;
}
{
"act_priority" = "";
}
{
"act_priority" = A;
}
{
"act_priority" = A3;
}
{
"act_priority" = B;
}
{
"act_priority" = A2;
}
{
"act_priority" = A1;
}
{
"act_priority" = "";
}
)
I would like to sort in alphabetically and numerically both way:
(
{
"act_priority" = A1;
}
{
"act_priority" = A2;
}
{
"act_priority" = A3;
}
{
"act_priority" = B1;
}
{
"act_priority" = A;
}
{
"act_priority" = B;
}
{
"act_priority" = "";
}
{
"act_priority" = "";
}
)
What i had tried is below:
NSArray* sorted = [activityArray sortedArrayUsingComparator:(NSComparator)^(NSDictionary *item1, NSDictionary *item2) {
NSString *score1 = [item1 objectForKey:#"act_priority"];
NSString *score2 = [item2 objectForKey:#"act_priority"];
return [score1 compare:score2 options:NSNumericSearch];
}];
Also:
NSSortDescriptor *Sorter = [[NSSortDescriptor alloc] initWithKey:#"act_priority" ascending:YES];
[activityArray sortUsingDescriptors:[NSArray arrayWithObject:Sorter]];
but it give me like
(
{
"act_priority" = "";
}
{
"act_priority" = "";
}
{
"act_priority" = A;
}
{
"act_priority" = B;
}
{
"act_priority" = A1;
}
{
"act_priority" = A2;
}
{
"act_priority" = A3;
}
{
"act_priority" = B1;
}
)
Here is what I think you want. I gave also an sample to test.
The main point is what you do inside the block.
NSArray *array = #[#{#"act_priority":#"B1"},
#{#"act_priority":#""},
#{#"act_priority":#"A"},
#{#"act_priority":#"A3"},
#{#"act_priority":#"B"},
#{#"act_priority":#"A2"},
#{#"act_priority":#"A1"},
#{#"act_priority":#""}];
NSArray *sortedArray = [array sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2)
{
NSString *string1 = [obj1 objectForKey:#"act_priority"];
NSString *string2 = [obj2 objectForKey:#"act_priority"];
if ([string1 length] == 0) //To put the #"" at the end
return NSOrderedDescending;
else if ([string2 length] == 0) //To put the #"" at the end
return NSOrderedAscending;
else
{
BOOL string1HasSuffixNumber = [self hasSuffixNumber:string1]; //If string1 has a number at the end
BOOL string2HasSuffixNumber = [self hasSuffixNumber:string2]; //If string2 has a number at the end
if (string1HasSuffixNumber && !string2HasSuffixNumber)
return NSOrderedAscending; //Put the string2 at the end
else if (!string1HasSuffixNumber && string2HasSuffixNumber)
return NSOrderedDescending; //Put the string1 at the end
else
return [string1 compare:string2 options:NSCaseInsensitiveSearch]; //Other option can be used, if you want case sensitive one for example, or not, etc.
}
}];
NSLog(#"SortedArray: %#", sortedArray);
With this your "special" method:
-(BOOL)hasSuffixNumber:(NSString *)string
{
if ([string length] < 1)
return FALSE; //"Security added, but in your use case you shouldn't go there;
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"]; //Explicit the character set in case you want to add some changes
if ([[string substringFromIndex:[string length]-1] rangeOfCharacterFromSet:set].location != NSNotFound)
return TRUE;
else
return FALSE;
}
Output:
>SortedArray: (
{
"act_priority" = A1;
},
{
"act_priority" = A2;
},
{
"act_priority" = A3;
},
{
"act_priority" = B1;
},
{
"act_priority" = A;
},
{
"act_priority" = B;
},
{
"act_priority" = "";
},
{
"act_priority" = "";
} )
OK Here is my Code
It now limits user input to two characters after the decimal and checks for a decimal but now it wont let me add or subtract anymore.... /sigh
//
// CalculatorViewController.h
//
//
#import "ViewController.h"
int Method;
long int SelectNumber;
float RunningTotal;
#interface CalculatorViewController : ViewController <UITextFieldDelegate>
#property (strong, nonatomic) IBOutlet UILabel *screen;
- (IBAction)backToMainViewFromCalculatorViewButton:(id)sender;
#end
and my .m
//
// CalculatorViewController.m
//
//
#import "CalculatorViewController.h"
BOOL isDecimal;
float resultNumber;
float displayNumber;
int operation;
NSUInteger decimalPlacesLimit = 2;
#interface CalculatorViewController ()
#end
#implementation CalculatorViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// self.screen.text = #"";
isDecimal = false;
resultNumber = 0;
self.screen.adjustsFontSizeToFitWidth = TRUE;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (IBAction)backToMainViewFromCalculatorViewButton:(id)sender {
// Dismiss the VIEW
// DO NOT SEGUE back
// DO THIS
//Going back using segue just stacks views and then soon you run out of memory and APP crashes
[self dismissViewControllerAnimated:YES completion:NULL];
}
-(void)setResultWithNumber:(int)theNumber{
if(!isDecimal){
displayNumber *= 10;
displayNumber += theNumber;
self.screen.text = [NSString stringWithFormat:#"%.0f", displayNumber];
}
else
{
self.screen.text = [self.screen.text stringByAppendingString:[NSString stringWithFormat:#"%d", theNumber]];
}
displayNumber = [self.screen.text floatValue];
}
-(void)operationWithNumber:(int)theNumber{
isDecimal = false;
if(resultNumber == 0){
resultNumber = displayNumber;
}
else{
self.screen.text = [NSString stringWithFormat:#"%.2f",resultNumber];
switch (operation) {
case 1:
resultNumber += displayNumber;
break;
case 2:
resultNumber -= displayNumber;
break;
case 3:
resultNumber = displayNumber*resultNumber;
break;
case 4:
resultNumber /= displayNumber;
break;
default:
break;
}
}
operation = theNumber;
displayNumber = 0;
}
- (IBAction)AC:(id)sender {
operation = 0;
resultNumber = 0;
displayNumber = 0;
isDecimal = false;
self.screen.text = [NSString stringWithFormat:#"%i",0];
}
/*
- (IBAction)plus_minus:(id)sender {
displayNumber = 0 - displayNumber;
if(isDecimal)
result.text = [NSString stringWithFormat:#"%.2f", displayNumber];
else
result.text = [NSString stringWithFormat:#"%.0f", displayNumber];
}
- (IBAction)divide:(id)sender {
if(resultNumber != 0){
[self operationWithNumber:operation];
result.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [result.text floatValue];
resultNumber = 0;
}
[self operationWithNumber:4];
}
*/
- (IBAction)seven:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:7];
isDecimal = false;
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
isDecimal = false;
[self setResultWithNumber:7];
}
}
}
- (IBAction)eight:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:8];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:8];
}
}
}
- (IBAction)nine:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:9];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:9];
}
}
}
/*
- (IBAction)multiply:(id)sender {
if(resultNumber != 0){
[self operationWithNumber:operation];
result.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [result.text floatValue];
resultNumber = 0;
}
[self operationWithNumber:3];
}
*/
- (IBAction)six:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:6];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:6];
}
}
}
- (IBAction)five:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:5];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:5];
}
}
}
- (IBAction)four:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:4];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:4];
}
}
}
- (IBAction)substract:(id)sender {
if(resultNumber != 0){
[self operationWithNumber:operation];
self.screen.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [self.screen.text floatValue];
resultNumber = 0;
}
[self operationWithNumber:2];
}
- (IBAction)three:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:3];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:3];
}
}
}
- (IBAction)two:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:2];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:2];
}
}
}
- (IBAction)one:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
if (range.location == NSNotFound)
{
// No period found
isDecimal = false;
// set the number
[self setResultWithNumber:1];
}
else
{
// If we allready have a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:1];
}
}}
- (IBAction)add:(id)sender {
if(resultNumber != 0)
{
[self operationWithNumber:operation];
self.screen.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [self.screen.text floatValue];
resultNumber = 0;
}
[self operationWithNumber:1];
}
- (IBAction)zero:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:0];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:0];
}
}
}
- (IBAction)dot:(id)sender {
isDecimal = true;
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
self.screen.text = [self.screen.text stringByAppendingString:#"."];
}
// We must have a period so now lets test for how many places after the decimal
// and limit it to two
// NSLog(#"text on the way: %#", string);
/*
NSUInteger decimalPlacesLimit = 2;
NSRange rangeDot = [self.screen.text rangeOfString:#"." options:NSCaseInsensitiveSearch];
NSRange rangeComma = [self.screen.text rangeOfString:#"," options:NSCaseInsensitiveSearch];
if (rangeDot.length > 0 || rangeComma.length > 0)
{
if([self.screen.text isEqualToString:#"."])
{
NSLog(#"textField already contains a separator");
}
else
{
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
}
}
}
*/
}
- (IBAction)equals:(id)sender {
[self operationWithNumber:operation];
self.screen.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [self.screen.text floatValue];
resultNumber = 0;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSLog(#"text on the way: %#", string);
NSUInteger decimalPlacesLimit = 2;
NSRange rangeDot = [textField.text rangeOfString:#"." options:NSCaseInsensitiveSearch];
NSRange rangeComma = [textField.text rangeOfString:#"," options:NSCaseInsensitiveSearch];
if (rangeDot.length > 0 || rangeComma.length > 0){
if([string isEqualToString:#"."]) {
NSLog(#"textField already contains a separator");
return NO;
} else {
NSArray *explodedString = [textField.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![string isEqualToString:#""]) {
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
return NO;
}
}
}
return YES;
}
#end
I cant figure out how to make sure I check for a decimal and then limit entries to no more then two places after the decimal and still be able to calculate
Any help would be appreciated
Travis
I build a calculator application before. What I did was:
Use tag to identify different buttons, 0 is for button 0, 1 is for button 1 ... and 99 is for button dot. All these button triggered the same IBAction function, and in this function just check whether the button user pressed could be added to the Label (You can just use UILabel instead of UITextField here) by applying the rules you created above.
Try this code:
// Assume that, the number buttons have tags from 0 - 9 respectively, and dot button has tag 99
- (IBAction)onButtonPressed:(UIButton *)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
BOOL canUpdateScreen = YES;
if(range.location != NSNotFound) {
if(sender.tag == 99) {
// Already got dot, cannot show another dot
canUpdateScreen = NO;
} else {
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
if(explodedString[1].length >= decimalPlacesLimit) {
canUpdateScreen = NO;
}
}
}
if(canUpdateScreen) {
if(sender.tag == 99) {
self.screen.text = [NSString stringWithFormat:#"%#%#", self.screen.text, #"."];
} else {
self.screen.text = [NSString stringWithFormat:#"%#%d", self.screen.text, sender.tag];
}
}
}
Here
-(IBAction)NUmber1:(id)sender{
SelectNumber = SelectNumber * 10;
SelectNumber = SelectNumber + 1;
Screen.text = [NSString stringWithFormat:#"%i", SelectNumber];
}
you are erasing your current string with new INTEGER value. Try to change to
-(IBAction)NUmber1:(id)sender{
Screen.text = [Screen.text stringByAppendingString:#"1"];
}
Also you need to change your SelectNumber calculation logic. I suggest you to get it from string when you really need it
double SelectNumber = [Screen.text doubleValue];
Issue,
The code you have posted is, UITextFieldDelegate method implementation. It will not be triggered, if you update the UITextField text through code.
Solution
You can use any of the below solution.
To simplify the tasks, you can use, tag 0 to 9 for buttons 0 to 9 and some other value for β.β and "," as mentioned in another answer.
// 1. Call the method (in your code) on tapping the buttons (0 - 9 and β.β) and conditionally update the UITextField.text (yourTextField.text), like
update = [self textField:yourTextField shouldChangeCharactersInRange:NSMakeRange(yourTextField.text.length, 1) replacementString:yourButtonLabel];
if (update)
{
// Uodate yourTextField
}
else
{
// Donβt update yourTextField
}
// 2. Before updating yourTextField.text, check there is any decimal point or comma already in yourTextField, using
if([yourTextField.text rangeOfString:#"."].location != NSNotFound) // Update condition to check comma if the tapped button is so
{
// Donβt update yourTextField
}
else
{
// Update yourTextField
}
I have a string like #"1234123412341234", i need to append space between every 4 chars like.
#"1234 1234 1234 1234"
i.e, I need a NSString like Visa Card Type. I have tried like this but i didn't get my result.
-(void)resetCardNumberAsVisa:(NSString*)aNumber
{
NSMutableString *s = [aNumber mutableCopy];
for(int p=0; p<[s length]; p++)
{
if(p%4==0)
{
[s insertString:#" " atIndex:p];
}
}
NSLog(#"%#",s);
}
Here's a unicode aware implementation as a category on NSString:
#interface NSString (NRStringFormatting)
- (NSString *)stringByFormattingAsCreditCardNumber;
#end
#implementation NSString (NRStringFormatting)
- (NSString *)stringByFormattingAsCreditCardNumber
{
NSMutableString *result = [NSMutableString string];
__block NSInteger count = -1;
[self enumerateSubstringsInRange:(NSRange){0, [self length]}
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
if ([substring rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]].location != NSNotFound)
return;
count += 1;
if (count == 4) {
[result appendString:#" "];
count = 0;
}
[result appendString:substring];
}];
return result;
}
#end
Try it with this test string:
NSString *string = #"ab ππ 132487 387 e e e ";
NSLog(#"%#", [string stringByFormattingAsCreditCardNumber]);
The method works with non-BMP characters (i.e. emoji) and handles existing white space.
Your code is pretty close, however a better semantic for the method is to return a new NSString for any given input string:
-(NSString *)formatStringAsVisa:(NSString*)aNumber
{
NSMutableString *newStr = [NSMutableString new];
for (NSUInteger i = 0; i < [aNumber length]; i++)
{
if (i > 0 && i % 4 == 0)
[newStr appendString:#" "];
unichar c = [aNumber characterAtIndex:i];
[newStr appendString:[[NSString alloc] initWithCharacters:&c length:1]];
}
return newStr;
}
You should do like this:
- (NSString *)resetCardNumberAsVisa:(NSString*)originalString {
NSMutableString *resultString = [NSMutableString string];
for(int i = 0; i<[originalString length]/4; i++)
{
NSUInteger fromIndex = i * 4;
NSUInteger len = [originalString length] - fromIndex;
if (len > 4) {
len = 4;
}
[resultString appendFormat:#"%# ",[originalString substringWithRange:NSMakeRange(fromIndex, len)]];
}
return resultString;
}
UPDATE:
You code will be right on the first inserting space charactor:
This is your originalString:
Text: 123412341234
Location: 012345678901
Base on your code, on the first you insert space character, you will insert at "1" (with location is 4)
And after that, your string is:
Text: 1234 12341234
Location: 0123456789012
So, you see it, now you have to insert second space charater at location is 9 (9%4 != 0)
Hope you can fix your code by yourself!
The code snippet from here do what do you want:
- (NSString *)insertSpacesEveryFourDigitsIntoString:(NSString *)string
andPreserveCursorPosition:(NSUInteger *)cursorPosition
{
NSMutableString *stringWithAddedSpaces = [NSMutableString new];
NSUInteger cursorPositionInSpacelessString = *cursorPosition;
for (NSUInteger i=0; i<[string length]; i++) {
if ((i>0) && ((i % 4) == 0)) {
[stringWithAddedSpaces appendString:#" "];
if (i < cursorPositionInSpacelessString) {
(*cursorPosition)++;
}
}
unichar characterToAdd = [string characterAtIndex:i];
NSString *stringToAdd =
[NSString stringWithCharacters:&characterToAdd length:1];
[stringWithAddedSpaces appendString:stringToAdd];
}
return stringWithAddedSpaces;
}
swift3 based on Droppy
func codeFormat(_ code: String) -> String {
let newStr = NSMutableString()
for i in 0..<code.characters.count {
if (i > 0 && i % 4 == 0){
newStr.append(" ")
}
var c = (code as NSString).character(at: i)
newStr.append(NSString(characters: &c, length: 1) as String)
}
return newStr as String
}
Please make sure that your string length should times by 4.
This solution will insert on the right hand side first.
- (NSString*) fillWhiteGapWithString:(NSString*)source
{
NSInteger dl = 4;
NSMutableString* result = [NSMutableString stringWithString:source];
for(NSInteger cnt = result.length - dl ; cnt > 0 ; cnt -= dl)
{
[result insertString:#" " atIndex:cnt];
}
return result;
}
I've been trying to sort an NSArray of NSDictionaries using a comparator, but I cannot seem to get the output I desire.
The output I'm trying to achieve is that A-Z usernames should come first in the sorted array, then usernames that start with a digit should come second in the sorted array, and lastly usernames that start with an underscore should be last in the sorted array. Any help is truly appreciated!
EDIT: It should be sorted so it looks consistent through the whole NSArray so that: _Anna comes before _Bob and _11Bob comes before _12Cary but after _09Bob
Example of desired output I'm looking for:
(
{
username = abcd;
},
{
username = Anna;
},
{
username = 01Bob;
},
{
username = 02Tob;
},
{
username = 03ZED;
},
{
username = 04_Hob;
},
{
username = 04_sob;
},
{
username = "_anna";
},
{
username = "_bob";
},
{
username = "_boc";
},
{
username = "_bocd12";
},
{
username = "_bocd13";
}
{
username = _01Bob;
},
{
username = _02Tob;
},
)
I hope that makes sense now.
Sample NSDictionary with an NSArray of NSDictionaries:
NSDictionary *dictionary = #{#"users":#[#{#"username":#"191anna"},#{#"username":#"_091bob"},#{#"username":#"Bob"},#{#"username":#"charlie"}]};
I'm trying by using this comparator:
NSArray *array = [[dictionary objectForKey:#"users"] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2)
{
NSString *f1 = [obj1 objectForKey:#"username"];
NSString *f2 = [obj2 objectForKey:#"username"];
NSString *s1 = [[obj1 objectForKey:#"username"]substringFromIndex:1];
NSString *s2 = [[obj2 objectForKey:#"username"]substringFromIndex:1];
if ([s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location == [s2 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location)
{
return [f1 localizedCaseInsensitiveCompare:f2];
}
else if ([s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location != [s2 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location)
{
return [f1 localizedCaseInsensitiveCompare:f2];
if ([s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location == NSNotFound)
{
return NSOrderedDescending;
}
}
return NSOrderedAscending;
}];
But it gives me the following (not the way I want) sorted NSArray:
(
{
username = "_091bob";
},
{
username = 191anna;
},
{
username = Bob;
},
{
username = charlie;
}
)
Here's what I came up with. It's a touch long because it requires quite a bit of logic. It can likely be optimized further:
My Set Up:
NSArray * usernames = #[#"191anna", #"abcd", #"Anna", #"01Bob", #"02Tob", #"03ZED", #"04_rob", #"_anna", #"_bob", #"_boc", #"_bocd12", #"_bocd13", #"_01Bob", #"_02Tob"];
NSMutableArray * users = [NSMutableArray array];
for (NSString * username in usernames) {
[users addObject:#{#"username":username}];
}
NSDictionary * dictionary = #{#"users":users};
And The Sort:
NSArray *sortedArray = [dictionary[#"users"] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2)
{
NSString *nameOne = obj1[#"username"];
NSString *nameTwo = obj2[#"username"];
NSString *startOne;
NSString *startTwo;
NSInteger currentIndex = 0;
NSInteger maxIndex = (nameOne.length < nameTwo.length) ? nameOne.length : nameTwo.length;
// Get our first differentiating letter
do {
if (currentIndex < maxIndex) {
startOne = [nameOne substringWithRange:NSMakeRange(currentIndex, 1)];
startTwo = [nameTwo substringWithRange:NSMakeRange(currentIndex, 1)];
currentIndex++;
}
else {
// Names are equal up to max length. Same length is same, else shorter word ascending. (bob above bobb)
if (nameOne.length == nameTwo.length) {
return NSOrderedSame;
}
else {
return (nameOne.length < nameTwo.length) ? NSOrderedAscending : NSOrderedDescending;
}
}
} while ([startOne isEqualToString:startTwo]);
// Prioritize underscores to bottom
NSCharacterSet * underscoreCharSet = [NSCharacterSet characterSetWithCharactersInString:#"_"];
NSRange underscoreRangeOne = [startOne rangeOfCharacterFromSet:underscoreCharSet];
NSRange underscoreRangeTwo = [startTwo rangeOfCharacterFromSet:underscoreCharSet];
if (underscoreRangeOne.length > 0 || underscoreRangeTwo.length > 0) {
// Something is underscored, put it on the bottom
return (underscoreRangeOne.length > 0) ? NSOrderedDescending : NSOrderedAscending;
}
// Prioritize numbers to bottom
NSRange decimalRangeOne = [startOne rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
NSRange decimalRangeTwo = [startTwo rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
if (decimalRangeOne.length > 0 || decimalRangeTwo.length > 0) {
// Something is numbered, put it on the bottom
if (decimalRangeOne.length == decimalRangeTwo.length) {
return (startOne.intValue > startTwo.intValue) ? NSOrderedDescending : NSOrderedAscending;
}
else if (decimalRangeOne.length > decimalRangeTwo.length) {
return NSOrderedDescending;
}
else if (decimalRangeTwo.length > decimalRangeOne.length) {
return NSOrderedAscending;
}
}
// Now, sort alphabetically
return [nameOne localizedCaseInsensitiveCompare:nameTwo];
}];
NSLog(#"SortedArray: %#", sortedArray);
Will log as:
abcd,
Anna,
01Bob,
02Tob,
03ZED,
"04_rob",
191anna,
"_anna",
"_bob",
"_boc",
"_bocd12",
"_bocd13",
"_01Bob",
"_02Tob"
You can optimize this further, but your sort logic would be like below.
NSArray *sorted = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *name1 = [(NSDictionary *) obj1 objectForKey:NAME];
NSString *name2 = [(NSDictionary *) obj2 objectForKey:NAME];
if ([name1 characterAtIndex:0] == '_' && [name2 characterAtIndex:0] == '_')
{
return [name1 compare:name2 options:NSCaseInsensitiveSearch];
}
else if ([name1 characterAtIndex:0] == '_')
{
return NSOrderedDescending;
}
else if ([name2 characterAtIndex:0] == '_')
{
return NSOrderedAscending;
}
else if (([name1 intValue] && [name2 intValue]) || ([name1 characterAtIndex:0] == '0' && [name2 characterAtIndex:0] == '0'))
{
return [name1 compare:name2 options:NSCaseInsensitiveSearch];
}
else if ([name1 intValue] >0 || [name1 characterAtIndex:0] == '0')
{
return NSOrderedDescending;
}
else if ([name2 intValue]>0 || [name2 characterAtIndex:0] == '0')
{
return NSOrderedAscending;
}
else
{
return [name1 compare:name2 options:NSCaseInsensitiveSearch];
}
//return res;
}];
I was looking into implementing hashtag autocomplete with objective-C as shown in the picture
I found it a bit difficult than expected. I'm looking more specific implementation for adding and deleting hashtags. For example, the hashtag should be deleted as a whole at once. I was wondering if anyone has similar experience implemented it and if there's a more efficient way implemented it. Thanks
I ended up writing some functions that I feel is a bit ugly but it works. Maybe there are some more efficient ways to implement it.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
//user is a singleton instance
User *user = [User sharedUser];
user.autocompleteTableView.hidden = NO;
int identifiedTagsStringLength = [self.identifiedTagsString length];
int cursorLocation = range.location;
//insert characters
if (range.location >= identifiedTagsStringLength) {
NSString *newSearch =#"";
NSRange newRange;
if(identifiedTagsStringLength != 0) {
newSearch = [urlField.text substringFromIndex:identifiedTagsStringLength];
newRange = NSMakeRange(range.location - identifiedTagsStringLength, 0);
}
else {
newSearch = textField.text;
newRange = range;
}
NSString *substring = [NSString stringWithString:newSearch];
substring = [substring stringByReplacingCharactersInRange:newRange withString:string];
[self searchAutocompleteEntriesWithSubstring:substring];
if (cursorLocation > currentTagsRange) {
currentTagsRange = cursorLocation;
}
}
//delete tags
else {
if ([self.ranges count] != 0 && cursorLocation < currentTagsRange) {
int rangeLength = [self.ranges count];
int toBeRemovedIndex = 0;
for (int i = 0; i< rangeLength; i++) {
if (cursorLocation >= [[self.ranges objectAtIndex:i][0] intValue]
&& cursorLocation <= [[self.ranges objectAtIndex:i][1] intValue]) {
toBeRemovedIndex = i;
}
}
[self.tags removeObjectAtIndex:toBeRemovedIndex];
[self updateRanges];
NSString *outputString = #"";
for (NSString *tag in self.tags) {
outputString = [NSString stringWithFormat:#"%##%# ", outputString,
tag];
}
urlField.text = outputString;
self.identifiedTagsString = urlField.text;
currentTagsRange = [outputString length] - 1;
}
}
return YES;
}
- (void)updateRanges {
self.ranges = [[NSMutableArray alloc] init];
int startIndex = 0;
for (NSString *tag in self.tags) {
startIndex = [self.ranges count] == 0 ? 0 : [[self.ranges lastObject][1] intValue] + 1;
int tagLength = [tag length];
NSArray *range = [NSArray arrayWithObjects:[NSNumber numberWithInt:startIndex], [NSNumber numberWithInt:startIndex + tagLength + 1], nil];
[self.ranges addObject: range];
}
}
#pragma mark UITableViewDataSource methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if (self.identifiedTagsString == NULL) {
self.identifiedTagsString = #"";
}
[self.tags addObject: selectedCell.textLabel.text];
[self updateRanges];
NSString *output = #"";
for (NSString *tag in self.tags) {
output = [NSString stringWithFormat:#"%##%# ", output, tag];
}
urlField.text = output;
User *user = [User sharedUser];
user.autocompleteTableView.hidden = YES;
self.identifiedTagsString = urlField.text;
currentTagsRange = [urlField.text length];
}