Refreshing annotations mix photos - ios

First time the view controller load, I download data from web service, then use the details to add annotations.
When I tap the button to download again the data and put in all over again, wrong photos each and every time.
I tried to clear the cache and it doesn't help, I tried a lot of ways.
Any ideas?
EDIT:
- (void)addCustomersToMap
{
marker = nil;
profilePics = [[NSMutableArray alloc] init];
if ([self.usersNearMe isKindOfClass:[NSDictionary class]])
{
NSDictionary *singleUser = (NSDictionary*)self.usersNearMe;
self.usersNearMe = [[NSArray alloc] initWithObjects:singleUser, nil];
}
if ([self.usersNearMe isKindOfClass:[NSArray class]])
{
for (int i = 0; i < self.usersNearMe.count; i++)
{
NSDictionary *dict = self.usersNearMe[i];
float lat = [[dict objectForKey:#"Lat"] floatValue];
float lon = [[dict objectForKey:#"Lon"] floatValue];
NSString *name = [dict objectForKey:#"Name"];
NSString *time = [dict objectForKey:#"Time"];
NSString *picUrl = [dict objectForKey:#"PictureUrl"];
[profilePics addObject:picUrl];
CLLocationCoordinate2D mLoc = CLLocationCoordinate2DMake(lat, lon);
if ([picUrl isEqualToString:#"empty"])
{
marker = [[CustomerMarker alloc] initWithLocation:mLoc title:name andSubtitle:time andUrlString:nil andImagePerson:[UIImage imageNamed:#"user"]];
}
else if ([picUrl isEqualToString:#"system"])
{
marker = [[CustomerMarker alloc] initWithLocation:mLoc title:name andSubtitle:time andUrlString:nil andImagePerson:[UIImage imageNamed:#"police_icon"]];
}
else
{
NSString *completeURL = #"";
completeURL = [completeURL stringByAppendingString:kPROFILE_IMAGE_URL];
completeURL = [completeURL stringByAppendingString:#"/2/"];
completeURL = [completeURL stringByAppendingString:picUrl];
completeURL = [completeURL stringByAppendingString:#".png"];
NSURL *url = [NSURL URLWithString:completeURL];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
if (responseData) {
NSString *imageName = completeURL.lastPathComponent;
NSString *imagePath = [[self documentsDirectoryPath] stringByAppendingString:imageName];
NSLog(#"imagePath: %#", imagePath);
[responseData writeToFile:imagePath atomically:YES];
marker = [[CustomerMarker alloc] initWithLocation:mLoc title:name andSubtitle:time andUrlString:nil andImagePerson:[UIImage imageWithContentsOfFile:imagePath]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.btnRefreshUsers setImage:[UIImage imageWithContentsOfFile:imagePath] forState:UIControlStateNormal];
[self.mapView viewForAnnotation:marker];
[self.mapView addAnnotation:marker];
});
}
}];
}
}
}
self.btnRefreshUsers.userInteractionEnabled = YES;
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
dispatch_async(dispatch_get_main_queue(), ^{
if ([annotation isKindOfClass:[MKUserLocation class]])
{
[self.mapView selectAnnotation:((MKUserLocation*) annotation) animated:YES];
}
});
if ([annotation isKindOfClass:[MKUserLocation class]])
{
((MKUserLocation*) annotation).title = NSLocalizedString(#"current_location", nil);
return nil;
}
if ([annotation isKindOfClass:[CustomerMarker class]])
{
MKAnnotationView *pinView = nil;
static NSString *customerMarkerPinID = #"com.idm.customer";
pinView = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:customerMarkerPinID];
if (!pinView)
{
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:customerMarkerPinID];
}
pinView.canShowCallout = YES;
pinView.backgroundColor = [UIColor clearColor];
CustomerMarker *customerMarker = (CustomerMarker*)annotation;
pinView.image = customerMarker.imagePerson;
CGRect pinViewFrame = CGRectZero;
pinViewFrame.origin.x = pinView.frame.origin.x;
pinViewFrame.origin.y = pinView.frame.origin.y;
pinViewFrame.size.width = 40.0f;
pinViewFrame.size.height = 40.0f;
pinView.frame = pinViewFrame;
////////// Animate Pin Drop //////////
CGRect endFrame = pinView.frame;
pinView.frame = CGRectOffset(pinView.frame, 0, -230);
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.45f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
pinView.frame = endFrame;
[UIView commitAnimations];
/* CGRect endFrame = pinView.frame;
pinView.frame = CGRectOffset(pinView.frame, 0, -230);
[UIView animateWithDuration:0.45f delay:0 options:UIViewAnimationOptionShowHideTransitionViews animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
} completion:^(BOOL finished) {
if (finished) {
pinView.frame = endFrame;
}
}]; */
return pinView;
}
return nil;
}

There are two major problems here. First you're reusing a pinView but not setting the attributes according to the annotation that it is trying to draw. You're correct to dequeue a reusable annotationView, but if you reuse one or don't you will always need to set the image to the one you want to draw right now otherwise the pinView's image will be the one that it was previously. e.g.
if ([annotation isKindOfClass:[CustomerMarker class]])
{
MKAnnotationView *pinView = nil;//Create a new handle, don't risk having one global one
static NSString *customerMarkerPinID = #"com.idm.customer";
pinView = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:customerMarkerPinID];
if (!pinView)
{
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:customerMarkerPinID];
//Since all your pinViews can show callouts you only need to set this
//when creating a new one, not when reusing an existing one which will
// already have it set
pinView.canShowCallout = YES;
}
pinView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"map_pic"]];
...
return pinView;
}
//[self.mapView setNeedsDisplay]; <--- Don't do this
return nil;
Secondly you're choosing the image for each pin based on profilePicsCounter which is changed while you're adding the images but the mapView can and will ask for the views in any order it feels like not the order you add them. So you need to respond to viewForAnnotation by calculating the view based on the annotation it gives you. The clue is in the name.
This line:
NSString *urlString = profilePics[profilePicsCounter];
Needs to be something like
MyAnnotationClass *myAnno = (MyAnnotationClass)annotation
NSString *urlString = myAnno.urlString;
Essentially you've got to store the info it needs within the annotation.
Also, the annotations part of removeAllMarkers function can be shortened to one line
[self.mapView removeAnnotations:self.mapView.annotations];
And you're setting the backgroundColor on your pinViews twice

Related

Obj-C - All Custom MKMapView annotations not always showing on MapView?

I'm trying to display multiple arrays of locations on my mapView with the below code (Restaurants, Parks, Meet Ups & Stores). When I open the app, not all addresses inside the arrays are displayed on my mapView (even though all data is returned) - e.g. sometimes only 'Stores' and 'Parks' arrays of annotations will show up on my map, but Restaurants aren't visible (even though data for all arrays is returned successfully). If I close out and reopen the app, sometimes Parks will show on the map, but nothing else. Any idea why this happens, and how I can fix it? Code updated below. Been at this forever!
Note: ParksAnnotation, RestAnnotation, meetupAnn, & StoreAnnotation are all MKPointAnnotation classes.
UPDATE (10/13/2020): I tried logging 'placemarks' under the block of code that retrieves and geocodes my 'Stores' coordinates. It appears as though self.storeData is populated, as is the created NSDictionary storeFields. That said, when I log 'placemarks', it isn't returning any coordinates, even though storeFields[#"address"] is populated. The other blocks of code seem to be doing their job just fine (ie. retrieving Meet Ups and retrieving Parks). So Meet Ups, Restaurants and Parks annotations appear fine, but all Stores annotations aren't populated. This happens to at least one type of annotation (sometimes it's Stores that's missing, other times it's Parks) at random when I launch the app. I can't for the life of me sort out why this is happening.
See below code:
MapViewController.m
#import "StoreAnnotation.h"
#import "ParksAnnotation.h"
#import "RestAnnotation.h"
#import "meetupAnn.h"
#interface MapViewController ()
#end
#implementation MapViewController
-(void)viewWillAppear:(BOOL)animated {
NSMutableDictionary *viewParamsallUsers1 = [NSMutableDictionary new];
[viewParamsallUsers1 setValue:#"hosted_meet_ups" forKey:#"view_name"];
[DIOSView viewGet:viewParamsallUsers1 success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.meetUps = [responseObject mutableCopy];
int index = 0;
for (NSMutableDictionary *allMeetups in self.meetUps) {
NSString *location = allMeetups[#"where"];
NSString *userNames = allMeetups[#"node_title"];
NSString *userBio = allMeetups[#"body"];
self.alertMessage = [allMeetups[#"node_title"] mutableCopy];
CLGeocoder *geocoderFriend = [[CLGeocoder alloc] init];
[geocoderFriend geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:topResult];
MKCoordinateRegion region = self.mapView.region;
region.span.longitudeDelta /= 150.0;
region.span.latitudeDelta /= 150.0;
meetupAnn *meet = [[meetupAnn alloc] init];
meet.coordinate = placemark.coordinate;
meet.title = userNames;
meet.subtitle = userBio;
meet.index = index; // Store index here.
[self.mapView addAnnotation:meet];
}
}
];
index = index + 1;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
/// GRAB ALL RESTAURANT LOCATIONS ///
NSMutableDictionary *viewParams6 = [NSMutableDictionary new];
[viewParams6 setValue:#"restaurants" forKey:#"view_name"];
[DIOSView viewGet:viewParams6 success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.neighbourhoodData = [responseObject mutableCopy];
int index = 0;
for (NSMutableDictionary *multiplelocationsFriend in self.neighbourhoodData) {
NSString *location = multiplelocationsFriend[#"address"];
NSString *userNames = multiplelocationsFriend[#"node_title"];
NSString *ampRemoved = [userNames stringByReplacingOccurrencesOfString:#"amp;" withString:#""];
NSString *userBio = multiplelocationsFriend[#"body"];
self.x3 = multiplelocationsFriend[#"x3"];
CLGeocoder *geocoderFriend = [[CLGeocoder alloc] init];
[geocoderFriend geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:topResult];
MKCoordinateRegion region = self.mapView.region;
region.span.longitudeDelta /= 150.0;
region.span.latitudeDelta /= 150.0;
RestAnnotation *point1 = [[RestAnnotation alloc] init];
point1.coordinate = placemark.coordinate;
point1.title = ampRemoved;
point1.subtitle = userBio;
point1.index = index; // Store index here.
[self.mapView addAnnotation:point1];
}
}
];
index = index + 1;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
NSString *test = self.areaName;
NSLog(#"SHOW TEST %#", test);
/// GRAB ALL STORE LOCATIONS ///
NSMutableDictionary *viewParams7 = [NSMutableDictionary new];
[viewParams7 setValue:#"stores" forKey:#"view_name"];
[DIOSView viewGet:viewParams7 success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.storesData = [responseObject mutableCopy];
int index = 0;
for (NSMutableDictionary *storeFields in self.storesData) {
NSString *location = storeFields[#"address"];
NSString *userNames = storeFields[#"node_title"];
NSString *ampRemoved = [userNames stringByReplacingOccurrencesOfString:#"amp;" withString:#""];
NSString *userBio = storeFields[#"body"];
self.x3 = storeFields[#"x3"];
CLGeocoder *geocoderFriend = [[CLGeocoder alloc] init];
[geocoderFriend geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:topResult];
MKCoordinateRegion region = self.mapView.region;
region.span.longitudeDelta /= 150.0;
region.span.latitudeDelta /= 150.0;
StoreAnnotation *point2 = [[StoreAnnotation alloc] init];
point2.coordinate = placemark.coordinate;
point2.title = ampRemoved;
point2.subtitle = userBio;
point2.index = index; // Store index here.
[self.mapView addAnnotation:point2];
}
}
];
index = index + 1;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
/// GRAB ALL PARKS LOCATIONS ///
NSMutableDictionary *viewParams8 = [NSMutableDictionary new];
[viewParams8 setValue:#"parks" forKey:#"view_name"];
[DIOSView viewGet:viewParams8 success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.parksData = [responseObject mutableCopy];
int index = 0;
for (NSMutableDictionary *parkFields in self.parksData) {
// NSLog(#"WHAT IS IN FRIENDS %#", self.friendData);
NSString *location = parkFields[#"address"];
NSString *userNames = parkFields[#"node_title"];
NSString *ampRemoved = [userNames stringByReplacingOccurrencesOfString:#"amp;" withString:#""];
NSString *userBio = parkFields[#"body"];
self.x3 = parkFields[#"x3"];
CLGeocoder *geocoderFriend = [[CLGeocoder alloc] init];
[geocoderFriend geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:topResult];
MKCoordinateRegion region = self.mapView.region;
region.span.longitudeDelta /= 150.0;
region.span.latitudeDelta /= 150.0;
ParksAnnotation *point3 = [[ParksAnnotation alloc] init];
point3.coordinate = placemark.coordinate;
point3.title = ampRemoved;
point3.subtitle = userBio;
point3.index = index; // Store index here.
[self.mapView addAnnotation:point3];
}
}
];
index = index + 1;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
}
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{
MKCoordinateRegion mapRegion;
mapRegion.center = mapView.userLocation.coordinate;
mapRegion.span.latitudeDelta = 0.5;
mapRegion.span.longitudeDelta = 0.5;
[mapView setRegion:mapRegion animated: YES];
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, 8000, 8000);
[mapView setRegion:[mapView regionThatFits:region] animated:YES];
[mapView addAnnotations:[mapView annotations]];
});
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
if([annotation isKindOfClass:[StoreAnnotation class]]) {
static NSString *identifier = #"stores";
MKAnnotationView *storesView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(storesView == nil) {
storesView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
storesView.displayPriority = MKFeatureDisplayPriorityRequired;
storesView.canShowCallout = YES;
storesView.image = [UIImage imageNamed:#"storeann4.png"];
}
else {
storesView.annotation = annotation;
}
return storesView;
}
if([annotation isKindOfClass:[meetupAnn class]]) {
static NSString *identifier = #"meetUps";
MKAnnotationView *pulsingView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(pulsingView == nil) {
pulsingView.displayPriority = MKFeatureDisplayPriorityRequired;
pulsingView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
pulsingView.image = [UIImage imageNamed:#"meetupbeacon.png"];
pulsingView.canShowCallout = YES;
}
else {
pulsingView.annotation = annotation;
}
return pulsingView;
}
if([annotation isKindOfClass:[ParksAnnotation class]]) {
static NSString *identifier = #"parks";
MKAnnotationView *parksView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(parksView == nil) {
parksView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
parksView.displayPriority = MKFeatureDisplayPriorityRequired;
parksView.image = [UIImage imageNamed:#"parksann.png"];
parksView.canShowCallout = YES;
}
else {
parksView.annotation = annotation;
}
return parksView;
}
if([annotation isKindOfClass:[RestAnnotation class]]) {
static NSString *identifier = #"rests";
MKAnnotationView *restView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(restView == nil) {
restView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
restView.displayPriority = MKFeatureDisplayPriorityRequired;
restView.image = [UIImage imageNamed:#"restann.png"];
restView.canShowCallout = YES;
}
else {
restView.annotation = annotation;
}
return restView;
}
As Ramis said, the problem is that you're setting the displayPriority before you even instantiate the pulsingView. Thus, the MKAnnotationView you subsequently instantiate never got its displayPriority set.
That having been said, I'd suggest a slightly different implementation, though. Specifically, I'd move the configuration of the annotation view into its own subclass, rather than cluttering the view controller with this sort of code:
#interface MeetUpAnnotationView: MKAnnotationView
#end
#implementation MeetUpAnnotationView
- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self) {
self.displayPriority = MKFeatureDisplayPriorityRequired;
self.canShowCallout = YES;
self.image = [UIImage imageNamed:#"meetupbeacon.png"];
}
return self;
}
- (void)setAnnotation:(id<MKAnnotation>)annotation {
[super setAnnotation:annotation];
self.displayPriority = MKFeatureDisplayPriorityRequired;
}
#end
Then, if targeting iOS 11 or later, your view controller's viewDidLoad should register that subclass.
Now, if this is the only annotation view that you’re showing in iOS 11, you don't need a viewForAnnotation at all and can just register your default annotation view:
[self.mapView registerClass:[MeetUpAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultAnnotationViewReuseIdentifier];
And having registered the annotation view class, you're done. No viewForAnnotation is needed or desired.
The only time you have to implement viewForAnnotation in iOS 11 and later is if you have multiple custom annotation view types on your map. In that case, you'd register them:
[self.mapView registerClass:[MeetUpAnnotationView class] forAnnotationViewWithReuseIdentifier:meetupIdentifier];
[self.mapView registerClass:[SomeOtherAnnotationView class] forAnnotationViewWithReuseIdentifier:someOtherIdentifier];
And then use dequeueReusableAnnotationViewWithIdentifier:forAnnotation: in your viewForAnnotation:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MeetupAnn class]]) {
return [mapView dequeueReusableAnnotationViewWithIdentifier:meetupIdentifier forAnnotation:annotation];
}
if ([annotation isKindOfClass:[SomeOtherAnnotation class]]) {
return [mapView dequeueReusableAnnotationViewWithIdentifier:someOtherIdentifier forAnnotation:annotation];
}
...
return nil;
}
As you can see, in iOS 11 (especially when you have only one annotation view type), the code is greatly simplified.
But if you need to support older iOS versions, you can't register the identifier and you have to use the older dequeueReusableAnnotationViewWithIdentifier:, but I'd still let the annotation view subclass take care of configuring itself. But, a more subtle problem in your code is that you are not setting the annotation in an else clause, so make sure to do that, e.g.:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MeetupAnn class]]) {
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:meetupIdentifier];
if (!annotationView) {
annotationView = [[MeetUpAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:meetupIdentifier];
} else {
annotationView.annotation = annotation;
}
return annotationView;
}
return nil;
}
You should check if some of your geocoding requests are failing inconsistently.
CLGeocoder rate limits your requests and if you do too many requests in a short time, you'll receive an error. https://developer.apple.com/documentation/corelocation/clgeocoder/1423509-geocodeaddressstring?language=objc
Check if you are getting any reason for failure inside NSError* error inside the completionHandler block.
As many times as it fails, you should see as many less number of annotations on the MapView because it is not entering the following code path.
if (placemarks && placemarks.count > 0) {
// Not entering here
}
if (nil != error) {
// Could land here
}
The only other reason would be not properly calculating map region to show all annotations in screen. Make sure you are calculating/adjusting the region correctly upon each annotation being added to the mapView.
// Define these variables globally in the view controller
CLLocationDegrees minLatitude = 90.0;
CLLocationDegrees maxLatitude = -90.0;
CLLocationDegrees minLongitude = 180.0;
CLLocationDegrees maxLongitude = -180.0;
// Call following method every time a new annotation needs to be added to the mapView
-(void)addAnnotation:(id<MKAnnotation>)annotation toMapView:(MKMapView*)mapView {
// Add the annotation to map
[mapView addAnnotation:annotation];
// Set the map region to make it visible along with all other annotations
CLLocationDegrees latitude = annotation.coordinate.latitude;
CLLocationDegrees longitude = annotation.coordinate.longitude;
minLatitude = min(minLatitude, latitude);
maxLatitude = max(maxLatitude, latitude);
minLongitude = min(minLongitude, longitude);
maxLongitude = max(maxLongitude, longitude);
CLLocationDegrees latitudeDelta = (maxLatitude - minLatitude);
CLLocationDegrees longitudeDelta = (maxLongitude - minLongitude);
CLLocationDegrees midLatitude = (maxLatitude - latitudeDelta/2);
CLLocationDegrees midLongitude = (maxLongitude - longitudeDelta/2);
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(midLatitude, midLongitude);
MKCoordinateSpan span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
MKCoordinateRegion region = MKCoordinateRegionMake(center, span);
if (CLLocationCoordinate2DIsValid(center)) {
[mapView setRegion:region animated:YES];
}
}
You are assigning MKFeatureDisplayPriorityRequired to empty object. Try to assign display priority in two case:
When pulsingView is not nil.
After pulsingView allocated.
if([annotation isKindOfClass:[meetupAnn class]]) {
static NSString *identifier = #"currentLocation";
MKAnnotationView *pulsingView = (MKAnnotationView *)[self.friendsMapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(pulsingView == nil) {
pulsingView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
pulsingView.displayPriority = MKFeatureDisplayPriorityRequired;
pulsingView.canShowCallout = YES;
pulsingView.image = [UIImage imageNamed:#"meetupbeacon.png"];
NSLog(#"Location Returned");
} else {
pulsingView.displayPriority = MKFeatureDisplayPriorityRequired;
// TODO: Do map pin initial setup.
}
}

Obj-C - Custom Map Annotation Callout View from XIB

I'm trying to have a custom view/XIB display when an annotation on my MapView is tapped. That said, I've found various answers to this question for swift - but none for objective C.
Currently, I'm able to display custom annotations with the following code:
ViewController.m
-(void)viewDidLoad {
NSMutableDictionary *viewParamsFriend = [NSMutableDictionary new];
[viewParamsFriend setValue:#"accepted_friends" forKey:#"view_name"];
[DIOSView viewGet:viewParamsFriend success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.friendData = [responseObject mutableCopy];
int index = 0;
for (NSMutableDictionary *multiplelocationsFriend in self.friendData) {
NSString *location = multiplelocationsFriend[#"address2"];
NSString *userNames = multiplelocationsFriend[#"node_title"];
NSString *userBio = multiplelocationsFriend[#"body"];
CLGeocoder *geocoderFriend = [[CLGeocoder alloc] init];
[geocoderFriend geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:topResult];
MKCoordinateRegion region = self.friendsMapView.region;
region.span.longitudeDelta /= 150.0;
region.span.latitudeDelta /= 150.0;
PointAnnotation *point = [[PointAnnotation alloc] init];
point.coordinate = placemark.coordinate;
point.title = userNames;
point.subtitle = userBio;
point.index = index; // Store index here.
[self.friendsMapView addAnnotation:point];
}
}
];
index = index + 1;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
MKAnnotationView *pinView = (MKAnnotationView*)[self.friendsMapView dequeueReusableAnnotationViewWithIdentifier:#"AnnotationIdentifier"];
if (!pinView)
{
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"AnnotationIdentifier"];
pinView.image = [UIImage imageNamed:#"mapann3.png"];
} else {
pinView.annotation = annotation;
}
pinView.canShowCallout = YES;
pinView.calloutOffset = CGPointMake(0, 0);
UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"mapann3.png"]];
pinView.leftCalloutAccessoryView = iconView;
return pinView;
}
return nil;
}
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
UITapGestureRecognizer *tapGesture2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(calloutTappedTwo:)];
[view addGestureRecognizer:tapGesture2];
}
-(void)calloutTappedTwo:(UITapGestureRecognizer *) sender
{
MKAnnotationView *view = (MKAnnotationView*)sender.view;
id <MKAnnotation> annotation = [view annotation];
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
PointAnnotation *selectedPoint = (PointAnnotation *) view.annotation;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
OtherUserViewController *yourViewController = (OtherUserViewController *)[storyboard instantiateViewControllerWithIdentifier:#"OtherUserViewController"];
NSMutableDictionary *dictionary = self.friendData[selectedPoint.index];
yourViewController.frienduserData = dictionary;
[self.navigationController pushViewController:yourViewController animated:YES];
}
}
That said, if I want a custom XIB I've created to appear as the callout when each annotation is tapped, where/how should I call this?

How to add image in annotation in mkmap [duplicate]

This question already has answers here:
How to use custom icons with mapKit framework?
(2 answers)
Closed 8 years ago.
I am new to Xcode, i need to set the image in annotation point. I am trying to replace the typical iOS mapkit "pin" with a particular image. I'm new to coding so I'm not sure exactly why this isn't working, but here is what i've attempted
My code is
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *urlMapString=[NSString stringWithFormat:#"http://www.xxxx.com/xxxx_webservice/map.php?format=json&truckno=%#",nam2];
NSURL *urlMap=[NSURL URLWithString:urlMapString];
NSData *dataMap=[NSData dataWithContentsOfURL:urlMap];
NSError *errorMap;
NSDictionary *jsonMap = [NSJSONSerialization JSONObjectWithData:dataMap options:kNilOptions error:&errorMap]; NSArray *resultsMap = [jsonMap valueForKey:#"posts"];
NSArray *resMap = [resultsMap valueForKey:#"post"];
NSArray *latitudeString=[resMap valueForKey:#"latitude"];
NSString *latOrgstring = [latitudeString objectAtIndex:0];
double latitude=[latOrgstring doubleValue];
NSArray *longitudeString=[resMap valueForKey:#"longitude"];
NSString *longOrgstring = [longitudeString objectAtIndex:0];
double longitude=[longOrgstring doubleValue];
MKCoordinateRegion myRegion;
//Center
CLLocationCoordinate2D center;
center.latitude=latitude;
center.longitude=longitude;
//Span
MKCoordinateSpan span;
span.latitudeDelta=THE_SPAN;
span.longitudeDelta=THE_SPAN;
myRegion.center=center;
myRegion.span=span;
//Set our mapView
[MapViewC setRegion:myRegion animated:YES];
//Annotation
//1.create coordinate for use with the annotation
CLLocationCoordinate2D wimbLocation;
wimbLocation.latitude=latitude;
wimbLocation.longitude=longitude;
Annotation * myAnnotation= [Annotation alloc];
myAnnotation.image = [UIImage imageNamed:#"icon.gif"];
CLLocation *someLocation=[[CLLocation alloc]initWithLatitude:latitude longitude:longitude];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:someLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSDictionary *dictionary = [[placemarks objectAtIndex:0] addressDictionary];
addressOutlet=[dictionary valueForKey:#"Street"];
City=[dictionary valueForKey:#"City"];
State=[dictionary valueForKey:#"State"];
myAnnotation.coordinate=wimbLocation;
if (addressOutlet!=NULL&&City!=NULL)
{
myAnnotation.title=addressOutlet;
myAnnotation.subtitle=[NSString stringWithFormat:#"%#,%#", City, State];
myAnnotation.image = [UIImage imageNamed:#"icon.gif"];
}
else if (addressOutlet==NULL&&City!=NULL)
{
myAnnotation.title=City;
myAnnotation.subtitle=[NSString stringWithFormat:#"%#,%#", City, State];
myAnnotation.image = [UIImage imageNamed:#"icon.gif"];
}
else if (addressOutlet!=NULL&&City==NULL)
{
myAnnotation.title=addressOutlet;
myAnnotation.subtitle=[NSString stringWithFormat:#"%#", State];
myAnnotation.image = [UIImage imageNamed:#"icon.gif"];
}
else if(addressOutlet==NULL&&City==NULL)
{
myAnnotation.title=State;
myAnnotation.subtitle=[NSString stringWithFormat:#"%#",State];
myAnnotation.image = [UIImage imageNamed:#"icon.gif"];
}
[self.MapViewC addAnnotation:myAnnotation];
}];
}
Please guide me to set the image..
Thanks in Advance
You can use mapView Delegate method.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if (annotation == mapView.userLocation)
return nil;
static NSString *s = #"identifier";
MKAnnotationView *pin = [mapView dequeueReusableAnnotationViewWithIdentifier:s];
if (!pin) {
pin = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:s];
pin.canShowCallout = YES;
pin.image = [UIImage imageNamed:#"pin.png"];
pin.calloutOffset = CGPointMake(0, 0);
}
return pin;
}

Building MapView Annotations is very slow

I have an application which shows something around 100 Annotations (with custom pin-images, callout accessories and annotation-images) in a MapView. While building the annotations I store a link between annotation and building so I can assign the right building and open the right segue afterwards.
In iOS 6 they get built really fast, I also enabled animation while adding them, so one pin got dropped after the other, but with apple maps in iOS7 this isn't possible anymore (?). Now building those 100 annotations takes over 1 second on my iPhone 4S and that's too long. Is there anyway to improve the code?
- (void)viewDidLoad
...
//creating annotations
annotationlink = [[NSMutableArray alloc] init];
for (int i = 0; i < data.count; i++) {
NSDictionary *dataItem = [data objectAtIndex:i];
//storing annotation in array for link
Annotation *buildingannotation = [[Annotation alloc] init];
NSNumber *index = [NSNumber numberWithInteger:i];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:index, indexKey, buildingannotation, annotationKey, nil];
[annotationlink addObject:dict];
buildingannotation.title = [dataItem objectForKey:#"Building"];
buildingannotation.subtitle = [dataItem objectForKey:#"Info"];
MKCoordinateRegion buildingcoordinates;
buildingcoordinates.center.latitude = [[dataItem objectForKey:#"Latitude"] floatValue];
buildingcoordinates.center.longitude = [[dataItem objectForKey:#"Longitude"] floatValue];
buildingannotation.coordinate = buildingcoordinates.center;
[self.mapView addAnnotation:buildingannotation];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]){
return nil;
}
MKAnnotationView *pinView = (MKAnnotationView *)
[self.mapView dequeueReusableAnnotationViewWithIdentifier:pinIdentifier];
MKAnnotationView *customAnnotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pinIdentifier];
customAnnotationView.canShowCallout = YES;
//right button to detail view
UIButton* disclosureButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
customAnnotationView.rightCalloutAccessoryView = disclosureButton;
//left button for image
NSInteger *buildingindex = [self getIndex:annotation];
NSDictionary *dataItem = [data objectAtIndex:buildingindex];
NSString* filename = [dataItem objectForKey:#"Thumb"];
filename = [filename stringByAppendingString:#"#2x.jpg"];
NSString* resourceimagePath = [resourcePath stringByAppendingPathComponent:filename];
Image = [UIImage imageWithContentsOfFile:resourceimagePath];
UIImageView *AnnotationThumb = [[UIImageView alloc] initWithImage:Image];
AnnotationThumb.frame = CGRectMake(0, 0, 31, 31);
customAnnotationView.leftCalloutAccessoryView = AnnotationThumb;
//annotation image
customAnnotationView.image = [UIImage imageNamed:#"Annotation_white.png"];
return customAnnotationView;
return pinView;
}
the following function gets the index of the current annotation using nspredicate to filter the array with the dictionaries. the advantage of this is the fact, that i can also use it when calloutAccessoryControlTapped:
-(NSInteger*) getIndex:(Annotation*)searchannotation
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %#", annotationKey, searchannotation];
NSArray *filteredarray = [annotationlink filteredArrayUsingPredicate:predicate];
NSDictionary *building = [filteredarray objectAtIndex:0];
NSInteger *buildingIndex = [[building objectForKey:indexKey] integerValue];
return buildingIndex;
}
With an iPhone 4S the last pin is built 1.14 seconds after the view gets loaded.
if i search the annotation link array manually instead of using nspredicate function like this:
//left button for image
int buildingIndex;
for (int i = 0; i < annotationlink.count; i++) {
NSDictionary *annotationDict = [annotationlink objectAtIndex:i];
if ([[annotationDict objectForKey:annotationKey] isEqual:annotation]) {
buildingIndex= [[annotationDict objectForKey:indexKey] integerValue];
i = annotationlink.count;
}
}
NSDictionary *dataItem = [data objectAtIndex:buildingIndex];
the log says that the last pin is built 1.89 seconds after the viewDidLoad.
if i create the annotations in viewDidApper instead of viewDidLoad the View is shown off course immediately but the background takes some time to load so until the pins are dropped everything is gray which is also not very nice...
Thank you Anna for your suggestions! I implemented the improvements like this:
Annotation.h:
#import <MapKit/MKAnnotation.h>
#interface Annotation : NSObject <MKAnnotation> {}
#property(nonatomic, assign) CLLocationCoordinate2D coordinate;
#property(nonatomic, copy) NSString *title;
#property(nonatomic, copy) NSString *subtitle;
#property NSInteger *ID;
#end
Annotation.m:
#import "Annotation.h"
#implementation Annotation
#synthesize coordinate, title, subtitle, ID;
#end
ViewDidAppear:
//creating annotations
for (int i = 0; i < data.count; i++) {
NSDictionary *dataItem = [data objectAtIndex:i];
Annotation *buildingannotation = [[Annotation alloc] init];
buildingannotation.ID = i;
buildingannotation.title = [dataItem objectForKey:#"Building"];
buildingannotation.subtitle = [dataItem objectForKey:#"Subtitle"];
MKCoordinateRegion buildingcoordinates;
buildingcoordinates.center.latitude = [[dataItem objectForKey:#"Latitude"] floatValue];
buildingcoordinates.center.longitude = [[dataItem objectForKey:#"Longitude"] floatValue];
buildingannotation.coordinate = buildingcoordinates.center;
[self.mapView addAnnotation:buildingannotation];
}
viewForAnnotation:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]){
return nil;
}
MKAnnotationView *pinView = (MKAnnotationView *)
[self.mapView dequeueReusableAnnotationViewWithIdentifier:pinIdentifier];
if (pinView == nil) {
MKAnnotationView *customAnnotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pinIdentifier];
customAnnotationView.canShowCallout = YES;
//right button to detail view
UIButton* disclosureButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
customAnnotationView.rightCalloutAccessoryView = disclosureButton;
//left button for image
Annotation *buildingAnnotation = (Annotation *)annotation;
NSInteger *buildingindex = buildingAnnotation.ID;
NSString *filePath = [thumbname objectAtIndex:buildingindex];
UIImageView *AnnotationThumb = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:filePath]];
AnnotationThumb.frame = CGRectMake(0, 0, 31, 31);
customAnnotationView.leftCalloutAccessoryView = AnnotationThumb;
//annotation image
customAnnotationView.image = [UIImage imageNamed:#"Annotation_white.png"];
return customAnnotationView;
} else {
pinView.annotation = annotation;
}
return pinView;
}
Callout:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
Annotation *buildingAnnotation = (Annotation *)view.annotation;
selectedbuilding = buildingAnnotation.ID;
[self performSegueWithIdentifier:#"DetailViewController" sender:self];
}
Takes still some time for showing all Annotations. Is there any chance to further improve the code?
I updated the vievForAnnotation function regarding to Anna's reply and the PhotosByLocation Sample Application. It works now and I hope it's the correct way to implement the reuse...
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]){
return nil;
}
MKAnnotationView *buildingAnnotationView = (MKAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:pinIdentifier];
if (buildingAnnotationView) {
[buildingAnnotationView prepareForReuse];
} else {
buildingAnnotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pinIdentifier];
buildingAnnotationView.canShowCallout = YES;
//right button to detail view
UIButton* disclosureButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
buildingAnnotationView.rightCalloutAccessoryView = disclosureButton;
//annotation image
buildingAnnotationView.image = [UIImage imageNamed:#"Annotation_white.png"];
}
//left button for image
Annotation *buildingAnnotation = (Annotation *)annotation;
NSInteger *buildingindex = buildingAnnotation.ID;
NSString *filePath = [thumbname objectAtIndex:buildingindex];
UIImageView *AnnotationThumb = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:filePath]];
AnnotationThumb.frame = CGRectMake(0, 0, 31, 31);
buildingAnnotationView.leftCalloutAccessoryView = AnnotationThumb;
return buildingAnnotationView;
}

Loading online data very slow difficult to PUSH

First of all I am running a Map page that just only show pins on map for every store. I was running only one pin on the map and it was fast after I put more than 25 pins it push very slow to the map page. What it is doing now at that process the app just load all data of the pin location (as I see in the target output) and then it push to the next screen. So please where would be my problem?
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"";
self.navigationItem.title = #"Mek";
response = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://kalkatawi.com/mapLocation.php"]];
if(response!=nil) {
NSError *parseError = nil;
jsonArray = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingAllowFragments error:&parseError];
jsonArray1 = [[NSMutableArray alloc] init];
jsonArray2 = [[NSMutableArray alloc] init];
jsonArray3 = [[NSMutableArray alloc] init];
for(int i=0;i<[jsonArray count];i++)
{
name = [[jsonArray objectAtIndex:i] objectForKey:#"name"];
longitude = [[jsonArray objectAtIndex:i] objectForKey:#"longitude"];
latitude = [[jsonArray objectAtIndex:i] objectForKey:#"latitude"];
[jsonArray1 addObject:name];
[jsonArray2 addObject:longitude];
[jsonArray3 addObject:latitude];
self.locationMap.delegate = self; //set delegate before adding annotations
CLLocationCoordinate2D annotationCoord;
for (int i=0; i < [jsonArray count]; i++)
{
NSDictionary *annotationDictionary = [jsonArray objectAtIndex:i];
name = [annotationDictionary objectForKey:#"name"];
annotationCoord.latitude
= [[annotationDictionary objectForKey:#"longitude"] doubleValue];
annotationCoord.longitude
= [[annotationDictionary objectForKey:#"latitude"] doubleValue];
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init];
annotationPoint.coordinate = annotationCoord;
annotationPoint.title = name;
annotationPoint.subtitle = [NSString stringWithFormat:#"%f %f", annotationPoint.coordinate.latitude, annotationPoint.coordinate.longitude];
[self.locationMap addAnnotation:annotationPoint];
//------------------------//
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(annotationPoint.coordinate, 50000, 50000);
[self.locationMap setRegion:[self.locationMap regionThatFits:region] animated:YES];
locationManager = [[CLLocationManager alloc] init];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
[locationManager startUpdatingLocation];
lat = locationManager.location.coordinate.latitude;
lon = locationManager.location.coordinate.longitude;
}
}
}
else {
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"You are not connected to internet" message:#"Please check the internet connection" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
- (MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *pinView = nil;
if(annotation != locationMap.userLocation)
{
static NSString *defaultPinID = #"myPin";
pinView = (MKAnnotationView *)[locationMap dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if ( pinView == nil )
pinView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID] autorelease];
pinView.image = [UIImage imageNamed:#"pinpinpin.png"];
pinView.canShowCallout = YES;
pinView.enabled = YES;
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.rightCalloutAccessoryView = infoButton;
}
return pinView;
}
SImple way is load data in background thread, and then when data is fully load display it on the map. You can do this in any of view controller, means you can do it in parent view controller and when you got response then update on map view controller.
Or on view did load method load data in background and update it when its load. This approach will not hold your UI. You can use blocks like this
dispatch_async(queue, ^{
//load data from server
dispatch_async(dispatch_get_main_queue(), ^{
//Update map
});
});

Resources