Improving Performance Of Core Plot - ios

I'm plotting Real Time ECG using CorePlot library.When it uses in iPad air the performance is okay.But when i tried it with iPad mini, There is a delay in the plotting.I have done with collapseLayer and this link also.that didn't solved my problem.Can anyone suggest new solution for this.
My code is below:
-(void)newData:(NSTimer *)theTimer
{
for (int i =0;i<plotcount;i++){
if([Qrrch0 count]>0 || [Qrrch1 count]>0 || [Qrrch2 count]>0 || [Qrrch3 count]>0 || [Qrrspo2 count]>0 ){
if(g1==1 && thePlot){
currentIndex ++;
}
if(g2==1 && thePlot1){
currentIndex1 ++;
}
if(g3==1 && thePlot2){
currentIndex2 ++;
}
if(g4==1 && thePlot3){
currentIndex3 ++;
}
if(spo2==1 && thePlot4){
currentIndex4 ++;
}
if(arrayIndex>=kchannel1-1)
{
arrayIndex=0;
}
if(arrayIndex1>=kchannel2-1)
{
arrayIndex1=0;
}
if(arrayIndex>=kchannel1-1)
{
arrayIndex=0;
}
if(arrayIndex2>=kchannel3-1)
{
arrayIndex2=0;
}
if(arrayIndex3>=kchannel4-1)
{
arrayIndex3=0;
}
if(arrayIndex4>=kchannel5-1)
{
arrayIndex4=0;
}
if(g1==1 && thePlot){
currentIndex5++;
if(currentIndex5>=kchannel1)
{
if(arrayIndex==0)
{
[thePlot reloadDataInIndexRange:NSMakeRange(arrayIndex, arrayIndex)];
}else{
[thePlot deleteDataInIndexRange:NSMakeRange(arrayIndex, 1)];
}
}
if([Qrrch0 count]!=0)
{
arrPlot[arrayIndex]=[[Qrrch0 objectAtIndex:0] integerValue];
lastPlot0=[Qrrch0 objectAtIndex:0];
}else{
arrPlot[arrayIndex]=[lastPlot0 integerValue];
}
arrayIndex++;
}
if(g2==1 && thePlot1){
currentIndex6++;
if(currentIndex6>=kchannel2)
{
if(arrayIndex1==0)
{
[thePlot1 reloadDataInIndexRange:NSMakeRange(arrayIndex1, arrayIndex1)];
}else{
[thePlot1 deleteDataInIndexRange:NSMakeRange(arrayIndex1, 1)];
}
}
if([Qrrch1 count]!=0)
{
arrPlot1[arrayIndex1]=[[Qrrch1 objectAtIndex:0] integerValue];
lastPlot1=[Qrrch1 objectAtIndex:0];
}else{
arrPlot1[arrayIndex1]=[lastPlot1 integerValue];
}
arrayIndex1++;
}
if(g3==1 && thePlot2){
currentIndex7++;
if(currentIndex7>=kchannel3)
{
if(arrayIndex2==0)
{
[thePlot2 reloadDataInIndexRange:NSMakeRange(arrayIndex2, arrayIndex2)];
}else{
[thePlot2 deleteDataInIndexRange:NSMakeRange(arrayIndex2, 1)];
}
}
if([Qrrch2 count]!=0)
{
arrPlot2[arrayIndex2]=[[Qrrch2 objectAtIndex:0] integerValue];
lastPlot2=[Qrrch2 objectAtIndex:0];
}else{
arrPlot2[arrayIndex2]=[lastPlot2 integerValue];
}
arrayIndex2++;
}
if(g4==1 && thePlot3){
currentIndex8++;
if(currentIndex8>=kchannel4)
{
if(arrayIndex3==0)
{
[thePlot3 reloadDataInIndexRange:NSMakeRange(arrayIndex3, arrayIndex3)];
}else{
[thePlot3 deleteDataInIndexRange:NSMakeRange(arrayIndex3, 1)];
}
}
if([Qrrch3 count]!=0)
{
arrPlot3[arrayIndex3]=[[Qrrch3 objectAtIndex:0] integerValue];
lastPlot3=[Qrrch3 objectAtIndex:0];
}else{
arrPlot3[arrayIndex3]=[lastPlot3 integerValue];
}
arrayIndex3++;
}
if(spo2==1 && thePlot4){
currentIndex9++;
if(currentIndex9>=kchannel5)
{
if(arrayIndex4==0)
{
[thePlot4 reloadDataInIndexRange:NSMakeRange(arrayIndex4, arrayIndex4)];
}else{
[thePlot4 deleteDataInIndexRange:NSMakeRange(arrayIndex4, 1)];
}
}
if([Qrrspo2 count]!=0)
{
arrPlot4[arrayIndex4]=[[Qrrspo2 objectAtIndex:0] integerValue];
lastPlot4=[Qrrspo2 objectAtIndex:0];
}else{
arrPlot4[arrayIndex4]=[lastPlot4 integerValue];
}
arrayIndex4++;
}
if(g1==1 && thePlot){
if([Qrrch0 count]!=0)
{
[Qrrch0 removeObjectAtIndex:0];
}
if(currentIndex>=kchannel1)
{
currentIndex=1;
}
[thePlot insertDataAtIndex:currentIndex-1 numberOfRecords:1];
}
if(g2==1 && thePlot1){
if([Qrrch1 count]!=0)
{
[Qrrch1 removeObjectAtIndex:0];
}
if(currentIndex1>=kchannel2)
{
currentIndex1=1;
}
[thePlot1 insertDataAtIndex:currentIndex1-1 numberOfRecords:1];
}
if(g3==1 && thePlot2){
if([Qrrch2 count]!=0)
{
[Qrrch2 removeObjectAtIndex:0];
}
if(currentIndex2>=kchannel3)
{
currentIndex2=1;
}
[thePlot2 insertDataAtIndex:currentIndex2-1 numberOfRecords:1];
}
if(g4==1 && thePlot3){
if([Qrrch3 count]!=0)
{
[Qrrch3 removeObjectAtIndex:0];
}
if(currentIndex3>=kchannel4)
{
currentIndex3=1;
}
[thePlot3 insertDataAtIndex:currentIndex3-1 numberOfRecords:1];
}
if(spo2==1 && thePlot4){
if([Qrrspo2 count]!=0)
{
[Qrrspo2 removeObjectAtIndex:0];
}
if(currentIndex4>=kchannel5)
{
currentIndex4=1;
}
[thePlot4 insertDataAtIndex:currentIndex4-1 numberOfRecords:1];
}
}
else
{
[self datacha];
}
}
}
-(void) datacha{
NSArray *array;
if([FinalArray count]>0){
if(g1==1 && thePlot){
array = [[NSArray alloc] initWithArray:[FinalArray objectAtIndex:0]];
[Qrrch0 addObjectsFromArray:array];
}
if(g1==1 && thePlot){
[FinalArray removeObjectAtIndex:0];
}
}
if([FinalArray1 count]>0){
if(g2==1 && thePlot1){
array = [[NSArray alloc] initWithArray:[FinalArray1 objectAtIndex:0]];
[Qrrch1 addObjectsFromArray:array];
}
if(g2==1 && thePlot1){
[FinalArray1 removeObjectAtIndex:0];
}
}
if([FinalArray2 count]>0){
if(g3==1 && thePlot2){
array = [[NSArray alloc] initWithArray:[FinalArray2 objectAtIndex:0]];
[Qrrch2 addObjectsFromArray:array];
}
if(g3==1 && thePlot2){
[FinalArray2 removeObjectAtIndex:0];
}
} if([FinalArray3 count]>0){
if(g4==1 && thePlot3){
array = [[NSArray alloc] initWithArray:[FinalArray3 objectAtIndex:0]];
[Qrrch3 addObjectsFromArray:array];
}
if(g4==1 && thePlot3){
[FinalArray3 removeObjectAtIndex:0];
}
}
if([FinalArray4 count]>0){
if(spo2==1 && thePlot4){
array = [[NSArray alloc] initWithArray:[FinalArray4 objectAtIndex:0]];
[Qrrspo2 addObjectsFromArray:array];
}
if(spo2==1 && thePlot4){
[FinalArray4 removeObjectAtIndex:0];
}
}
}
#pragma mark -
#pragma mark Plot Data Source Methods
-(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
{
if(plot == thePlot)
{
if(currentIndex5>=kchannel1)
{
return kchannel1;
}else{
return currentIndex5;
}
}
if(plot == thePlot1)
{
if(currentIndex6>=kchannel2)
{
return kchannel2;
}else{
return currentIndex6;
}
}
if(plot == thePlot2)
{
if(currentIndex7>=kchannel3)
{
return kchannel3;
}else{
return currentIndex7;
}
}
if(plot == thePlot3)
{
if(currentIndex8>=kchannel4)
{
return kchannel4;
}else{
return currentIndex8;
}
}
if(plot == thePlot4)
{
if(currentIndex9>=kchannel5)
{
return kchannel5;
}else{
return currentIndex9;
}
}
return 0;
}
-(double)doubleForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx{
double num;
NSString *textval = [NSString stringWithFormat:#"%d", countofpktloss];
self.losscount.text = textval;
switch ( fieldEnum ) {
case CPTScatterPlotFieldX:
if(plot == thePlot){
num =currentIndex;
}
else if(plot == thePlot1)
{
num = currentIndex1;
}
else if(plot == thePlot2)
{
num = currentIndex2;
}
else if(plot == thePlot3)
{
num = currentIndex3;
}
else if(plot == thePlot4)
{
num = currentIndex4;
}
break;
case CPTScatterPlotFieldY:
if(plot == thePlot){
num =arrPlot[idx];
}
else
if(plot == thePlot1){
num =arrPlot1[idx];
}
else if(plot == thePlot2){
num =arrPlot2[idx];
}
else if(plot == thePlot3)
{
num =arrPlot3[idx];
}
else if(plot == thePlot4)
{
num =arrPlot4[idx];
}
break;
default:
break;
}
return num;
}

The collapsesLayers property is there to help save memory for a static graph. Using it on a graph that updates frequently makes the performance worse since it requires the graph to redraw everything, not just the part that changed, e.g., the plot.
Added comments
Make sure the timer stops when you expect it to. Otherwise, it will keep adding points to the plots.
How often does the timer fire to add points to the plots? There's no point in updating the plots more than 60 times per second and you may need to reduce that further on older devices like the iPad 2 to get good performance with lots of data points. You can add more than one point in each update if needed.
Check the ranges used to reload data points. You're passing the same number as the location and length of the range. I suspect you mean to use a length of one (1) for each range. If so, you're reloading more data than required on each pass.

Have you tried looking at other chart libraries? SciChart provides an iOS chart tailored for realtime ECG applications. It's extremely fast and doesn't have the performance problems of Core plot.
See iOS Chart Performance Comparison
and SciChart iOS ECG demo
Disclosure, I am the tech lead on the scichart projects

Related

Check if iOS Update is available using objective-c?

How can I compare installed iOS version with the latest iOS version programatically using objective-c?
You have to take new version number from web service.This is because if you want to update app from app store then you just need to increase your app version from your pannel and you will get it in web service and then you can check it like this (call this method by passing web service version) :-
(BOOL)isUpdateAvailable:(NSString*)latestVersion
{
NSString *currentAppVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];
NSArray *myCurrentVersion = [currentAppVersion componentsSeparatedByString:#"."];
NSArray *myLatestVersion = [latestVersion componentsSeparatedByString:#"."];
NSInteger legthOfLatestVersion = myLatestVersion.count;
NSInteger legthOfCurrentVersion = myCurrentVersion.count;
if (legthOfLatestVersion == legthOfCurrentVersion)
{
for (int i=0; i<myLatestVersion.count; i++)
{
if ([myCurrentVersion[i] integerValue] < [myLatestVersion[i] integerValue])
{
return true;
}
else if ([myCurrentVersion[i] integerValue] == [myLatestVersion[i] integerValue])
{
continue;
}
else
{
return false;
}
}
return false;
}
else
{
NSInteger count = legthOfCurrentVersion > legthOfLatestVersion ? legthOfLatestVersion : legthOfCurrentVersion;
for (int i=0; i<count; i++)
{
if ([myCurrentVersion[i] integerValue] < [myLatestVersion[i] integerValue])
{
return true;
}
else if([myCurrentVersion[i] integerValue] > [myLatestVersion[i] integerValue])
{
return false;
}
else if ([myCurrentVersion[i] integerValue] == [myLatestVersion[i] integerValue])
{
continue;
}
}
if (legthOfCurrentVersion < legthOfLatestVersion)
{
for (NSInteger i=legthOfCurrentVersion; i<legthOfLatestVersion; i++)
{
if ([myLatestVersion[i] integerValue] != 0)
{
return YES;
}
}
return NO;
}
else
{
return NO;
}
}
}
This will return bool value if it will return YES then you will have new version of your app and if NO then your app has updated version.

dump app call stack in “for in” only onetime

- (id)objectAtIndexSafe:(NSUInteger)index
{
if (index < [self count]) {
return [self objectAtIndexSafe:index];
}else {
TTSafeKitAssert(NO, #"unsafe");
return nil;
}
}
TTSafeKitAssert : send dumped threads info(use PlCashReporter) to sever,but when objectAtIndexSafe called in for in,TTSafeKitAssert will send so many times . How can i just send once in this situation!
If it is a category:
- (id)objectAtIndexSafe:(NSUInteger)index
{
if (index < [self count]) {
return [self objectAtIndex:index];
}else {
TTSafeKitAssert(NO, #"unsafe");
return nil;
}
}

Can an asynchronous task be run in a loop? (in iOS)

basically I'm using firebase to query a user's 'status' property and doing so in a do/while loop.
If the status property is free then i want to break the loop and continue with the rest of the method. If the status property is not free then I want to query firebase again for a new user until a free user is found.
My firebase code works fine outside the loop but doesn't seem to be called inside of it. Here is the loop:
__block uint32_t rando;
self.freedom = #"about to check";
do {
//check if free
[self checkIfFree:^(BOOL finished) {
if (finished) {
if ([self.freedom isEqualToString:#"free"]) {
//should break loop here
}
else if ([self.freedom isEqualToString:#"matched"]){
//get another user
do {
//picking another random user from array
rando = arc4random_uniform(arraycount);
}
while (rando == randomIndex && rando == [self.randString intValue]);
self.randString = [NSString stringWithFormat:#"%u", rando];
[users removeAllObjects];
[users addObject:[usersArray objectAtIndex:rando]];
self.freeUser = [users objectAtIndex:0];
//should repeat check here but doesn't work
}
else{
NSLog(#"error!");
}
}
else{
NSLog(#"not finished the checking yet");
}
}];
} while (![self.freedom isEqual: #"free"]);
And here's my firebase code:
-(void)checkIfFree:(myCompletion) compblock{
self.freeUserFB = [[Firebase alloc] initWithUrl:[NSString stringWithFormat: #"https://skipchat.firebaseio.com/users/%#", self.freeUser.objectId]];
[self.freeUserFB observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot)
{
self.otherStatus = snapshot.value[#"status"];
NSLog(#"snapshot info %#", snapshot.value);
if ([self.otherStatus isEqualToString:#"free"]) {
self.userIsFree = YES;
self.freedom = #"free";
}
else{
self.userIsFree = NO;
self.freedom = #"matched";
}
compblock(YES);
}];
}
Thanks!
I am not sure I understand correctly your question.
If you want to run your completion code again till some condition is matched (in this case [self.freedom isEqualToString:#"free"]) you can do the following (removing the do - while):
void( ^ myResponseBlock)(BOOL finished) = ^ void(BOOL finished) {
if (finished) {
if ([self.freedom isEqualToString: #"free"]) {
return;
} else if ([self.freedom isEqualToString: #"matched"]) {
//get another user
do {
//picking another random user from array
rando = arc4random_uniform(arraycount);
}
while (rando == randomIndex && rando == [self.randString intValue]);
self.randString = [NSString stringWithFormat: #"%u", rando];
[users removeAllObjects];
[users addObject:usersArray[rando]];
self.freeUser = users.firstObject;
// Schedule another async check
[self checkIfFree: myResponseBlock];
} else {
NSLog(#"error!");
}
} else {
NSLog(#"not finished the checking yet");
}
};
[self checkIfFree: myResponseBlock];

Pagination UIScrollView

Need some help with a complex issue on my paginated scrollview. Im adding multiple pages with tableviews and some more data that could be heavy for memory. But it building up memory with each scroll. I would like my code to only add a subview once and on scroll replace it and release the old on (im using ARC). But the memory is building up, i think its because its adding the subview multiple times.
- (void)scrollViewDidScroll:(UIScrollView *)sender
{
#synchronized(self) {
#autoreleasepool {
CGFloat pageWidth = mainScrollView.frame.size.width;
NSInteger page = round(mainScrollView.contentOffset.x / pageWidth);
int scrollOffset = (page * barWidth)-(mainScrollView.bounds.size.width/2);
if (scrollOffset<=0) {
scrollOffset = 0;
} else if (scrollOffset >= (pageIndicatorScrollView.contentSize.width - pageIndicatorScrollView.frame.size.width)) {
scrollOffset = (pageIndicatorScrollView.contentSize.width - pageIndicatorScrollView.frame.size.width);
}
[pageIndicatorScrollView setContentOffset:CGPointMake(scrollOffset, 0) animated:YES];
if(page < 0)
{
page = 0;
}
if(page >= [categoryScrollersArray count])
{
page = [categoryScrollersArray count] - 1;
}
//page = page +1;
//////////NSLog(#"page = %d",page);
// don't create or delete pages while rotating
if (!isRotating) {
if([categoryScrollersArray count] == 1)
{
if([categoryScrollersArray objectAtIndex:0] == [NSNull null])
{
[self loadScrollViewWithPage:0];
}
}
else if([categoryScrollersArray count] == 2)
{
if([categoryScrollersArray objectAtIndex:0] == [NSNull null])
{
[self loadScrollViewWithPage:0];
}
if([categoryScrollersArray objectAtIndex:1] == [NSNull null])
{
[self loadScrollViewWithPage:1];
}
}
else
{
if(page - 1 < 0)
{
if([categoryScrollersArray objectAtIndex:0] == [NSNull null])
{
[self loadScrollViewWithPage:0];
}
if([categoryScrollersArray objectAtIndex:1] == [NSNull null])
{
[self loadScrollViewWithPage:1];
}
}
else
{
if([categoryScrollersArray objectAtIndex:page - 1] == [NSNull null])
{
[self loadScrollViewWithPage:page - 1];
}
if([categoryScrollersArray objectAtIndex:page] == [NSNull null])
{
[self loadScrollViewWithPage:page];
}
if(page + 1 < [categoryScrollersArray count])
{
if([categoryScrollersArray objectAtIndex:page + 1] == [NSNull null])
{
[self loadScrollViewWithPage:page + 1];
}
}
}
if(page -2 >= 0 )
{
if([categoryScrollersArray objectAtIndex:page -2] != [NSNull null])
{
UIView *u = [categoryScrollersArray objectAtIndex:page-2];
//////////NSLog(#"remove page = %d",page-2);
[u removeFromSuperview];
if (page-2 < [categoryScrollersArray count]) {
if ([categoryScrollersArray objectAtIndex:page-2] == nil) {
[self showUnexpectedBehaviorAlertWithTest:#"An unknown error occured, please reopen the questionnaire. (1307)"];
}
else {
[categoryScrollersArray replaceObjectAtIndex:page-2 withObject:[NSNull null]];
}
}
else {
[self showUnexpectedBehaviorAlertWithTest:#"An unknown error occured, please reopen the questionnaire. (1314)"];
}
}
}
if(page +2 < [categoryScrollersArray count])
{
if([categoryScrollersArray objectAtIndex:page +2] != [NSNull null])
{
UIView *u = [categoryScrollersArray objectAtIndex:page+2];
// [u removeObserver:u.self forKeyPath:#"doReload"];
//////////NSLog(#"remove page = %d",page+2);
[u removeFromSuperview];
if (page+2 < [categoryScrollersArray count]) {
if ([categoryScrollersArray objectAtIndex:page+2] == nil) {
[self showUnexpectedBehaviorAlertWithTest:#"An unknown error occured, please reopen the questionnaire. (1329)"];
}
else {
[categoryScrollersArray replaceObjectAtIndex:page+2 withObject:[NSNull null]];
}
}
else {
[self showUnexpectedBehaviorAlertWithTest:#"An unknown error occured, please reopen the questionnaire. (1337)"];
}
}
}
}
}
[self changeProgressBar:page];
// ////////NSLog(#"viewControllers are %#",viewControllers);
// ////////NSLog(#"questionsScroll.subviews %#",questionsScroll.subviews);
}
}
}
And the other function:
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0)
return;
if (page >= [categoryScrollersArray count])
return;
//Categories * anCat;
//anCat = [catArray objectAtIndex:page];
// replace the placeholder if necessary
CategoryScrollerView *catScroll = [categoryScrollersArray objectAtIndex:page];
if ((NSNull *)catScroll == [NSNull null])
{
Step *aStep = [steps objectAtIndex:page];
NSString *coder = [[NSString alloc] init];
if ([self.visit.savedStatus integerValue] == 2 && [self.visit.state integerValue] == 1) {
coder = #"SAVED";
} else {
coder = #"OPEN";
}
NSString *questCode = [NSString stringWithFormat:#"%#",self.visit.questionnaire.version];
catScroll = [[CategoryScrollerView alloc] initWithFrame:mainScrollView.frame andStep:aStep andDelegate:self andStepname:aStep.name andVisitcode:coder andQuestionnairecode:questCode];
catScroll.autoresizesSubviews = YES;
catScroll.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
[self setNextBtnTitleForPage:page andView:catScroll];
if (page < [categoryScrollersArray count]) {
if ([categoryScrollersArray objectAtIndex:page] == nil) {
[self showUnexpectedBehaviorAlertWithTest:#"An unknown error occured, please reopen the questionnaire. (1101)"];
}
else {
[categoryScrollersArray replaceObjectAtIndex:page withObject:catScroll];
}
}
else {
[self showUnexpectedBehaviorAlertWithTest:#"An unknown error occured, please reopen the questionnaire. (1108)"];
}
}
// add the controller's view to the scroll view
if (catScroll.superview == nil)
{
CGRect frame = mainScrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
catScroll.frame = frame;
[mainScrollView addSubview:catScroll];
// NSDictionary *numberItem = [self.contentList objectAtIndex:page];
// controller.numberImage.image = [UIImage imageNamed:[numberItem valueForKey:ImageKey]];
// controller.numberTitle.text = [numberItem valueForKey:NameKey];
}
}
Doing this inside one view controller is going to be really complex. Especially if you're trying to do all the tableview datasource stuff in there too.
You would be much better off using a UIPageViewController.
It is a container controller (a lot like UINavigationController or UITabBarController) that is used to display view controllers in a paged scroll view type of view.
There are some tutorials here about using UIPageViewController. http://www.raywenderlich.com/?s=uipageviewcontroller&cof=FORID%3A10
Essentially you pass a different table view controller in for each page. It takes a bit of settings up but once it's done it will help make it much easier and will keep memory usage down.
There are lots more tutorials out there too... http://www.appcoda.com/uipageviewcontroller-tutorial-intro/
Also, you don't have to make it full screen. You can use the Page View Controller for only the part of the screen that you want to scroll.

Sort NSArray of NSDictionaries using comparator

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;
}];

Resources