To remove some annotations from an MKMapView, but not all of them, based on some condition, there seems to be 3 ways. I'd like to know which is the best one, and if there are any pitfalls with any of them. Thank you.
First way: removing annotations from annotations directly
for (id<MKAnnotation> annotation in self.mapView.annotations) {
if ([annotation isKindOfClass:[PinAnnotation class]]) {
[self.mapView removeAnnotation:annotation];
}
}
As the docs say, the mapView annotations property is readonly. So I assume it's a copy that I can safely manipulate.
Documentation: #property(nonatomic, readonly) NSArray <id<MKAnnotation>> *annotations
Second way: adding the unwanted annotation to an array first
NSInteger toRemoveCount = myMap.annotations.count;
NSMutableArray *toRemove = [NSMutableArray arrayWithCapacity:toRemoveCount];
for (id annotation in myMap.annotations){
if (annotation != myMap.userLocation){
[toRemove addObject:annotation];
}
}
[myMap removeAnnotations:toRemove];
This code is copied from the example found here
It seems safer, but there's the overhead of creating the mutable array. If it's not necessary, I'd rather avoid it.
Third way : filtering the array
[_mapView.annotations filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"!(self isKindOfClass: %#)", [MKUserLocation class]]];
(answer found here: https://stackoverflow.com/a/2915063/873436).
I didn't try out this one, but it seems rather elegant and powerful.
What's the best way?
Is it dangerous to remove annotations while itering through them?
Thank you for your knowledge and insights!
i am using here maps in my app and works fine. i pass destination coordinate from one view controller to another viewcontroller which contains here map and its methods. i tried to get the current location inside the method in the second view controller to calculate the roue, but current location always returns 0,0.please advice how to get current location inside the method where i pass the values from first viewcontroller
viewcontroller1:
- (IBAction)btn_navigate:(id)sender
{
NSLog(#"%#",_POiarray);
_VC=[[NaviMeVC alloc]init];
[_VC GetPoi:_POiarray];
[self.navigationController pushViewController:_VC animated:YES];
}
mapviewcontroller:
-(void)GetPoi:(NSMutableArray *)anArray
{
//get current location
NMAGeoPosition * position=[[NMAGeoPosition alloc]init];
position = [[NMAPositioningManager sharedPositioningManager] currentPosition];
_StartCoordinate=[[NMAGeoCoordinates alloc]initWithLatitude:position.coordinates.latitude longitude:position.coordinates.longitude];
//not working returns nil
[[NMAMapLoader sharedMapLoader] setDelegate:self];
_SelectedPOi=[[NSMutableArray alloc]init];
_SelectedPOi=anArray;
NSString *lat=[anArray valueForKey:#"latitude"];
CLLocationDegrees latitu=[lat doubleValue];
NSString *longi=[anArray valueForKey:#"longitude"];
CLLocationDegrees longit=[longi doubleValue];
_DestinationCoordinate=[[NMAGeoCoordinates alloc]initWithLatitude:latitu longitude:longit];
NSLog(#"%f %f",_DestinationCoordinate.latitude,_DestinationCoordinate.longitude);
NSNumber *lati = [NSNumber numberWithDouble:latitu];
NSNumber *longitu = [NSNumber numberWithDouble:longit];
NSDictionary *userLocation=#{#"lat":lati,#"long":longitu};
NMARoutingMode* routingMode = [[NMARoutingMode alloc] initWithRoutingType:NMARoutingTypeShortest
transportMode:NMATransportModeCar
routingOptions:0];
[self CalculateRoute:routingMode];
}
To start receiving positioning updates you need to call NMAPositioningManager startPositioning which I don't see in your sample code. More details in user guide link below. Please read the other instructions on that page to see if anything helps.
Another thing to check: have you added NSLocationWhenInUseUsageDescription into your projects Info.plist file to ensure your app can receive user location from CLLocationManager?
Also, this may seem obvious but please keep in mind that you will only be able to receive a valid value for currentPosition if you have a position fix.
Positioning User Guide
NMAPositioningManager Doxygen
I've googled but am unable to find a solution - looking for some help.
I have a database that includes various locations and distances between them:
beg_location end_location miles
pointA pointB 2
pointA pointC 3
pointB pointA 2
pointB pointC 1
pointC pointA 3
pointC pointB 1
I am using MagicalRecord to interface with CoreData - I just need to figure out how to best create an array containing each name (i.e. "PointA, pointB, pointC")
Here is my code:
LocationMiles location;
//Create ResultsController
NSFetchedResultsController *fetchedLocationsController = [LocationMiles MR_fetchAllSortedBy:#"beg_location" ascending:YES withPredicate:nil groupBy:#"end_school" delegate:nil];
//turn controller into array
NSArray *fetchedLocations = [fetchedLocationsController fetchedObjects];
//go through array
for (location in fetchedLocations){
NSLog(#"Here is a location: %#", location.beg_location);
}
Currently it gives me results - but they results are similar to:
Here is a location: pointA
Here is a location: pointA
Here is a location: pointB
Here is a location: pointB
I just want to get the array to read as pointA, pointB, pointC so I should have only 3 locations (I will be putting those into a UIPickverview later).
I'm sure something in my logic is just faulty here - I just can't figure out what.
The faulty logic is on the type of objects you put in your array:
NSArray *fetchedLocations is an array of LocationMiles
What you
want is an array of NSString
Also, you want a collection of object with no duplicate. This is what NSSet is.
// NSSet ensures there's only one occurence of each object
NSMutableSet *locationsStrings = [[NSMutableSet alloc] init];
//go through array and add the field you're interested in into set
for (location in fetchedLocations){
[locationsStrings addObject:location.beg_location];
}
// make whatever use of locationsStrings you need
Have you tried
[fetchedLocationsController.fetchRequest setReturnsDistinctResults:YES];
and thanks for looking...
I'm not even sure how to phrase this question, let alone search for the answer... I have tried, honestly, so all help needed!
It's probably pretty simple as I am sure this is a pattern that happens all the time:
I have an entity in my model (MainLevel*) that has a relationship to itself.
The entity is for levels of a law, and the only requirement is that each law has at least one (the top) level. Beyond that the number of sublevels is, technically, infinite (but in reality about 5 normally and probably no more than 8-10 at most). As might be expected each child level has only one parent (MainLevel.parentLevel) and any parent can have multiple (or zero) children (NSSet *childLevels).
What I would like to do is to get all the structure of this relationship to put in a UITableView or a collectionView.
I have a recursive function as follows:
- (NSDictionary *)getStructureOfLawForLevel:(MainLevel *)level
{
NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc]initWithCapacity:50];
MainLevel *currentLevel = level;
[mutableDict setObject:currentLevel.shortName forKey:#"name"];
if (level.childLevels) {
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [currentLevel.childLevels sortedArrayUsingDescriptors:sortdescriptors];
for (MainLevel *childLevel in children)
{
NSDictionary *dict = [self getStructureOfLawForLevel:childLevel];
[mutableDict setObject:dict forKey:#"sublevels"];
}
}
return [mutableDict copy];
}
Then in viewWillAppear: I have this:
self.structure = [self getStructureOfLawForLevel:self.selectedLevel]
With this I hope I am on the right lines...(untested due to another issue I am sorting right now).
I still cant figure out how to configure a UITableView or a UICollectionView from this though. I mean I am sure I can do it by adding a counter or two and getting the number of lines, and sections, that way. It just seems way, way overcomplicated and I am certain there must be a more obvious method I am just not seeing...
The only criteria for the data is that it must be ordered by the .order attribute of the entity instance, and that is not unique. I mean, for example, each childLevel can have a childLevel with order number 1. It is the order in THAT parent level.
Sorry if this has been asked a thousand times. I have tried to search for an answer but nothing seems to fint the search terms I am using.
I am not doing anything with this data except putting on screen, no editing, adding, deleting... Not sure if that is relevant.
Edit for clarity...
I am not looking to do a drill-down type table view. I want a snapshot of the whole structure in one view, and then I may need to drill down from that (using relationships to other entities in the model).
EDIT FOR MY SOLUTION
Here's what I ended up doing...
- (NSArray *)getStructureAsArrayForLevel:(MainLevel *)child
{
NSMutableArray *thisChildAndChildren = [[NSMutableArray alloc]initWithCapacity:2];
[thisChildAndChildren addObject:child];
if (child.childLevels)
{
// children exist
// sort the children into order
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [child.childLevels sortedArrayUsingDescriptors:sortdescriptors];
// get an array for each child via recursion
for (MainLevel *child in children)
{
[thisChildAndChildren addObject:[self getStructureAsArrayForLevel:child]];
}
}
return [thisChildAndChildren copy];
}
Then I am using similar recursive function to convert the array to NSAttributedString and display in textView.
I really DO NOT like recursion. I don't know why but I find it SOOOOOOO hard to get my head around the logic, and when it's done it seems so obvious... Go figure!
Thanks to everyone for suggestions, help etc...
If you can use a 3rd-party controller, take a look at TLIndexPathTools. It handles tree structures. For example, try running the Outline example project.
Your view controller would look something like this (not much to it):
#import "TLTableViewController.h"
#interface TableViewController : TLTableViewController
#end
#import "TableViewController.h"
#import "TLIndexPathTreeItem.h"
#implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
MainLevel *topLevel = nil;//get the top level object here
TLIndexPathTreeItem *topItem = [self treeItemForLevel:topLevel depth:0];
self.indexPathController.dataModel = [[TLTreeDataModel alloc] initWithTreeItems:#[topItem] collapsedNodeIdentifiers:nil];
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
// customize cell configuration here
TLIndexPathTreeItem *item = [self.dataModel itemAtIndexPath:indexPath];
MainLevel *level = item.data;
cell.textLabel.text = [level description];
}
- (TLIndexPathTreeItem *)treeItemForLevel:(MainLevel *)level depth:(NSInteger)depth
{
NSMutableArray *childItems = [NSMutableArray arrayWithCapacity:50];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [level.childLevels sortedArrayUsingDescriptors:sortdescriptors];
for (MainLevel *child in children) {
TLIndexPathTreeItem *childItem = [self treeItemForLevel:child depth:depth + 1];
[childItems addObject:childItem];
}
//set identifier to some unique identifier, if one exists. Otherwise, the item itself
//will be used as the identifier
id identifier = nil;
//set cell identifier based on depth. This can be set to something unique for each
//depth, or set to a constant value. If nil, the value "Cell" is assumed.
NSString *cellIdentifier = [NSString stringWithFormat:#"Cell%d", depth];
//pass "level" to the data argument. Or pass any other data, e.g. include the depth #[#(depth), level]
TLIndexPathTreeItem *item = [[TLIndexPathTreeItem alloc] initWithIdentifier:identifier sectionName:nil cellIdentifier:cellIdentifier data:level andChildItems:children];
return item;
}
#end
You can subclass TLTreeTableViewController instead of TLTableViewController if you want collapsable levels. Let me know if you need more help.
EDIT
Sorry, I missed the part that says you want to display it all at once. Basically, I think the easiest way to do this would be to basically have a recursive structure that gets the description of each object. This could be a string or even a UIView that you could then place inside your tableviewcell.
Lets stick with a dictionary for now. Each dictionary representation can have information about itself and its children. The template can be:
<LevelInfoDictionary>
<NSObject>someObjectThatRepresentsInfoAboutThisLevel
<NSArray>arrayOfInfoDictionariesThatRepresentChildren
</LevelInfoDictionary>
Then to implement your recursive method:
- (NSDictionary *)getLevelInfo
{
NSMutableArray *childInfo = [NSMutableArray array];
for(ClassName *child in self.children)
{
[childInfo addObject:[child getLevelInfo]];
}
return [NSDictionary dictionaryWithObjectsAndKeys:
<descriptionOfThisLevel>, #"CurrentLevel",
childInfo, #"children>, nil];
}
END EDIT
Basically, as some of these other guys have said, you should create your tableview that displays all of your top level objects. From there, after you select an object, you should be pushed to a new tableview that uses a different fetch request with the predicate like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"parent = %#",
selectedParentObject];
Then you can use sort descriptors to sort the NSFetchRequest you are using.
Alternatively, you could just fetch the children by using the property on the parent object and store that in an array sorted by your sort descriptors.
One other thing that I should mention is that currently the sort descriptor does not accomplish anything. You may not notice this because there are other parts of the design that you should change, but since an NSDictionary does not have an order (it is a hash table), sorting objects before placing them in a dictionary does nothing.
From the ViewController for the TableView or CollectionView you should start by showing all of the top level objects (No parent). From there as a user selects an object that parent becomes the current level and the ViewController should refresh its data source to show all of the child elements at that level. You can then traverse back up to the parent via back button.
Let me know if you need any more detail.
Your loop doesn't make sense, because you want a list of the dictionaries for the structure of the children, but what you actually do it to overwrite it each time. You probably want something like:
NSMutableArray *subLevels = [NSMutableArray array];
for (MainLevel *childLevel in children)
{
[subLevels addObject:[self getStructureOfLawForLevel:childLevel]];
}
[mutableDict setObject:subLevels forKey:#"sublevels"];
I guess you want to show each level in a table view and drill to another table view for each subsequent level. That should be simple based on the dictionary which gives you a name to display and an optional array which defines whether drilling is possible and the data to pass to the next view controller.
In case it helps someone else, here's what I ended up doing...
- (NSArray *)getStructureAsArrayForLevel:(MainLevel *)child
{
NSMutableArray *thisChildAndChildren = [[NSMutableArray alloc]initWithCapacity:2];
[thisChildAndChildren addObject:child];
if (child.childLevels)
{
// children exist
// sort the children into order
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [child.childLevels sortedArrayUsingDescriptors:sortdescriptors];
// get an array for each child via recursion
for (MainLevel *child in children)
{
[thisChildAndChildren addObject:[self getStructureAsArrayForLevel:child]];
}
}
return [thisChildAndChildren copy];
}
Then I am using similar recursive function to convert the array to NSAttributedString and display in textView.
I really DO NOT like recursion. I don't know why but I find it SOOOOOOO hard to get my head around the logic, and when it's done it seems so obvious... Go figure!
Thanks to everyone for suggestions, help etc...
EDIT
It isn't exactly right, as the first layer has a slightly different structure to those that follow. at some point I need to change this to have the top level as a directory and use the very first level as the key for the directory, then add the complete array as the object for that key. But it works... my brain aches... and I can live with it until I get around to changing it.
I have a mutablearray that is populated from sqlite db in ios. I have gotten the annotations to load and view properly. My question is how can I write a loop that will add annotations with the size of the array. I have tried the following code and get and display the last entry in the array
NSMutableArray *annotations=[[NSMutableArray alloc] init];
CLLocationCoordinate2D theCoordinate5;
MyAnnotation* myAnnotation5=[[MyAnnotation alloc] init];
for (int i = 0; i < _getDBInfo.count; i++) {
dbInfo *entity = [_getDBInfo objectAtIndex:i];
NSNumber *numlat=[[NSNumber alloc] initWithDouble:[entity.Latitude doubleValue]];
NSNumber *numlon=[[NSNumber alloc] initWithDouble:[entity.Longitude doubleValue]];
NSLog(#"%d",[_getDBInfo count]);
la=[numlat doubleValue];
lo=[numlon doubleValue];
theCoordinate5.latitude=la;
theCoordinate5.longitude=lo;
myAnnotation5.coordinate=theCoordinate5;
myAnnotation5.title=[NSString stringWithFormat:#"%#"entity.EntityNo];
myAnnotation5.subtitle=[NSString stringWithFormat:#"%#",entity.EntityName];
[mapView addAnnotation:myAnnotation5];
[annotations addObject:myAnnotation5];
}
I guess my question is how can I create and add to my view annotation objects based on the count in my array?
Any help is much appreciated.
I am new to iOS as well as programming so please be gentle.
You only have one myAnnotation5 object. When you set its coordinate, title, etc., you are setting it for that instance, which you happen to have added to annotations multiple times. Hence every entry in annotations will have the last set of properties you set - since every entry in annotations is actually the same object.
To remedy this, you need to create your myAnnotation5 object anew each iteration of the loop, i.e.
for (int i = 0; i < _getDBInfo.count; i++) {
MyAnnotation* myAnnotation5=[[MyAnnotation alloc] init];
...
myAnnotation5.coordinate=theCoordinate5;
myAnnotation5.title=[NSString stringWithFormat:#"%#", entity.EntityNo];
myAnnotation5.subtitle=[NSString stringWithFormat:#"%#", entity.EntityName];
...
[mapView addAnnotation:myAnnotation5];
}
Two asides:
I hope you are building with ARC, otherwise you are leaking memory left and right.
Since MKMapView has an -annotations property, there is likely little reason for you to keep your own annotations array - just keep a reference to mapView.
Move this line:
MyAnnotation* myAnnotation5=[[MyAnnotation alloc] init];
to inside the for-loop just before setting the properties on myAnnotation5.
The way it is right now, you are creating only one MyAnnotation object and modifying its properties repeatedly.