Delay the call to the delegate method - mapView:regionDidChangeAnimated: - iphone

Whenever the user scrolls map or zooms in/out, this method gets called instantaneously. I want to delay the call to this method by say 2 secs. Is it possible to do that?

You could implement that method like this:
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
NSNumber *animatedNumber = [NSNumber numberWithBool:animated];
NSArray *args = [[NSArray alloc] initWithObjects:mapView,
animatedNumber,nil];
[self performSelector:#selector(delayedMapViewRegionDidChangeAnimated:)
withObject:args
afterDelay:2.0f];
[args release];
}
Then, somewhere in the same class:
-(void)delayedMapViewRegionDidChangeAnimated:(NSArray *)args
{
MKMapView *mapView = [args objectAtIndex:0];
BOOL animated = [[args objectAtIndex:1] boolValue];
// do what you would have done in mapView:regionDidChangeAnimated: here
}
Of course, if you don't need one of those arguments (either mapView or animated), you could make this considerably simpler by only passing the one you did need.
If you can't just edit the code for your MKMapViewDelegate, perhaps you could do something similar with method swizzling, although then you're getting really hacky.

You can send a delayed message with performSelector:withObject:afterDelay: or one of its related methods.

Related

Collection <CALayerArray: 0x1ed8faa0> was mutated while being enumerated

My app crashes some time while removing wait view from screen. Please guide me how can i improve code given below.
The wait view is only called when app is downloading something from server. and when it completed download then i call removeWaitView method.
Exception Type: NSGenericException
Reason: Collection was mutated while being enumerated.
+(void) removeWaitView:(UIView *) view{
NSLog(#"Shared->removeWaitView:");
UIView *temp=nil;
temp=[view viewWithTag:kWaitViewTag];
if (temp!=nil) {
[temp removeFromSuperview];
}
}
my waitview adding code is
+(void) showWaitViewInView:(UIView *)view withText:(NSString *)text{
NSLog(#"Shared->showWaitViewWithtag");
UIView *temp=nil;
temp=[view viewWithTag:kWaitViewTag];
if (temp!=nil)
{
return;
}
//width 110 height 40
WaitViewByIqbal *waitView=[[WaitViewByIqbal alloc] initWithFrame:CGRectMake(0,0,90,35)];
waitView.center=CGPointMake(view.frame.size.width/2,(view.frame.size. height/2) -15);
waitView.tag=kWaitViewTag; // waitView.waitLabel.text=text;
[view addSubview:waitView];
[waitView release];
}
The exception is pretty clear - a collection (in this case something like an array) is being modified while it is also being enumerated.
In this specific case we are talking about array of layers, or better said, instances of UIView which are all backed up by layers.
The modifications happen when you are calling removeFromSuperview or addSubview. The enumeration can happen anytime during redrawing.
My guess is - you are not calling your methods from the main thread and the main thread is currently redrawing, so you'll get a racing condition.
Solution: call these methods only from the main thread.
You should get a copy of the array, so you don't risk the chance of it being modified while your code is enumerating it. Of course this leaves the chance that a new item may be added as a result of the mutation, thus your enumeration will miss that one array item. Here is an example specific to the title of your question:
[[viewLayer.sublayers copy] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CALayer * subLayer = obj;
if(subLayer == theLayerToBeDeleted){
[subLayer removeFromSuperlayer];
}
}];
It's possible that you're adding or removing a the waitView while iterating through the waitView's siblings (it's superView's subviews). Check to see what methods call removeWaitView and showWaitInView to make sure the calling methods don't call the show/remove wait view methods from within a for loop iterating the wait view's siblings (the waitView's superview's subviews).
To fix issue just start from last element
NSMutableArray* Items = [[NSMutableArray alloc] initWithArray:[_ScrollList.documentView subviews]];
while ([Items count]>0) {
NSView *v = [Items lastObject];
[v removeFromSuperview];
[Items removeLastObject];
}

RestKit Handling overlapping RKRequest Delegates

I have several RestKit gets that all use the same format:
[[RKClient sharedClient] get:endString queryParameters:params delegate:self];
I have a masterMethod that essentially refreshes all my user's restful data that looks like this
-(void)masterMethod
{
[self get1];
[self get2];
[self get3];
[self get4];
[self get5];
}
Where all the gets are in the same format as the one above. All of this code is in a class that includes the delegate method:
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response
However, I think something is going wrong when I try to call all give gets in the same method. It's as though the delegate didLoadResponse & didRecieveResponse methods are overlapping or getting release or something. Is there a way to make a master queue to handle this huge call? Or is something else going wrong.
I'm getting a BAD_ACCESS error somewhere in the masterMethod call.
Thanks, any help greatly appreciated.
What are you getting? If you're pulling down objects you should us the isKindOfClass method to distinguish the objects in objectLoader:didLoadObjects and set appropriately.
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
if ([[objects objectAtIndex:0] isKindOfClass:[Apple class]]) {
Apple *apple = [objects objectAtIndex:0];
}
else if ([[objects objectAtIndex:0] isKindOfClass:[Banana class]]) {
Banana *banana = [objects objectAtIndex:0];
}
}
If you're pulling data from the request response, look into setting userdata on the request object, then checking the userdata in request:didLoadResponse. For more information see RestKit: distinguish multiple requests in didLoadResponse:.

