Is there a way to determine if a MKMapView drag and zoom stops?
Right now I've added an UIPanGestureRecognizer for dragging MKMapView but I'll receive gestureRecognizer.state == UIGestureRecognizerStateEnded immediately when the user lift his finger even though the map is scrolling. What I try to figure out is how to prevent loading server data for my map annotations when the map is still moving and/or the user touches the map one's again to drag the map again? The data load mechanism should be only called when the map stops moving and zooming and is standing still for some predefined time.
This is what I've implement so far:
- (void)viewDidLoad {
...
UIPanGestureRecognizer* panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(didDragMap:)];
[panRec setDelegate:self];
[panRec setDelaysTouchesBegan:YES];
[panRec setDelaysTouchesEnded:YES];
[panRec setCancelsTouchesInView:YES];
[self.mapView addGestureRecognizer:panRec];
}
And the selector method didDragMap:
- (void)didDragMap:(UIGestureRecognizer*)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
_searchBar.text = #"";
_filtered = NO;
_crosshair.hidden = NO;
[self removeAllAnnotationExceptOfOriginalLocation];
}
else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
[self performSelector:#selector(delayAddressResolving:) withObject:nil afterDelay:1.0];
}
}
The selector method delayAddressResolving: is loading the needed data from server to display the information for my annotations.
All notes are welcome!
Use the following MKMapViewDelegate methods:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
These methods are called every time when map region is changing.
determine if MKMapView was dragged/moved
http://developer.apple.com/library/ios/#samplecode/Breadcrumb/Listings/Classes_BreadcrumbViewController_m.html. It may help you
Related
On my mapview I draw polygon overlays that belong to a specific annotation. I want that annotation to be selected when the overlay is tapped. My first attempt was to add a UITapGestureRecognizer to the mapview, test whether the tapped point is inside a polygon and perform [mapView selectAnnotation:myAnnotation] on success. The problem is that after this, the mapview decides there was a tap not on any annotations, so it deselects the annotation again.
My question is how to best prevent this from happening, I don't seem to be able to find a nice solution. What I have tried:
Create a new UIGestureRecognizer subclass that recognizes just taps inside overlays, then iterate through mapView.gestureRecognizers and call requireGestureRecognizerToFail on each. However, the mapview does not expose any recognizers through its property.
Return YES for shouldBeRequiredToFailByGestureRecognizer in my custom recognizer for any other recognizer that isKindOfClass tap recognizer. However, there still seems to be another recognizer that is not passed in there.
Place a transparent view on there and do the polygon check in pointInside:withEvent, but does also blocks any other gestures besides only taps.
EDIT:
After poking around a bit more, I have code that is almost working, of which I know where it goes wrong. I have a custom recognizer as before. In its delegate I do:
- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
{
[otherGestureRecognizer requireGestureRecognizerToFail:gestureRecognizer]; // can possibly do this in custom recognizer itself instead
return YES;
}
Now taps inside polygons successfully prevent deselection. However, when I then do:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView
{
// displayRegion is chosen to center annotation
[mapView setRegion:self.displayRegion animated:YES];
}
it breaks again, and the annotation gets deselected again..
It seems we have the same problem ( a little different: i'm tryng to select manually an annotation in a gesture recognizer )
I'm doing so ( and it works, but it seems to complex to me , feel free to ask more if it's not clear ):
i'm working with a long pressure event :
...
_lp1 = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(handleOverlayLp1:)];
((UILongPressGestureRecognizer*)_lp1).minimumPressDuration = 0.05;
_lp1.delegate = self;
[_mapView addGestureRecognizer:_lp1];
...
I collect all gesture recognizers in a global var :
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (_gestureRecognizers==nil)
_gestureRecognizers = [NSMutableSet set];
[_gestureRecognizers addObject:otherGestureRecognizer];
return YES;
}
// when i recognize gestures, disable everything and call an asyncrhronous task where i re-enable
- (void)handleOverlayLp1:(UIGestureRecognizer*)recognizer
{
// Do Your thing.
if (recognizer.state == UIGestureRecognizerStateBegan)
{
BOOL found=NO;
...
if (found) {
// disable gestures, this forces them to fail, and then reenable in selectOverlayAnnotation that is called asynchronously
for (UIGestureRecognizer *otherRecognizer in _gestureRecognizers) {
otherRecognizer.enabled = NO;
[self performSelector:#selector(selectOverlayAnnotation:) withObject:polyline afterDelay:0.1];
}
}
}
}
- (void)selectOverlayAnnotation: (id<MKAnnotation>) polyline
{
[_mapView selectAnnotation:polyline animated:NO];
for (UIGestureRecognizer *otherRecognizer in _gestureRecognizers) {
otherRecognizer.enabled = YES;
}
}
I have an app with a MKMapView and code that is called each time the map changes locations (in regionDidChangeAnimated). When the app initially loads, regionDidChangeAnimated is called on pans (swipes), pinches, taps and buttons that explicitly update the map coordinates. After loading other views and coming back to the map the regionDidChangeAnimated is only called for taps and the buttons that explicitly update the map. Panning the map and pinches no longer call regionDidChangeAnimated.
I have looked at this stackoverflow post which did not solve this issue. The forum posts on devforums and iphonedevsdk also did not work. Does anyone know what causes this issue? I am not adding any subviews to MKMapView.
I did not want to initially do it this way, but it appears to work with no problems so far (taken from devforums post in question):
Add the UIGestureRecognizerDelegate to your header.
Now add a check for the version number... If we're on iOS 4 we can do this:
if (NSFoundationVersionNumber >= 678.58){
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinchGestureCaptured:)];
pinch.delegate = self;
[mapView addGestureRecognizer:pinch];
[pinch release];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureCaptured:)];
pan.delegate = self;
[mapView addGestureRecognizer:pan];
[pan release];
}
Add the delegate methods to handle the gestures:
#pragma mark -
#pragma mark Gesture Recognizers
- (void)pinchGestureCaptured:(UIPinchGestureRecognizer*)gesture{
if(UIGestureRecognizerStateEnded == gesture.state){
///////////////////[self doWhatYouWouldDoInRegionDidChangeAnimated];
}
}
- (void)panGestureCaptured:(UIPanGestureRecognizer*)gesture{
if(UIGestureRecognizerStateEnded == gesture.state){
///////////////////[self doWhatYouWouldDoInRegionDidChangeAnimated];
}
}
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
return YES;
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch: (UITouch *)touch{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
My app places a pushpin on the map and then selects its using animation so the user has a visual clue and can immediately read the title/subtitle. The following code works in both iOS4 and iOS5, but in iOS5, the annotation doesn't get selected automatically unless I change the animation to NO in the selectAnnotation method.
Any ideas why?
MapAnnotations *pushpin = [[MapAnnotations alloc] initWithCoordinate:coordinate];
pushpin.title = [selectedStation valueForKey:#"name"];
pushpin.subtitle = [selectedStation valueForKey:#"address"];
[stationMap addAnnotation:pushpin];
[stationMap selectAnnotation:pushpin animated:YES];
[pushpin release]; pushpin = nil;
Not sure why it would work before but the animation probably requires the annotation view to be created and ready which is unlikely immediately after adding the annotation.
What you can do is move the selection to the didAddAnnotationViews delegate method which should work on all iOS versions:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
for (MKAnnotationView *av in views) {
if ([av.annotation isKindOfClass:[MapAnnotations class]]) {
MapAnnotations *pushpin = (MapAnnotations *)av.annotation;
if (_this_pushpin_is_the_one_to_select) {
[mapView selectAnnotation:av.annotation animated:YES];
break; //or return;
}
}
}
}
I have a view with several UIButtons. I have successfully implemented using UILongPressGestureRecognizer with the following as the selector;
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateEnded ) {
NSLog(#"Long Press");
}
}
What I need to know within this method is which UIButton received the longpress since I need to do something different, depending on which button received the longpress.
Hopefully the answer is not some issue of mapping the coordinates of where the longpress occured to the bounds of the buttons - would rather not go there.
Any suggestions?
Thanks!
This is available in gesture.view.
Are you adding the long tap gesture controller to the UIView that has the UIButtons as subviews? If so, something along the lines of #Magic Bullet Dave's approach is probably the way to go.
An alternative is to subclass UIButton and add to each UIButton a longTapGestureRecogniser. You can then get your button to do what you like. For example, it could send a message identifying itself to a view controller. The following snippet illustrates methods for the subclass.
- (void) setupLongPressForTarget: (id) target;
{
[self setTarget: target]; // property used to hold target (add #property and #synthesise as appropriate)
UILongPressGestureRecognizer* longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:button action:#selector(longPress:)];
[self addGestureRecognizer:longPress];
[longPress release];
}
- (void) longPress: (UIGestureRecognizer*) recogniser;
{
if (![recogniser isEnabled]) return; // code to prevent multiple long press messages
[recogniser setEnabled:NO];
[recogniser performSelector:#selector(setEnabled:) withObject: [NSNumber numberWithBool:YES] afterDelay:0.2];
NSLog(#"long press detected on button");
if ([[self target] respondsToSelector:#selector(longPressOnButton:)])
{
[[self target] longPressOnButton: self];
}
}
In your view controller you might have code something like this:
- (void) viewDidLoad;
{
// set up buttons (if not already done in Interface Builder)
[buttonA setupLongPressForTarget: self];
[buttonB setupLongPressForTarget: self];
// finish any other set up
}
- (void) longPressOnButton: (id) sender;
{
if (sender = [self buttonA])
{
// handle button A long press
}
if (sender = [self buttonB])
{
// handle button B long press
}
// etc.
}
If your view contains multiple subViews (like lots of buttons) you can determine what was tapped:
// Get the position of the point tapped in the window co-ordinate system
CGPoint tapPoint = [gesture locationInView:nil];
UIView *viewAtBottomOfHeirachy = [self.window hitTest:tapPoint withEvent:nil];
if ([viewAtBottomOfHeirachy isKindOfClass:[UIButton class]])
I have a mapView using xib file now when i touch in the mapview i want the latitude and longitude of that particular area so there any whey or any sample code which help me in this task.Thanks in Advance.
With iOS 3.2 or greater, it's probably better and simpler to use a UIGestureRecognizer with the map view instead of trying to subclass it and intercepting touches manually.
First, add the gesture recognizer to the map view:
UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(tapGestureHandler:)];
tgr.delegate = self; //also add <UIGestureRecognizerDelegate> to #interface
[mapView addGestureRecognizer:tgr];
[tgr release];
Next, implement shouldRecognizeSimultaneouslyWithGestureRecognizer and return YES so your tap gesture recognizer can work at the same time as the map's (otherwise taps on pins won't get handled automatically by the map):
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer
:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Finally, implement the gesture handler:
- (void)tapGestureHandler:(UITapGestureRecognizer *)tgr
{
CGPoint touchPoint = [tgr locationInView:mapView];
CLLocationCoordinate2D touchMapCoordinate
= [mapView convertPoint:touchPoint toCoordinateFromView:mapView];
NSLog(#"tapGestureHandler: touchMapCoordinate = %f,%f",
touchMapCoordinate.latitude, touchMapCoordinate.longitude);
}
Its kind of a tricky thing to be done.
First you need to subclass mkmapview
in
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
method you can find the location of touch and then using this method
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view
you can find lat and long..
In Swift 3:
func tapGestureHandler(_sender: UITapGestureRecognizer) {
let touchPoint = _sender.location(in: mapView)
let touchMapCoordinate = mapView.convert(touchPoint, toCoordinateFrom:mapView)
print("latitude: \(touchMapCoordinate.latitude), longitude: \(touchMapCoordinate,longitude)")
}