I was thinking of an application where user could select a location on an iphone app. I have googled it and didn't find anything useful.
My question is, is it possible to let the user select a location from an iphone app using MapKit/CLLocation? If yes, please help me where should i start.
Thanks
You can add a long press gesture recognizer to the map:
UILongPressGestureRecognizer* lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
lpgr.minimumPressDuration = 1.5;
lpgr.delegate = self;
[self.map addGestureRecognizer:lpgr];
[lpgr release];
In the handle long press method get the CLLocationCordinate2D:
- (void) handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
/*
Only handle state as the touches began
set the location of the annotation
*/
CLLocationCoordinate2D coordinate = [self.map convertPoint:[gestureRecognizer locationInView:self.map] toCoordinateFromView:self.map];
[self.map setCenterCoordinate:coordinate animated:YES];
// Do anything else with the coordinate as you see fit in your application
}
}
Take a look at this SO answer. The answer tells you not only how to get the coordinate, but also how to put a pin (annotation) on the map.
Related
How do we follow the user in maps. I want to have the blue dot (user location) be in the center of the map, But I also what to allow the user to zoom in and zoom out and then after a couple seconds zoom in back in the user location.
My Educated Guess for the Solution: We detect if the user is zooming in or out, after three seconds of no zooming in or out detection, we starting follow the user :). Your HELP would be awesome :)
This code zoom in the user location but doesn't delay for zoom in and out:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
MKCoordinateRegion userLocation = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, 1500.0, 1500.0); [mapView setRegion:userLocation animated:YES];
}
A quick look in the docs reveals the magic.
Set the userTrackingMode of your map to MKUserTrackingModeFollow.
See here.
Update:
Since you've updated your question, here's the new answer.
To recenter the map to the user location i would recommend to write a simple helper Method:
- (void)recenterUserLocation:(BOOL)animated{
MKCoordinateSpan zoomedSpan = MKCoordinateSpanMake(1000, 1000);
MKCoordinateRegion userRegion = MKCoordinateRegionMake(self.mapView.userLocation.coordinate, zoomedSpan);
[self.mapView setRegion:userRegion animated:animated];
}
And now you should call it after a short delay if user has stopped moving the map. You can do this in the regionDidChange delegate method of the mapView.
But you can get problems if you don't lock the reset-method if the user changes the region multiple times before it really resets the map. So it would be wise to make a flag if it is possible to recenter the map. Like a property BOOL canRecenter.
Init it with YES and update the recenterUserLocation method to:
- (void)recenterUserLocation:(BOOL)animated{
MKCoordinateSpan zoomedSpan = MKCoordinateSpanMake(1000, 1000);
MKCoordinateRegion userRegion = MKCoordinateRegionMake(self.mapView.userLocation.coordinate, zoomedSpan);
[self.mapView setRegion:userRegion animated:animated];
self.canRecenter = YES;
}
Now you can call it safely after the user has moved the map in any way with a small delay:
- (void)mapView:(MKMapView *)mMapView regionDidChangeAnimated:(BOOL)animated{
if (self.canRecenter){
self.canRecenter = NO;
[self performSelector:#selector(recenterUserLocation:) withObject:#(animated) afterDelay:3];
}
}
I had the same problem. I guessed:
If the user drag the map, he wants to stay on that position.
If the user do nothing or reset to show current location, I need to follow the user.
I added a reset button to show the current user location like this:
On the reset button clicked, changed the needToCenterMap to TRUE
Added a drag gesture recognizer on map
// Map drag handler
UIPanGestureRecognizer* panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(didDragMap:)];
- (void)didDragMap:(UIGestureRecognizer*)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
NSLog(#"Map drag ended");
self.needToCenterMap = FALSE;
}
}
Followed the user on map depending on needToCenterMap flag
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
if (self.needToCenterMap == TRUE)
[mapView setCenterCoordinate:userLocation.location.coordinate animated:YES];
}
I made a little example to show how you can delegate this job to the Map SDK.
Of course you could listen to the Location change but MKUserTrackingModeFollow automatically does this for you, so just a single line of code
#import <MapKit/MapKit.h>
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
MKMapView *mapView = [[MKMapView alloc] initWithFrame:self.view.frame];
//Always center the dot and zoom in to an apropriate zoom level when position changes
[mapView setUserTrackingMode:MKUserTrackingModeFollow];
//don't let the user drag around the the map -> just zooming enabled
[mapView setScrollEnabled:NO];
[self.view addSubview:mapView];
}
Then the app looks like this:
For more information just read the Apple Documentation:
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html
This shell do the trick: mkMapview.showsUserLocation = YES;
I have two labels in two different positions, when both labels are tapped at the same time i want another label to show a success message.
How do I accomplish this? I can recognize a single tap or double tap with one or more finger touches but this is a different scenario. Please help. I tried this, but it does not work.
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.numberOfTouchesRequired = 2;
tapRecognizer.delegate = self;
[self.view addGestureRecognizer:tapRecognizer];
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (touch.view == tap2 && touch.view == tap1)
{
result.text = #"success";
}
return YES;
}
Thanks in advance.
What you're trying to detect isn't really a single gesture.
I'd suggest adding a tap gesture recogniser to each button. The handler would:
Store the time of the tap (at the moment that the handler is called)
Compare this time with the time that the other button was last
tapped. If the times are very similar (perhaps 0.25 secs apart),
consider that they've both been tapped simultaneously and react
accordingly.
Play with the time interval on a real device to find the ideal amount.
UPDATE:
A code snippet that obviously hasn't been tested in any way:
- (void)handleButton1Tap:(UITapGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateEnded)
{
self.button1TapTime = CACurrentMediaTime();
[self testForSimultaneousTap];
}
}
- (void)handleButton2Tap:(UITapGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateEnded)
{
self.button2TapTime = CACurrentMediaTime();
[self testForSimultaneousTap];
}
}
- (void)testForSimultaneousTap
{
if (fabs(self.button1TapTime - self.button2TapTime) <= 0.2)
{
// Do stuff
}
}
where self.button1TapTime and self.button2TapTime are member variables (doubles).
Tim
Formally I had accepted termes's answer first and that worked too, but I have found a more simpler solution to this process. There is no need for two gesture recognizers, it is achievable with a simple tap gesture recognizer with number of touches count to two. Here is the code:
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.numberOfTouchesRequired = 2;
tapRecognizer.delegate = self;
[self addGestureRecognizer:tapRecognizer];
Now, in the handle tap method we can easily get the two touch points by "locationOfTouch:inView:", a instance method of UIGestureRecognizer class. So in the handleTap: method we need to check if the two touch points are in the desired location. Here is the code:
-(void)handleTap:(UITapGestureRecognizer*)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded)
{
CGPoint point1 = [recognizer locationOfTouch:0 self];
CGPoint point2 = [recognizer locationOfTouch:1 self];
if ([self validateTapIn:point1 and:point2])
{
resultLabel.text = #"success";
}
}
}
-(BOOL)validateTapIn:(CGPoint)point1 and:(CGPoint)point2
{
return
(CGRectContainsPoint(label1.frame, point1) && CGRectContainsPoint(label2.frame,:point2)) ||
(CGRectContainsPoint(label1.frame, point2) && CGRectContainsPoint(label2.frame, point1));
}
i am trying to implement my own gesture recognizer in addition to the one already used by the MKMapView. Right now i can tap on the map and set a pin. This behavior is realized by my UITapGestureRecognizer. When i tap on a pin that already exists, my gesture recognizer does nothing, but instead the callout bubble of this pin is shown. The UIGestureRecognizerDelegate looks like this:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (gestureRecognizer == self.tapRecognizer)
{
bool hitAnnotation = false;
int count = [self.mapView.annotations count];
int counter = 0;
while (counter < count && hitAnnotation == false )
{
if (touch.view == [self.mapView viewForAnnotation:[self.mapView.annotations objectAtIndex:counter]])
{
hitAnnotation = true;
}
counter++;
}
if (hitAnnotation)
{
return NO;
}
}
return YES;
}
This works fine. My only problem are the callout bubbles of the pins and the double tap. Normally the double tap is used for zooming in. This still works but in addition to this, i also get a new pin. Is there any way to avoid this?
The other problem occurs with the callout bubble of a pin. I can open the bubble by tapping on the pin without setting a new pin at this place (see code above) but when i want to close the bubble by tapping on it, another pin is set. My problem is, that i cannot check with touch.view , if the user tapped on a callout bubble, because it is not a regular UIView as far as i know. Any ideas or workarounds for this problem?
Thanks
I had the same problem as your first problem: distinguishing double taps from single taps in an MKMapView. What I did was the following:
[doubleTapper release];
doubleTapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mapDoubleTapped:)];
doubleTapper.numberOfTapsRequired = 2;
doubleTapper.delaysTouchesBegan = NO;
doubleTapper.delaysTouchesEnded = NO;
doubleTapper.cancelsTouchesInView = NO;
doubleTapper.delegate = self;
[mapTapper release];
mapTapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mapTapped:)];
mapTapper.numberOfTapsRequired = 1;
mapTapper.delaysTouchesBegan = NO;
mapTapper.delaysTouchesEnded = NO;
mapTapper.cancelsTouchesInView = NO;
[mapTapper requireGestureRecognizerToFail:doubleTapper];
and then implemented the following delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Using requireGestureRecognizerToFail: allows the app to distinguish single taps from double taps and implementing gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: ensures that double taps are still forwarded to the MKMapView so that it continues zooming normally. Note that doubleTapper doesn't actually do anything (in my case, except log debug messages). It's simply a dummy UIGestureRecognizer that's used to help separate single taps from double taps.
I am trying to add annotations and overlays to a mapview but it crashes randomly. It is an EXC_BAD_ACCESS error, but zombies doesn't tell me anything. It says it is crashing on CG::Path::apply_transform(CGAffineTransform const&). I have looked everywhere for why this is happening but can't pinpoint it.
I am creating the mapview in ib and have the delegates and everything set up right. It will work sometimes and then crash randomly.
I am using a gesture recognizer to add the annotations and overlay
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[self.mapView addGestureRecognizer:doubleTap];
and
- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateRecognized){
CGPoint touchPoint = [gestureRecognizer locationInView:self.mapView];
CLLocationCoordinate2D touchMapCoordinate =
[self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
//add pin where user touched down...
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
pa.coordinate = touchMapCoordinate;
//[pa setTitle:#"title"];
[mapView addAnnotation:pa];
MKCircle* circle=[MKCircle circleWithCenterCoordinate:touchMapCoordinate radius:500];
[mapView addOverlay:circle];
}
}
And the views for each:
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
{
if ([overlay isKindOfClass:[MKCircle class]]) {
MKCircleView* circleView = [[MKCircleView alloc] initWithOverlay:overlay];
circleView.strokeColor = [UIColor redColor];
circleView.lineWidth = 1.0;
circleView.fillColor = [UIColor blackColor];
circleView.alpha=.5;
return circleView;
}
else
return nil;
}
- (MKAnnotationView *)mapView:(MKMapView *)localmapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if (![annotation isKindOfClass:[MKUserLocation class]]) {
static NSString *AnnotationIdentifier = #"Annotation";
MKPinAnnotationView* pinView = (MKPinAnnotationView *)[localmapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView)
{
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.animatesDrop = YES;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
return nil;
}
Is there a better way to add annotations/overlays to maps with user interaction? Am I doing something wrong in this code? It appears to draw most of the circle but then crashes... Is there some special trick to mapviews?
I've been getting the exact same error:
CG::Path::apply_transform(CGAffineTransform const&) will hit a test instruction and give me an EXC_BAD_ACCESS
This specifically occurs when using a double click on the map to zoom in on an MKCircle.
I can't say this definitively, but to the best of my knowledge this problem only occurs on the simulator when you use a double click to zoom, I've never been able to cause the error from an actual device, or by using option+click to zoom on the simulator.
So at this point I've filed this under "simulator bug" and left it at that.
If you do discover anything to the contrary, please let me know because it really bothers me not explicitly knowing whether or not this is a bug existing in my app that I just can't properly reproduce.
edit:
This was flagged initially as "not an answer" so I'll provide a little bit more information supporting my conjecture.
Basically in both of our scenarios a gesture is firing the re-rendering of the MKCircleView, what I strongly suspect is that since the simulator is able to generate some kind of gesture which can't be created from a user on the actual device, there is a failed expectation somewhere down the chain while that gesture gets handled.
I am not sure where your EXC_BAD_ACCESS problems is. But you have a big problem with leaking memory. You have to releas object that you create with init. In the above code you create objects and never release them. That will not throw EXC_BAD_ACCESS but it will consume your memory.
Release the following object :
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
MKCircleView* circleView = [[MKCircleView alloc] initWithOverlay:overlay];
How to create Touch Events for MKMapView.
I'm using UIViewController and adding MKMapView on that using interface builder.
Now I need to handle touch events for that map.....
I tried by writing UITouch Delegate methods
But I failed...It is not getting called.
Please post a solution how to handle touch events on MKMapView.....
Thanks in advance...
If you are happy with an iOS 4 and above solution, I've used UIGesture recognisers and never had a problem.
Here's an example for a long pressure gesture (tap and hold):
// Long press gesture recogniser
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleLongPressGesture:)];
[self.view addGestureRecognizer:longPressGesture];
[longPressGesture release];
And then you can handle the even in your handleLongPressGesture: method:
-(void)handleLongPressGesture:(UILongPressGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateChanged)
return;
else {
// Your app logic here...
}
}