How to call a web service on page load

I am trying to call a web service on page load. Currently I call it on a button click and it works fine. But when I try to do the same on viewDidAppear, it doesn't happen. What i want to achieve is if username and password are saved then it should automatically load the next page. It is filling in the text boxes but not loading the next page.
Here is my code for submit button and ViewDidAppear:
-(IBAction)submitButton{
[apd showCoverView:YES WithActivityIndicator:YES];
PlaceWebService *handler = [[PlaceWebService alloc]init];
[handler setRequestType:Loginparser];
NSString *url = [NSString stringWithFormat:#"http://www.mywebsite.com/api.php?command=auth&cardno=%#&password=%#",username.text,password.text];
[handler sendingLoginRequest:url Respond:self At:#selector(showParsed:)];
}
and for viewDidAppear
-(void)viewDidAppear:(BOOL)animated
{
NSLog(#"Appeared");
[self loginArea];
apd=[[UIApplication sharedApplication]delegate];
NSString *filepath=[self pathOfFile];
if([[NSFileManager defaultManager]fileExistsAtPath:filepath])
{
NSArray *array=[[NSArray alloc]initWithContentsOfFile:filepath];
username.text=[array objectAtIndex:0];
password.text=[array objectAtIndex:1];
[self submitButton];
}
}
What should I do? Please help...
If you want to call the method after loading view and without any event, then you need to that as normal instance method instead of IBAction method.
-(Void)submitButton{
// implementation
}
and then call this method from viewDidAppear.
A couple things could be the problem here.
1)
IBActions usually take a parameter. Declare it as:
- (IBAction) submitButton: (id) sender;
And then call it from your viewDidAppear method as:
[self submitButton: self];
2)
Also make sure UI stuff is happening on the main thread (you didn't specify if the app is multi threaded or not), so maybe:
[self performSelectorOnMainThread: #selector(submitButton:) withObject: self];
And
3)
Set breakpoints to see if your submitButton method (and the lines before it) are actually even called when viewFromAppear: is called.
And Rishi's suggestion is good, too!

iOS rotating MKAnnotationView in response of MKMapView rotation

In my application I have a MKMapView where several annotations are shown. The map rotates based on the heading of the device. To rotate the map the following statement is performed (called by the method locationManager: didUpdateHeading:)
self.navigationMapView.mapView.transform = CGAffineTransformMakeRotation(-heading);
where the heading (magnetic) is expressed in radians. What I noticed it's that even the annotations in the map rotate and I don't want it. I tried to fix it in the following method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
static NSString *identifier = #"AnnotationViewIdentifier";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (av == nil) {
av = [[[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:identifier] autorelease];
}
else{
av.annotation = annotation;
}
av.transform = CGAffineTransformMakeRotation(degreesToRadians(self.arController.currentHeading.magneticHeading));
av.canShowCallout = YES;
return av;
}
and I want to call this method from "didUpdateHeading:" but I really don't know how to do it. The TableView class has the reloadData function that calls the delegate method but here the things seem different. Any suggestions?!
Another question, my annotations on the map show the distance from the user, I would like to update them (distance label) as soon as the user change location. Any suggestions?!
So with a MKMapView having that be called properly is a little bit annoying. Essentially you have one of two options. Option 1: Create an array of the annotation on the screen and remove that from the map_view and then re-add them to the map_view. Essentially creating your own reload data function. Option 2: Do something simple such as
CGLocationCoordinate2D coordinate = map_view.center;
map_view.center = coordinate;
-- Essentially the point is to reset a property of the map causing it to redraw. However this option is not always going to work. Option 1 has a higher chance of working however that one can also fail, so if simply taking the annotations off and re-adding them causes nothing to happen then simply decreate the map and then recreate the map at the end of your map refresh function something like.
[my_map_view removeFromSuperView];
[my_map_view release];
my_map_view = nil;
my_map_view = [[MKMapView alloc] initWithFrame:CGRectMake(0,0,320,480)];
one of these options should work. I had to do option one for my solution however I know some people are lucky and option 2 works just as well.

Problem adding multiple annotations to map

Ok, so I’m having this problem. What I want to do is manually add multiple annotations to a map. When I add just one annotation, it works flawlessly. The pin drops, you can click on it to see its callout, life is good.
The problem comes when I want to add more than one. When I add the second, suddenly the pin’s aren’t coloured correctly (i.e. depending on their magnitude they should be a certain color, but they’re now both the same…), and more importantly when you click on them, to see their callout, the app crashes with exex_bad_access. I really have no idea what’s wrong, maybe I’m adding too many views to the map? But it’s only 9 pins and the pins themselves add just fine.
Here’s my code…
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *stops = [[NSMutableArray alloc] init]; //Get list of all the stops available
Bus *bus1 = [[Bus alloc] init]; // Bus 1 holds the stops
stops = [bus1 returnStops];
for (NSString *stop in stops) //Go through each stop to add annotation to map
{
Bus *bus2 = [bus1 initWithStop:stop]; //Create an instance of bus with a given stop
MapAnnotation *eqAnn = [MapAnnotation annotationWithBus:bus2];
[self.mapView addAnnotation:eqAnn]; //Add the annotation to the map
//[eqAnn release];
//[bus2 release];
}
[self recenterMap];
[stops release];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *view = nil;
if(annotation != mapView.userLocation) {
MapAnnotation *eqAnn = (MapAnnotation*)annotation;
view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:#"busLoc"];
if(nil == view) {
view = [[[MKPinAnnotationView alloc] initWithAnnotation:eqAnn
reuseIdentifier:#"busLoc"] autorelease];
}
CGFloat magnituide = [eqAnn.bus.magnitude floatValue];
if(magnituide >= .80f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorRed];
} else if(magnituide >= .60f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorPurple];
} else
{
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorGreen];
}
[(MKPinAnnotationView *)view setAnimatesDrop:YES];
[view setCanShowCallout:YES];
}
return view;
}
even tried removing the second function, but it didn’t do anything.
Thanks for the help!
P.S I should also add, there’s usually one or two pins out of the 9 which works when you click the annotation…
If i even try to manually just two annotations by hand in the program (i.e., remove the loop), it still fails and the color is still wrong...
It would appear that your memory management of the stops variable is incorrect. You allocate a mutable array, then replace that array with the return value of -[Bus returnStops], then release that. Also it's not clear what's going on with bus2 - does -[Bus initWithStop:] return a different instance of Bus? It's not usual to send any method -init* on an already-initialised object. I think that you probably are confused by the memory management conventions in Cocoa Touch. Here's a collection of articles and other references on Cocoa memory management (which is the same beast).
Have you tried using AddAnnotations instead of add annotation? - (void)addAnnotations:(NSArray *)annotations. This might work for you...but looking at the answer above and further inspection you are having some memory managment issues in your viewDidLoad (though thi s might not be the cause of your problem, but it could be). First of you are allocating the array (stops) and then ovveriding it with some array in the Bus object, this will cause a leak. Also you are then releasing that array which might be causing the crash since you are releasing the array that is actually in the Bus object w ithout having increased a reference count to it. I am not sure what initWithStop is doing but you might be getting a leak here too if initWithStop retains the object.
I wouldn't call it a memory management problem -- I'd just say you are using array references incorrectly.
After constructing the array with NSMutableArray *stops = [[NSMutableArray alloc] init], the next step is to use [stops addObject: ] to add each stop you want to store.
After that? It's not clear what you are really trying to do.
SO the answer was that I kept sending bus1 the init object, so it got confused.
"Hi David,
Your data model looks hosed to me. You only have one bus object that you are repeatedly sending initWithStop: to.
Hope this helps.
Good luck!
"
Thank you guys for your help! You all helped me quite a bit!