I have a NSMutableArray wich holds a Custom class of MKMapkit.
MapPoint *placeObject = [[MapPoint alloc] initWithTitle:title subtitle:subtitle coordinate:loc description:description storeImage:storeImage];
[annotationArray addObject:placeObject];
My routine fills the placeObject without the image because they will load asynchronously and finished after a couple of seconds. Now my questions is: Is there a way to alter the placeObject which is within the annotationArray?
EDIT
To display my issue in a better way here some code parts:
if (annotationArray == nil)
{
[self setAnnotationArray:[[NSMutableArray alloc] init]];
}
for (NSDictionary *locationDetails in parser.items)
{
__block NSData *storeImageData;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
storeImageData = [NSData dataWithContentsOfURL:storeImageURL];
dispatch_sync(dispatch_get_main_queue(), ^{
UIImage *storeImageTMP = [UIImage imageWithData:storeImageData];
counterBlock ++;
NSLog(#"async: %d", counterBlock);
[imageArray addObject:storeImageTMP];
});
});
MapPoint *placeObject = [[MapPoint alloc] initWithTitle:title subtitle:subtitle coordinate:loc description:description storeImage:storeImage];
[annotationArray addObject:placeObject];
}
[mapView addAnnotations:annotationArray];
after this is done: dispatch_queue_t will start working. So I check every 2 seconds if its fully loaded. Then i start to alter annotationArray and refresh my annotations (remove all -> add to map witch additional image)
- (void) checkMap
{
if (counterBlock == 547)
{
for (int i=0; i<=counterBlock; i++)
{
MapPoint *point = annotationArray[i];
point.storeImage = imageArray[i];
}
if(timer)
{
[timer invalidate];
timer = nil;
}
[mapView removeAnnotations:mapView.annotations];
[mapView addAnnotations:annotationArray];
}
}
All you need to do is get a reference to one of the MapPoint objects in the array. Then you can set properties and/or call methods on the object as needed.
MapPoint *point = annotationArray[x]; // x is whatever index you need
point.image = ... // some image
You can retrieve from your mutable array, the object you want to alter with the objectAtIndex method
NSInteger indexofMyObject = 0;
MapPoint *point = [myArray objectAtIndex:indexofMyObject];
Your MapPoint instance placeObject is a pointer. So adding it to the array does not copy placeObject by value, but actually holds its address. Getting the address of the object from the array will allow you to manipulate it.
annotationArray[objectIndex].anything = anything you want
Related
This is my code for creating trackable POIs and starting a POI tracking.
This code is placed just before I would calculate a route and start navigation
-(void)createTrackablePOIs
{
// Go through the route array and create POIs out of all added exits and locations
if ([Constants shared].routeArray.count>2){
_trackablePOIs = [[NSMutableArray alloc] init];
for (int i=1; i<[Constants shared].routeArray.count-1; i++) {
if ([[Constants shared].routeArray[i] isKindOfClass:[Exit class]]){
Exit *e = [Constants shared].routeArray[i];
SKTrackablePOI* poi = [[SKTrackablePOI alloc] init];
poi.poiID = e.idExit;
poi.type = 1;
poi.coordinate = CLLocationCoordinate2DMake(e.lat, e.lon);
[self.trackablePOIs addObject:poi];
}else{
RoutePoint* rp = [Constants shared].routeArray[i];
SKTrackablePOI *poi = [[SKTrackablePOI alloc] init];
poi.poiID = rp.pointID;
poi.type = 2;
poi.coordinate = CLLocationCoordinate2DMake(rp.x,rp.y);
[self.trackablePOIs addObject:poi];
}
}
NSLog(#"%d POIs created and added to array",_trackablePOIs.count);
self.poiTracker = [[SKPOITracker alloc] init];
self.poiTracker.dataSource = self;
self.poiTracker.delegate = self;
// Create POI Tracking rules
SKTrackablePOIRule *rule = [SKTrackablePOIRule trackablePOIRule];
rule.routeDistance = 5000;
rule.aerialDistance = 5000;
[_poiTracker setRule:rule forPOIType:1];
// Start POI tracking
[_poiTracker startPOITrackerWithRadius:5000 refreshMargin:0.1 forPOITypes:#[#1,#2]];
}
}
-(NSArray*)poiTracker:(SKPOITracker *)poiTracker trackablePOIsAroundLocation:(CLLocationCoordinate2D)location inRadius:(int)radius withType:(int)poiType
{
return [self.trackablePOIs copy];
}
- (void)poiTracker:(SKPOITracker *)poiTracker didDectectPOIs:(NSArray *)detectedPOIs{
[detectedPOIs enumerateObjectsUsingBlock:^(SKDetectedPOI *detectedPOI, NSUInteger index, BOOL *stop){
NSLog(#"Detected: %#",[detectedPOI description]);
}];
NSLog(#"POI detected");
}
But POI delegate method is never called. No matter how much I played with trackable POI rules.
Also, another question, what exactly does the margin stands for in
[_poiTracker startPOITrackerWithRadius:5000 refreshMargin:0.1 forPOITypes:#[#1,#2]];
I'm working on an application for the iPhone and I'm keeping track of the users current location. When the didupdateLocations delegate method actually executes i would like to test if the location in the NSArray is in a predefined array the contains other locations, this array mind you could grow over time.
I'm running a for loop within this method to test against my own array of locations but i would like to move that to a separate thread. So in case my own array with multiple locations grows to a large number the for loop does not freeze my UI.
I have tried it like this but I'm getting undesirable results. I understand that the location tracking definitely happens in a separate thread. However those didupdateLocations execute on a separate thread. The Apple doc's are not very clear on the matter. My end goal again is to compare against my array and not lock the UI.
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)thisLocation {
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// get the last object in the array of locations
CLLocation* location = [thisLocation lastObject];
dispatch_async(queue, ^{
[self checkmyArray:location];
});
}
-(void)checkmyArray:(CLLocation *)workingLocation{
NSLog(#"SoundTheAlarm");
int alarm_on_c = 0;
NSUInteger tmp_count = [theData.LocationsObjectArray count];
BOOL alarm;
NSMutableDictionary * tempObject;
CLLocationDistance distance = 0.0;
for (int i = 0; i < tmp_count; i++) {
tempObject= [theData.LocationsObjectArray objectAtIndex:i];
thisLoc = [[tempObject objectForKey:#"onoff"] isEqual:#YES];
if (thisLoc) {
//check if we are near that location
double lat = [[tempObject objectForKey:#"latitude"] doubleValue];
double lon = [[tempObject objectForKey:#"longitude"] doubleValue];
// goal = [[CLLocation alloc] initWithLatitude:40.097771 longitude:-74.941399];
goal = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
// check the destination between current location and goal location - in meters
distance = [goal distanceFromLocation:workingLocation];
NSLog(#"distance %f\n", distance);
}
// if distance from goal is less than 350 meters
if (distance <= 350){
[self scheduleNotification:[tempObject objectForKey:#"name"]];
// turn off tracking for this location
[tempObject setObject:#NO forKey:#"onoff"];
[theData.LocationsObjectArray replaceObjectAtIndex:i withObject:tempObject];
NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
ExtendedSavedCellTableViewCell *cell = (ExtendedSavedCellTableViewCell *)[self.tableView cellForRowAtIndexPath:path];
cell.switchView.on = NO;
// save the update to the switch to the database as well
NSString *lat = [tempObject objectForKey:#"latitude"];
/*check to determine if the uiswitch is turned off or on.*/
[self fetchedResultsController:#NO lat:lat index:path];
[self displayAlertViewForAlarm:[tempObject objectForKey:#"name"]];
}
-(void)displayAlertViewForAlarm:(NSString *)nameOfLocation{
dispatch_async(dispatch_get_main_queue(), ^(void) {
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"Destination reached"
message:nameOfLocation
delegate:self
cancelButtonTitle:#"Go Away"
otherButtonTitles:#"Notify", nil];
[myAlert show];
});
}
It's generally a bad idea to use threads in iOS if you can at all avoid it. In your case, I'd implement the function which does the looping to automatically pop out of the loop after too many iterations and then schedule the next chunk of iterating to happen in another pass through the event loop. In other words, something like this:
- (void) checkLocationsStartingAt:(NSNumber)start
{
NSInteger s = (start) ? [start intValue] : 0;
for (i = s; i < list.length; i++) {
if (i > TOO_MANY) {
[self performSelector:#selector(checkLocationsStartingAt:)
withObject:#(i)
afterDelay:0.001];
return;
} else {
// check element at i
}
}
}
see: NSObject Reference
You are checking one location against an array of other locations.
You display an alert for time you are closer than 350m from a location.
You could be within 350m of many items in the list.
You have no code prevent multiple alerts.
So you will sometimes have many alerts.
You have a couple options. One will probably suit your needs better than the others. Possibly one I didn't list.
You could re-use a single UIAlertView instance and check the
visible property on it. If it's already visible, do nothing.
You could break out of the loop once you get a single "hit" on being
<350m.
You could stick in a BOOL which records if you've shown the alert and
check the BOOL every time before you show the alert again.
In my app, I download a set of point from a web service using json.
Actualizing the app to iOS7, I am experimenting that problem: the locations are downloaded but pins on the map are not painted until user touchs and "moves" the map. Then they appear and all work as in iOS6.
How can I correct that behavior?
EDIT:
AddAnnotation is called at the end of a method that receive data, parse the json and pass them to mylocaction object:
- (void)plotBarPosition:(NSString *)data_string {
// Parse the string into JSON
NSDictionary *json = [(NSDictionary*)[datos_string1 JSONValue]objectForKey:#"features"];
for (int i = 0; i < [json count]; i++){
/*
PARSING EACH POINT
*/
MyLocation *location =[[MyLocation alloc] initWithName:nameLoc coordinate:coordinate estado:status antenaId:antenaId];
[_mapView addAnnotation:location];
}
}
I tryed also:
[_mapView performSelectorOnMainThread: #selector(addAnnotations:) withObject: location waitUntilDone: NO];
but in this case, annotations do not appear at all.
As this answer and the comment by #RAJA suggest, call addAnnotation: or addAnnotations: on the main thread. You could do this using GCD or performSelectorOnMainThread.
The option with the least changes to your existing code is to call addAnnotation: (singular):
- (void)plotBarPosition:(NSString *)data_string {
// Parse the string into JSON
NSDictionary *json = [(NSDictionary*)[datos_string1 JSONValue]objectForKey:#"features"];
for (int i = 0; i < [json count]; i++){
/*
PARSING EACH POINT
*/
MyLocation *location =[[MyLocation alloc] initWithName:nameLoc coordinate:coordinate estado:status antenaId:antenaId];
//[_mapView addAnnotation:location];
[_mapView performSelectorOnMainThread:#selector(addAnnotation:)
withObject:location
waitUntilDone:YES];
}
}
Alternatively, to use addAnnotations: (plural), you first add your annotations to a local array and give them to the map view all together in a single call. Changes below are marked with >>>:
- (void)plotBarPosition:(NSString *)data_string {
// Parse the string into JSON
NSDictionary *json = [(NSDictionary*)[datos_string1 JSONValue]objectForKey:#"features"];
//>>> initialize array to hold annotations...
NSMutableArray *annotationsToAdd = [NSMutableArray array];
for (int i = 0; i < [json count]; i++){
/*
PARSING EACH POINT
*/
MyLocation *location =[[MyLocation alloc] initWithName:nameLoc coordinate:coordinate estado:status antenaId:antenaId];
//>>> comment out direct addAnnotation...
//[_mapView addAnnotation:location];
//>>> add annotation to local array...
[annotationsToAdd addObject:location];
}
//>>> call addAnnotations: (plural) on main thread...
[_mapView performSelectorOnMainThread:#selector(addAnnotations:)
withObject:annotationsToAdd
waitUntilDone:YES];
}
There is a protocol MKAnnotation - you have to determine you own class for annotation with realizing of this protocol:
#interface MyAnnotation : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
}
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
Then you have to init MyAnnotation and add it on map:
MyAnnotation *annotation = [[MyAnnotation alloc] initWithCoordinate:CLLocationCoordinate2DMake(latitude, longitude)];
[myMap addAnnotation:annotation];
Also try to call
[mapView setNeedsDisplay];
or
[mapView setNeedsLayout];
after adding annotations
I have a method which downloads some pics from a server. I had the buffer for the downloaded data defined withing async block (NSMutableArray *imgtmp) but haven't figured out how to get the array back out of there. What's the best way to access imgtmp in order to return it's contents, or set an instance variable from it?
I've been looking in the Apple Block docs but I must not be in the right section. Do I need to declare imgtmp using the __block keyword somehow? I tried that but imgtmp was still empty outside the block. Thanks!
EDIT: code updated with working model
- (void) loadImages
{
// temp array for downloaded images. If all downloads complete, load into the actual image data array for tablerows
__block NSMutableArray *imgtmp = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
int error = 0;
int totalitems = 0;
NSMutableArray *picbuf = [[NSMutableArray alloc] init];
for (int i=0; i < _imageURLS.count;i++)
{
NSLog(#"loading image for main image holder at index %i",i);
NSURL *mynsurl = [[NSURL alloc] initWithString:[_imageURLS objectAtIndex:i]];
NSData *imgData = [NSData dataWithContentsOfURL:mynsurl];
UIImage *img = [UIImage imageWithData:imgData];
if (img)
{
[picbuf addObject:img];
totalitems++;
}
else
{
NSLog(#"error loading img from %#", [_imageURLS objectAtIndex:i]);
error++;
}
}// for int i...
dispatch_async(dispatch_get_main_queue(),
^{
NSLog(#"_loadedImages download COMPLETE");
imgtmp = picbuf;
[_tvStatus setText: [NSString stringWithFormat:#"%d objects have been retrieved", totalitems]];
NSLog (#"imgtmp contains %u images", [imgtmp count]);
});// get_main_queue
});// get_global_queue
}
You're hitting that "final" NSLog call before any of your block code has executed. All your image loading stuff is wrapped in a dispatch_async. It is executed asynchronously whereas the NSLog is called right away.
I think the best thing for you to do is to pass imgtmp along to some persistent object. Perhaps your view controller can have a property like:
#property (nonatomic, copy) NSArray *images;
and you can assign that in the same block where you assign the text to _tvStatus.
My app is MapKit based, where multiple users can be tracked. Now using our web services, I am displaying my location on the map plus other users' last, let's say 10 locations. If a user updates their location, it is sent through the web service and displayed on the maps via call back. I am able to track other users in real time but don't know how to use Threading here. My UI is blocking at times and also crashing sometimes due to memory issue.
In my connectionDidFinishLoading method, I am parsing JSON data and then creating annotations and overlay:
-(void) connectionDidFinishLoading: (NSURLConnection *) connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSArray *trackingDict = [NSJSONSerialization JSONObjectWithData:empJsonData options:kNilOptions error:nil];
NSLog(#"Json Dictionary = %#", trackingDict);
NSLog(#"COUNT = %i",trackingDict.count);
if ([trackingDict count] >= 2) {
for (trackUsersCount = 0; trackUsersCount< trackingDict.count; trackUsersCount++) {
NSLog(#"trackUsersCount %i", trackUsersCount);
NSMutableArray *latlongArray = [[NSMutableArray alloc]init];
latlongArray = [[trackingDict objectAtIndex:trackUsersCount]objectForKey:#"latlong"];
[userLongitudeArray removeAllObjects];
[userLatitudeArray removeAllObjects];
for (int i = 0; i<latlongArray.count; i++) {
NSLog(#"COunt - > %#", [[latlongArray objectAtIndex:i]objectForKey:#"lat"]);
NSLog(#"COunt - > %#", [[latlongArray objectAtIndex:i]objectForKey:#"long"]);
[userLatitudeArray addObject:[[latlongArray objectAtIndex:i]objectForKey:#"lat"]];
[userLongitudeArray addObject:[[latlongArray objectAtIndex:i]objectForKey:#"long"]];
}
// ProfilePIC URL
profilePicURLString = [[trackingDict objectAtIndex:trackUsersCount]objectForKey:#"user_profilePicture"];
NSString *name = [[trackingDict objectAtIndex:trackUsersCount]objectForKey:#"user_firstName"];
[userNameArray addObject:name];
[profilePicURLStringArray addObject:profilePicURLString];
for (int i = 0; i<userLatitudeArray.count; i++) {
CLLocationCoordinate2D userLocation;
userLocation.latitude = [[userLatitudeArray objectAtIndex:i]doubleValue];
userLocation.longitude = [[userLongitudeArray objectAtIndex:i] doubleValue];
Annotation *Anno = [[Annotation alloc]init];
Anno.coordinate = userLocation;
Anno.title = name;
Anno.userProfileImageString = profilePicURLString;
[mapView addAnnotation:Anno];
}
NSLog(#"ARRAY for longitude %#", userLongitudeArray);
NSLog(#"ARRAY for latitude %#", userLatitudeArray);
int i;
for (i = 0; i<userLatitudeArray.count; i++) {
CLLocationCoordinate2D userLocation;
userLocation.latitude = [[userLatitudeArray objectAtIndex:i]doubleValue];
userLocation.longitude = [[userLongitudeArray objectAtIndex:i] doubleValue];
MKMapPoint * pointsArray = malloc(sizeof(CLLocationCoordinate2D)*userLongitudeArray.count);
pointsArray[i] = MKMapPointForCoordinate(userLocation);
polyline = [MKPolyline polylineWithPoints:pointsArray count:i];
free(pointsArray);
}
[mapView addOverlay:polyline];
}
}
[mapView reloadInputViews];
}
}
The web service is called after every 20 seconds, I know I can user GCD here or other threading approach but at the time-interval when web service is called via background thread, the annotations and overlays are not displayed not the map.
Any help is much appreciated!
I don't think there's an quick & easy fix for this. One approach would be to separate the code that collects (modifies) the data from the code that updates the view.
Set the code that is dealing with UI into a separate method, updateUI for example.
From here on you have couple of choices. You could try this for example:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//perform the data collection, calculations...
//here is where the model in MVC gets modified
//
[self performSelectorOnMainThread: #selector(updateUI) withObject:nil waitUntilDone: NO];
}
-(void)updateUI
{
//do the UI updates (like adding overlays etc...) here
}
You could also store all the data needed to update the UI to a kind of object and pass it as withObject: parameter.