How to sort distance in sectional table view? - objective-c

I'm making app with section tableview.The section header display district through keyArray. To row of table view, it will display all information including of name, address and distance. I can make it by below code.
Now, I'm going to sort ascending distance in tableview.
How can I sort it in 'cellForRowAtIndexPath' method?
My second thought is create NSArray at the outside of cellForRowAtIndexPath method. One is to load plist data and calculate distance. And then, sort it through NSSortDescriptor. If I go to this method, I am not sure how to populate it in section tableview correctly?
Can someone give me some idea or suggestion?
In key array, I use below code to create it in ViewDidLoad and put it into section header.
//location info draw from plist
NSArray*tempArray=[[NSArray alloc]init];
tempArray=[dataDictionary allKeys];
self.keyArray=[tempArray mutableCopy];
In cellForRowAtIndexPath, it calculate distance between target location and user location. And then display all information in table view row.
NSString*sectionHeader=[keyArray objectAtIndex:indexPath.section];
NSArray*sectionHeaderArray=[dataDictionary objectForKey:sectionHeader];
NSDictionary*targetLat=[sectionHeaderArray objectAtIndex:indexPath.row];
NSDictionary*targetLong=[sectionHeaderArray objectAtIndex:indexPath.row];
//location info draw from plist
CLLocation *targetLocation = [[CLLocation alloc] initWithLatitude:[[targetLat objectForKey:#"latitude"] floatValue] longitude:[[targetLong objectForKey:#"longitude"] floatValue]];
double distance = [self.myLocation distanceFromLocation:targetLocation]/1000;
UITableViewCell*cell=[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil) {
cell=[[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:CellIdentifier] autorelease];
}
UILabel*nameLabel=(UILabel*)[cell viewWithTag:100];
UILabel*addLabel=(UILabel*)[cell viewWithTag:101];
UILabel*latLabel1=(UILabel*)[cell viewWithTag:102];
UILabel*longLabel1=(UILabel*)[cell viewWithTag:103];
UILabel*disLabel=(UILabel*)[cell viewWithTag:106];
NSDictionary*name=[sectionHeaderArray objectAtIndex:indexPath.row];
NSDictionary*address=[sectionHeaderArray objectAtIndex:indexPath.row];
nameLabel.text=[name objectForKey:#"name"];
addrLabel.text=[address objectForKey:#"address"];
latLabel1.text=[NSString stringWithFormat:#"%f",self.myLocation.coordinate.latitude];
longLabel1.text=[NSString stringWithFormat:#"%f",self.myLocation.coordinate.longitude];
disLabel.text=[NSString stringWithFormat:#"%0.2f km",distance];
return cell;
}

I jumped to conclusions and posted an answer that was flawed. I see now that you have a multitude of issues in your process.
You assign the same object, [sectionHeaderArray objectAtIndex:indexPath.row], to several different variables and are expecting different results from each. To start with, I would make the following changes to your existing code:
NSString*sectionHeader=[keyArray objectAtIndex:indexPath.section];
NSArray *locationsForSection = [self.dataDictionary objectForKey:sectionHeader];
NSDictionary *thisLocationInfo = [locationsForSection objectAtIndex:indexPath.row];
float thisLat = [[thisLocationInfo objectForKey:#"latitude"] floatValue];
float thisLong = [[thisLocationInfo objectForKey:#"longitude"] floatValue];
//location info draw from plist
CLLocation *targetLocation = [[CLLocation alloc] initWithLatitude:thisLat longitude:thisLong];
double distance = [self.myLocation distanceFromLocation:targetLocation]/1000;
UITableViewCell*cell=[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil) {
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:CellIdentifier];
}
UILabel*nameLabel=(UILabel*)[cell viewWithTag:100];
UILabel*addLabel=(UILabel*)[cell viewWithTag:101];
UILabel*latLabel1=(UILabel*)[cell viewWithTag:102];
UILabel*longLabel1=(UILabel*)[cell viewWithTag:103];
UILabel*disLabel=(UILabel*)[cell viewWithTag:106];
NSDictionary*name=[sectionHeaderArray objectAtIndex:indexPath.row];
NSDictionary*address=[sectionHeaderArray objectAtIndex:indexPath.row];
latLabel1.text=[NSString stringWithFormat:#"%f",thisLat];
longLabel1.text=[NSString stringWithFormat:#"%f",thisLong];
disLabel.text=[NSString stringWithFormat:#"%0.2f km",distance];
return cell;
}
For your sorting issue, I would say to go back to your dataDictionary. Iterate through each of the sectionHeader arrays. Replace each array with a copy that is sorted by the distance from myLocation. You could add the key, distance, to each dictionary there and take that out of the cellForRowAtIndexPath method entirely.
-(NSDictionary *)dataDictionary
{
if (_dataDictionary) {
return _dataDictionary;
}
NSMutableDictionary *resultDictionary = [[NSMutableDictionary alloc] init];
for (NSString *sectionHead in self.keyArray) {
NSMutableArray *sectionArrayWithDistances = [[NSMutableArray alloc] init];
NSArray *thisSectionArray = [sourceDictionary objectForKey:sectionHead];
for (NSDictionary *theLocationDict in thisSectionArray) {
// Calculate the distance
float thisLat = [[theLocationDict objectForKey:#"latitude"] floatValue];
float thisLong = [[theLocationDict objectForKey:#"longitude"] floatValue];
CLLocation *targetLocation = [[CLLocation alloc] initWithLatitude:thisLat longitude:thisLong];
double distance = [self.myLocation distanceFromLocation:targetLocation]/1000;
// Insert modified dictionary into the resulting section array
NSMutableDictionary *thisLocationWithDistance = [NSMutableDictionary dictionaryWithDictionary:theLocationDict];
[thisLocationWithDistance insertObject:[NSNumber numberWithDouble:distance] ForKey:#"distance"];
[sectionArrayWithDistances addObject:[NSDictionary dictionaryWithDictionary:thisLocationWithDistance]];
}
// Sort the resulting array for this section and insert in dataDict
NSSortDescriptor *sortByDistance = [[NSSortDescriptor alloc] initWithKey:#"distance" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortByDistance, nil];
[sectionArrayWithDistances sortWithDescriptors:sortDescriptors];
[resultDictionary insertObject:[NSArray arrayWithArray:sectionArrayWithDistances] forKey:sectionHead];
}
// Set result and return
self.dataDictionary = [NSDictionary dictionaryWithDictionary:resultDictionary];
return _dataDictionary;
}
Total Air Code and turned out to be more than I bargained for. Possible errors in syntax and what not but this is the basic idea as I see it for your purposes.

Related

custom Annotations being switched when reloaded on MKMapView

I've been having this issue for a couple of weeks now, and I still have not found an answer. on my MapView I have custom annotations, and when I hit the "reload button" all the information is correct as in the annotation "title, subtitle". but the annotation has changed. the annotations are in a NSMutableArray and I'm sure that the issue i am having revolves around that. here is the code I am using to reload the annotations.
so just prevent any confusion, my custom annotations work just fine when i first load the mapView. But once i hit the reload button, all the annotation's information like "location,title, subtitle" all that is correct, just the actual annotation has changed. Like all the annotations have been switched around.
if anyone can help, it would greatly be appreciated! thanks!
- (IBAction)refreshMap:(id)sender {
NSArray *annotationsOnMap = myMapView.annotations;
[myMapView removeAnnotations:annotationsOnMap];
[locations removeAllObjects];
[citiesArray removeAllObjects];
[self retrieveData];
}
-(void) retrieveData {
userLAT = [NSString stringWithFormat:#"%f", myMapView.userLocation.coordinate.latitude];
userLNG = [NSString stringWithFormat:#"%f", myMapView.userLocation.coordinate.longitude];
NSString *fullPath = [mainUrl stringByAppendingFormat:#"map_json.php?userID=%#&lat=%#&lng=%#",theUserID,userLAT,userLNG];
NSURL * url =[NSURL URLWithString:fullPath];
NSData *data = [NSData dataWithContentsOfURL:url];
json =[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
citiesArray =[[NSMutableArray alloc]init];
for (int i = 0; i < json.count; i++)
{
//create city object
NSString * eID =[[json objectAtIndex:i] objectForKey:#"userid"];
NSString * eAddress =[[json objectAtIndex:i] objectForKey:#"full_address"];
NSString * eHost =[[json objectAtIndex:i] objectForKey:#"username"];
NSString * eLat =[[json objectAtIndex:i] objectForKey:#"lat"];
NSString * eLong =[[json objectAtIndex:i] objectForKey:#"lng"];
NSString * eName =[[json objectAtIndex:i] objectForKey:#"Restaurant_name"];
NSString * eState = [[json objectAtIndex:i] objectForKey:#"type"];
NSString * annotationPic = [[json objectAtIndex:i] objectForKey:#"Annotation"];
NSString * eventID = [[json objectAtIndex:i] objectForKey:#"id"];
//convert lat and long from strings
float floatLat = [eLat floatValue];
float floatLONG = [eLong floatValue];
City * myCity =[[City alloc] initWithRestaurantID: (NSString *) eID andRestaurantName: (NSString *) eName andRestaurantState: (NSString *) eState andRestaurantAddress: (NSString *) eAddress andRestaurantHost: eHost andRestaurantLat: (NSString *) eLat andRestaurantLong: (NSString *) eLong];
//Add our city object to our cities array
// Do any additional setup after loading the view.
[citiesArray addObject:myCity];
//Annotation
locations =[[NSMutableArray alloc]init];
CLLocationCoordinate2D location;
Annotation * myAnn;
//event1 annotation
myAnn =[[Annotation alloc]init];
location.latitude = floatLat;
location.longitude = floatLONG;
myAnn.coordinate = location;
myAnn.title = eName;
myAnn.subtitle = eHost;
myAnn.type = eState;
myAnn.AnnotationPicture = annotationPic;
myAnn.passEventID = eventID;
myAnn.hotZoneLevel = hotZone;
[locations addObject:myAnn];
[self.myMapView addAnnotations:locations];
}
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *annotationIdentifier = #"AnnotationIdentifier";
MKAnnotationView *annotationView = (MKAnnotationView *) [self.myMapView
dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (!annotationView)
{
annotationView = [[MKAnnotationView alloc]
initWithAnnotation:annotation
reuseIdentifier:annotationIdentifier];
NSString *restaurant_Icon = ((Annotation *)annotation).AnnotationPicture;
NSString *restaurant_Callout = [NSString stringWithFormat:#"mini.%#",restaurant_Icon];
UIImage *oldImage = [UIImage imageNamed:restaurant_Icon];
UIImage *newImage;
CGSize newSize = CGSizeMake(75, 75);
newImage = [oldImage imageScaledToFitSize:newSize]; // uses MGImageResizeScale
annotationView.image= newImage;
annotationView.canShowCallout = YES;
UIImage *Mini_oldImage = [UIImage imageNamed:event_Callout];
UIImage *Mini_newImage;
CGSize Mini_newSize = CGSizeMake(30,30);
Mini_newImage = [Mini_oldImage imageScaledToFitSize:Mini_newSize]; // uses MGImageResizeScale
UIImageView *finalMini_callOut = [[UIImageView alloc] initWithImage:Mini_newImage];
annotationView.leftCalloutAccessoryView = finalMini_callOut;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = rightButton;
}
else
{
annotationView.annotation = annotation;
}
return annotationView;
}
If nothing else, you're setting the icon and the callout based upon the annotation, but only doing that in viewForAnnotation if the annotation was not dequeued. You really want to do any annotation-specific customization outside of that if block, in case an annotation view is reused.
Unrelated to your reported issue, there are a few other observations:
You probably should be doing retrieveData asynchronously so you don't tie up the main thread with your data retrieval/parsing. Go ahead and dispatch the adding of the entry to your array and adding the annotation to the map in the main queue, but the network stuff and should definitely be done asynchronously.
You probably should check to make sure data is not nil (e.g. no network connection or some other network error) because JSONObjectWithData will crash if you pass it a nil value.
Your use of locations seems unnecessary because you're resetting it for every entry in your JSON. You could either (a) retire locations entirely and just add the myAnn object to your map's annotations; or (b) initialize locations before the for loop. But it's probably misleading to maintain this ivar, but only populate it with the last annotation.

Sorting keys and cellForRowAtIndexPath

I'm new to iOS programming. I have to sort my keys in my NSDictionary or more like reverse the order of my keys.
What my current keys right now are like this;
Oh! And I do not have values for my keys as they are taken out from my XML.
NSDictionary (called GradDict);
2012 S1
2012 S2
I want it to be like this;
2012 S2
2012 S1
Here are my codes to sort it;
In parserDidEndDocument:
NSEnumerator *enumerator = [gradDict keyEnumerator];
id key;
while ((key = [enumerator nextObject])) {
NSLog(#"Key:%#",key);
NSLog(#"%#", [gradDict objectForKey:key]);
}
self.sort = [key sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
In cellForRowAtIndexPath:
ModuleCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ModuleCell"];
if (!cell)
{
cell = [[[NSBundle mainBundle] loadNibNamed:#"ModuleCell" owner:nil options:nil] objectAtIndex:0];
}
NSString *period = [self.sort objectAtIndex:indexPath.section];
NSMutableArray *periodArray = [gradDict objectForKey:period];
Module *m = [periodArray objectAtIndex:indexPath.row];
[cell.moduleNameLabel setText:[m moduleName]];
[cell.moduleCreditLabel setText:[[NSString alloc] initWithFormat:#"%d", [m moduleCredits]]];
[cell.moduleGradeLabel setText:[m moduleGrade]];
cell.contentView.backgroundColor = [UIColor colorWithRed:0.75 green:0.93 blue:1 alpha:1];
[self.tableView setSeparatorColor:[UIColor colorWithRed:0.55 green:0.55 blue:0.55 alpha:1]];
return cell;
Am I doing the sorting wrongly?
And how am I to do the cellForRowAtIndexPath?
If I understand correctly you need to sort the data in the dictionary, right?
But the dictionary sorting is not saved. You can sort the keys and then create an array.

Extract additional NSString from array of JSON parsing data for TableView

I'm parsing my json file and showing it in a grouped table view.
-(void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSDictionary *allDataDictionary = [NSJSONSerialization JSONObjectWithData:webData options:0 error:nil];
NSDictionary *parks = [allDataDictionary objectForKey:#"Parks"];
NSArray *arrayOfParks = [parks objectForKey:#"Park"];
for (NSDictionary *diction in arrayOfParks) {
NSString *hebName = [diction objectForKey:#"hebName"];
NSString *engName = [diction objectForKey:#"engName"];
NSString *latitude = [diction objectForKey:#"lat"];
NSString *longtitude = [diction objectForKey:#"long"];
[array addObject:hebName];
}
[[self myTableView] reloadData];
}
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *details = [array objectAtIndex:indexPath.row];
[[[UIAlertView alloc] initWithTitle:#"B7Tour" message:details delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
}
It works very well and I present in my table view the engName.
Clicking on the item at the table view will pop up an alert with the engName itself.
My question is: how to extract the particular latitude and longtitude and save them?
I know how to present them in the table view but I want to extract and save them for additional usage.
Any ideas?
You might want to have the array of dictionaries exist outside this method.
Then you can access them any time.
Then you can maintain another ivar that is your current selection in the table view.
From there it is pretty straight forward to call the objectForKey: on the currently selected object.

Annotations in MapView

I have this code:
-(void)handleLongPressGesture:(UIGestureRecognizer*)sender {
NSNumber* existingpoints = [[NSNumber alloc]init];
existingpoints =[NSNumber numberWithInt:0];
// This is important if you only want to receive one tap and hold event
if (sender.state == UIGestureRecognizerStateEnded)
{
[self.mapView removeGestureRecognizer:sender];
}
else {
do {
int z = 1;
existingpoints =[NSNumber numberWithInt:z];
// Here we get the CGPoint for the touch and convert it to latitude and longitude coordinates to display on the map
CGPoint point = [sender locationInView:self.mapView];
CLLocationCoordinate2D locCoord = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
// Then all you have to do is create the annotation and add it to the map
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init]; annotationPoint.coordinate = locCoord;
NSString *latitude = [[NSString alloc] initWithFormat:#"%f",locCoord.latitude];
NSString *longitude = [[NSString alloc] initWithFormat:#"%f", locCoord.longitude];
annotationPoint.title = #"Event";
annotationPoint.subtitle = [NSString stringWithFormat:#"%# & %#", latitude, longitude];
[mapView addAnnotation:annotationPoint];
[[NSUserDefaults standardUserDefaults]setObject:latitude forKey:#"FolderLatitude"];
[[NSUserDefaults standardUserDefaults]setObject:longitude forKey:#"FolderLongitude"];
} while ([existingpoints intValue] == 0);
}
}
...but the problem is that when I hold, and then drag more than one pin is added. I want to add only one pin. So I tried the do method but it doesn't work. I can't understand, because when I executed the code I turn the value of the NSNumber to 1, and the while says = 0 to run the code.
Please Help!!
Your current code is prone to have quite a number of memory leaks. For example:
NSNumber* existingpoints = [[NSNumber alloc] init];
existingpoints = [NSNumber numberWithInt:0];
Is leaking because you leave the first instance of existingpoints with retain value of 1 and not freeing it anywhere. Unless you're using ARC. You can optimize the above code with just one instruction:
NSNumber* existingpoints = [NSNumber numberWithInt:0];
And retain it if you need to keep it somewhere (but i belive it's not the case).
Analyzing the code, I'd recommend NOT to use existingpoints as an NSNumber. Use an NSInteger instead (which is not an object, just a typedef to long).
Here's my rewritten code:
-(void)handleLongPressGesture:(UIGestureRecognizer*)sender {
NSInteger existingpoints = 0;
// This is important if you only want to receive one tap and hold event
if (sender.state == UIGestureRecognizerStateEnded) {
[self.mapView removeGestureRecognizer:sender];
}
else {
do {
int z = 1;
existingpoints = z;
// Here we get the CGPoint for the touch and convert it to latitude and longitude coordinates to display on the map
CGPoint point = [sender locationInView:self.mapView];
CLLocationCoordinate2D locCoord = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
// Then all you have to do is create the annotation and add it to the map
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init];
annotationPoint.coordinate = locCoord;
NSString *latitude = [NSString stringWithFormat:#"%f",locCoord.latitude];
NSString *longitude = [NSString stringWithFormat:#"%f", locCoord.longitude];
annotationPoint.title = #"Event";
annotationPoint.subtitle = [NSString stringWithFormat:#"%# & %#", latitude, longitude];
[mapView addAnnotation:annotationPoint];
[[NSUserDefaults standardUserDefaults] setObject:latitude forKey:#"FolderLatitude"];
[[NSUserDefaults standardUserDefaults] setObject:longitude forKey:#"FolderLongitude"];
[annotationPoint release]; // Remove this if you're using ARC.
} while (existingpoints == 0);
}
}
Note that I've also changed the code for creating latitude and longitude for not to create any memory leaks when using ARC.
EDIT:
Further analyzing your code, I don't see why this method would be dropping two pins at once. Maybe you could check if your method is not being called twice?
More: Why do you have a do/while loop if you just want it to run once? (but maybe you're just paving your ground to further ahead)

UISlider core data programing

Im working on a simple "point based" app.
under settings the user set´s the number of points needed to get a "goodie" using a slider.
-(IBAction) sliderChanged: (id)sender {
UISlider *slider = (UISlider *) sender;
int progressAsInt =(int)(slider.value +0.5);
NSString *newText = [[NSString alloc] initWithFormat:#"%d",progressAsInt];
sliderLabel.text = newText;
[newText release];
this works fine, but how so i store the slider value in my core data model, and how do make my slider show the stored value when view loads.
hope u can help me out :-D
Hey gerry3 i found my error. i never set my toD-object in my settingsViewController, with:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription
entityForName:#"ToDo" inManagedObjectContext:_context]];
NSError *error = nil;
NSArray *array = [_context executeFetchRequest:request error:&error];
if (array == nil)
{
// Deal with error...
}
if(array.count > 0){
toDo = [array objectAtIndex:0];
} else { // no one to fetch - generate one
toDo = [NSEntityDescription
insertNewObjectForEntityForName:#"ToDo"
inManagedObjectContext:_context];
[toDo retain];
your code works like a charm .....
Thanks
Skov
The key here is that Core Data stores numeric attributes (e.g. integers, floats, etc) as NSNumber objects.
Say that your entity is called Record and it has a integer attribute called 'progress'.
If you create a managed object instance of Record named 'record', then you can set its progress like this:
[record setValue:[NSNumber numberWithInteger:progressAsInt] forKey:#"progress"];
When you want to update your view with the value from your model (usually in viewWillAppear:), you can get its progress like this:
NSNumber *progressNumber = [record valueForKey:#"progress"];
slider.value = [progressNumber floatValue];
Alternatively, if you generate the class files for the Record entity, you can just do:
record.progress = [NSNumber numberWithInteger:progressAsInt];
and:
slider.value = [record.progress floatValue];