Detect tap gesture on mkmapview that isn't the annotation? - objective-c

How do I detect if the mapview is being tapped on, that isn't an annotation in obj-c?

Swift 4,
I fixed this in our case,
by adding tap gestures to both annotation view and map view.
add tap gesture to map
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.hideFilterView))
self.mapView.addGestureRecognizer(tapGesture)
//override "viewFor annotation:" something like this
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = self.mapView.dequeueReusableAnnotationView(withIdentifier: "Pin")
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "Pin"
annotationView?.canShowCallout = false
} else {
annotationView?.annotation = annotation
}
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.didTapPin(onMap:)))
annotationView?.addGestureRecognizer(tapGesture)
return annotationView
}
//then don't override
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { }
So the all pins selections will be handled using tap gestures.
And you can detect map and pins tap separately.

Try UITapGestureRecognizer:
UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(mapDidTap:)];
[mapView addGestureRecognizer: tapGesture];
And
-(void)mapDidTap:(UITapGestureRecognizer *)gestureRecognizer {
//do something when map is tapped
}

The following code detects a tap in the map and adds a map marker at that location:
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *fingerTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleMapFingerTap:)];
fingerTap.numberOfTapsRequired = 1;
fingerTap.numberOfTouchesRequired = 1;
[self.mapView addGestureRecognizer:fingerTap];
}
- (void)handleMapFingerTap:(UIGestureRecognizer *)gestureRecognizer {
NSLog(#"handleMapFingerTap gesture: %#", gestureRecognizer);
if (gestureRecognizer.state != UIGestureRecognizerStateEnded) {
return;
}
CGPoint touchPoint = [gestureRecognizer locationInView:self.mapView];
CLLocationCoordinate2D touchMapCoordinate =
[self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
pa.coordinate = touchMapCoordinate;
pa.title = #"Hello";
[self.mapView addAnnotation:pa];
}

Related

pan gesture recognise button issue

I set a pan gesture recogniser to recognise my touch on some buttons and am running into the following issue. I am trying to add an action to each of the buttons. I'm testing this by telling each different button to become highlighted when I touch them. So far when I keep my finger pressed on the screen en slide around only button1 and button2 show up (as written in the code).
But for some reason I can still see other buttons highlight the same way when I press them individually. Any idea how to solve this so that they only respond to. If button.tag == 3 etc.. Then respond? Here is the code. (This is all the code in the project and a few buttons in the interface builder.)
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Add Gesture to track the finger
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self.view addGestureRecognizer:pan];
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateChanged) {
CGPoint point = [gesture locationInView:self.view];
for (UIButton *button in [self.view subviews]) {
if ([button isKindOfClass:[UIButton class]]) {
if (button.tag == 1) {
button.highlighted = CGRectContainsPoint(button.frame, point);
} else if (button.tag == 2) {
button.highlighted = CGRectContainsPoint(button.frame, point);
} //
}
}
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
for (UIButton *button in [self.view subviews]) {
if ([button isKindOfClass:[UIButton class]]) {
button.highlighted = NO;
}
}
}
}
EDIT :
[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanned:)];
- (void)handlePanned:(UIPanGestureRecognizer*)thePanner{
if (thePanner.state == UIGestureRecognizerStateChanged ){
//disable button
}else if (thePanner.state == UIGestureRecognizerStateEnded) {
//enable button
}else if ( thePanner.state == UIGestureRecognizerStateFailed ){
//enable button
}
}
You aren't checking to see if where you are touching is where a button is located.
Quickest solution I see is:
Create properties for each of your buttons, we'll call them button1, button2 and button3.
Create your panGestureRecognizer
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self.view addGestureRecognizer:pan];
}
Your method to handle the pan:
-(void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
//create a CGpoint so you know where you are touching
CGPoint touchPoint = [gesture locationInView:self.view];
//just to show you where you are touching...
NSLog(#"%#", NSStringFromCGPoint(touchPoint));
//check your button frame's individually to see if you are touching inside it
if (CGRectContainsPoint(self.button1.frame, touchPoint))
{
NSLog(#"you're panning button1");
}
else if(CGRectContainsPoint(self.button2.frame, touchPoint))
{
NSLog(#"you're panning button2");
}
else if (CGRectContainsPoint(self.button3.frame, touchPoint))
{
NSLog(#"you're panning button3");
}
And that should be it.

iOS 5 custom annotation not showing its title

I wrote this code to create a custom annotation image
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
static NSString *google = #"googlePin";
if ([annotation isKindOfClass:[myClass class]])
{
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:google];
if (!annotationView)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:google];
annotationView.image = [UIImage imageNamed:#"pin.png"];
}
return annotationView;
}
return nil;
}
The image is appearing on the map; however when I click on it nothing happen, no title nor subtitle.
Do you guys have any idea?
When you override viewForAnnotation, you have to set canShowCallout to YES (the default on a new view you alloc/init is NO).
If you don't override that delegate method, the map view creates a default red pin with canShowCallout already set to YES.
However, even with canShowCallout set to YES, the callout will still not appear if the annotation's title is nil or blank (empty string).
(But again, if the title is not nil and not blank, the callout won't show unless canShowCallout is YES.)
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:google];
if (!annotationView)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:google];
annotationView.image = [UIImage imageNamed:#"pin.png"];
annotationView.canShowCallout = YES; // <-- add this
}
else
{
// unrelated but should handle view re-use...
annotationView.annotation = annotation;
}
return annotationView;

Dismiss modal view form sheet controller on outside tap

I am presenting a modal view controller as a form sheet and dismissing it when the cancel button, which is a bar button item, is clicked. I need to dismiss it when I tap on outside of that view. Please help me with a reference. Note: my modal view controller is presented with a navigation controller.
#cli_hlt, #Bill Brasky thanks for your answer. I need to dismiss it when tap occurs outside of the modal view which is a form sheet. I am pasting my code below.
-(void)gridView:(AQGridView *)gridView didSelectItemAtIndex:(NSUInteger)index
{
if(adminMode)
{
CHEditEmployeeViewController *editVC = [[CHEditEmployeeViewController alloc] initWithNibName:#"CHEditEmployeeViewController" bundle:nil];
editVC.delegate = self;
editVC.pickedEmployee = employee;
editVC.edit = TRUE;
editVC.delegate = self;
UINavigationController *navigationController = [[UINavigationController alloc]initWithRootViewController:editVC];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:navigationController animated:YES];
return;
} //the above code is from the view controller which presents the modal view. Please look at the below code too which is from my modal view controller. Please guide me in a proper way. -(void)tapGestureRecognizer {
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view addGestureRecognizer:recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
[self dismissModalViewControllerAnimated:YES];
[self.view.window removeGestureRecognizer:sender];
}
}
}
I know this is an old question but this IS possible, despite of what the "right" answer says. Since this was the first result when I was looking for this I decided to elaborate:
This is how you do it:
You need to add a property to the View Controller from where you want to present modally, in my case "tapBehindGesture".
then in viewDidAppear
if(!tapBehindGesture) {
tapBehindGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapBehindDetected:)];
[tapBehindGesture setNumberOfTapsRequired:1];
[tapBehindGesture setCancelsTouchesInView:NO]; //So the user can still interact with controls in the modal view
}
[self.view.window addGestureRecognizer:tapBehindGesture];
And Here is the implementation for tapBehindDetected
- (void)tapBehindDetected:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
//(edited) not working for ios8 above
//CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
CGPoint location = [sender locationInView: self.presentingViewController.view];
//Convert tap location into the local view's coordinate system. If outside, dismiss the view.
if (![self.presentedViewController.view pointInside:[self.presentedViewController.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
if(self.presentedViewController) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
}
Just remember to remove tapBehindGesture from view.window on viewWillDisappear to avoid triggering handleTapBehind in an unallocated object.
I solved iOS 8 issue by adding delegate to gesture recognizer
[taprecognizer setDelegate:self];
with these responses
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return YES;
}
that works for me with iOS 8 GM
Swift 4 version that works in both portrait and landscape - no swapping of x, y required.
class TapBehindModalViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if (self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
// MARK: - Gesture methods to dismiss this with tap outside
#objc func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.ended) {
let location: CGPoint = sender.location(in: self.view)
if (!self.view.point(inside: location, with: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender: sender)
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
As far as I can tell none of the answer seem to be working right away in every condition.
My solution (either inherit from it or paste it in):
#interface MyViewController () <UIGestureRecognizerDelegate>
#property (strong, nonatomic) UITapGestureRecognizer *tapOutsideRecognizer;
#end
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (!self.tapOutsideRecognizer) {
self.tapOutsideRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
self.tapOutsideRecognizer.numberOfTapsRequired = 1;
self.tapOutsideRecognizer.cancelsTouchesInView = NO;
self.tapOutsideRecognizer.delegate = self;
[self.view.window addGestureRecognizer:self.tapOutsideRecognizer];
}
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// to avoid nasty crashes
if (self.tapOutsideRecognizer) {
[self.view.window removeGestureRecognizer:self.tapOutsideRecognizer];
self.tapOutsideRecognizer = nil;
}
}
#pragma mark - Actions
- (IBAction)close:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self close:sender];
}
}
}
#pragma mark - Gesture Recognizer
// because of iOS8
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Here is my version that works for iOS 7 and iOS 8 and does not require conditional swapping of coordinates:
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:self.view];
if (![self.view pointInside:location withEvent:nil]) {
[self.view.window removeGestureRecognizer:self.recognizer];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
For iOS 8, you must both implement the UIGestureRecognizer per Martino's answer, and swap the (x,y) coordinates of the tapped location when in landscape orientation. Not sure if this is due to an iOS 8 bug.
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// add gesture recognizer to window
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
recognizer.delegate = self;
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
// passing nil gives us coordinates in the window
CGPoint location = [sender locationInView:nil];
// swap (x,y) on iOS 8 in landscape
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) {
// remove the recognizer first so it's view.window is valid
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}
Based on Bart van Kuik's answer and NavAutoDismiss and other great snippets here.
class DismissableNavigationController: UINavigationController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if tapOutsideRecognizer == nil {
tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(DismissableNavigationController.handleTapBehind))
tapOutsideRecognizer.numberOfTapsRequired = 1
tapOutsideRecognizer.cancelsTouchesInView = false
tapOutsideRecognizer.delegate = self
view.window?.addGestureRecognizer(tapOutsideRecognizer)
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if tapOutsideRecognizer != nil {
view.window?.removeGestureRecognizer(tapOutsideRecognizer)
tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
func handleTapBehind(sender: UITapGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Ended {
var location: CGPoint = sender.locationInView(nil)
if UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation) {
location = CGPoint(x: location.y, y: location.x)
}
if !view.pointInside(view.convertPoint(location, fromView: view.window), withEvent: nil) {
view.window?.removeGestureRecognizer(sender)
close(sender)
}
}
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Usage:
let vc = MyViewController()
let nc = DismissableNavigationController(rootViewController: vc)
nc.modalPresentationStyle = UIModalPresentationStyle.FormSheet
presentViewController(nc, animated: true, completion: nil)
#yershuachu's answer, in Swift 2:
class ModalParentViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if(self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: "handleTapBehind:")
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.Ended) {
let location: CGPoint = sender.locationInView(nil)
if (!self.view.pointInside(self.view.convertPoint(location, fromView: self.view.window), withEvent: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender)
}
}
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Swift 3
class ModalParentViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if(self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
appDelegate.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
appDelegate.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
// MARK: - Gesture methods to dismiss this with tap outside
func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.ended) {
let location: CGPoint = sender.location(in: nil)
if (!self.view.point(inside: self.view.convert(location, from: self.view.window), with: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender: sender)
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Ah ok. So I'm afraid thats not quite possible using the presentModalViewController: method. The whole idea of a "modal" view/window/message box/etc. pp. is that the user cannot do anything else than processing whatever the view/window/message box/etc. pp. wants him/her to do.
What you want to do instead is not present a modal view controller, but rather load and show your form view controller the regular way. Note in your master controller that the form is just showing e.g. with a BOOL variable and then handle there any taps that might occur. If your form is showing, dismiss it.
I use it in this form without any problems neither on iOS 7.1 nor iOS 8.3.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.view.window addGestureRecognizer:self.tapBehindGesture];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.view.window removeGestureRecognizer:self.tapBehindGesture];
}
- (UITapGestureRecognizer*)tapBehindGesture
{
if (_tapBehindGesture == nil)
{
_tapBehindGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapBehindRecognized:)];
_tapBehindGesture.numberOfTapsRequired = 1;
_tapBehindGesture.cancelsTouchesInView = NO;
_tapBehindGesture.delegate = self;
}
return _tapBehindGesture;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
FYI, on iOS 13 form sheets now have a standard dismiss gesture - they go away when you touch anywhere inside the modal and swipe it down (set the new isModalInPresentation property to false if you want to prevent that).
I made a navigationController that auto dismiss for iPad
https://github.com/curciosobrinho/NavAutoDismiss
It is just the same code as above, working on iOS 8.
So, all the credits go to the people above.
I just did it to be more generic and easier to use.
You just need to copy BOTH files to your project
Import the header file (.h)
And use your viewcontroller (that you want to show) as the rootViewController.
How to use it:
//Import the .h file
#import "NavDismissViewController.h"
//Instanciate your view controller (the view you want to present)
YourViewController * yourVC = [YourViewController new];
//Instanciate the NavDismissViewController with your view controller as the rootViewController
NavDismissViewController *nav = [[NavDismissViewController alloc] initWithRootViewController:yourVC];
//if you want to change the navigationBar translucent behaviour
[nav.navigationBar setTranslucent:NO];
//Choose the Modal style
nav.modalPresentationStyle=UIModalPresentationFormSheet;
//present your controller
[self presentViewController:nav animated:YES completion:nil];
//Done
Based on some of the questions and with some improvements and updates:
- (void)tapOutsideDetected:(UITapGestureRecognizer *)sender {
if (#available(iOS 13, *)) {} else {
UIView *aView = self.navigationController ? self.navigationController.view : self.view;
CGPoint location = [sender locationInView: aView];
if (![aView pointInside:location withEvent:nil]) {
[self removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
Where:
- (void) removeGestureRecognizer:(UIGestureRecognizer *) gesture {
if (self.recognizer) {
UIView *aView = self.recognizer.view;
if (aView) {
[aView removeGestureRecognizer:self.recognizer];
}
}
}
In this case, the UITapGestureRecognizer has been applied to the UIViewController

MKAnnotationView for userLocation pin iPhone

I have an application that uses MKMapView and at some point in the app I need to remove all the current pins in the map using
[mapView removeAnnotations:mapView.annotations]
And then I want to show again the current user location
mapView.showUserLocation = YES
But I can only make it reappear as a regular pin, because the userLocation view class its not public so I cant return views of that type. Here is my code
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id<MKAnnotation>)annotation
MKPinAnnotationView* annView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"currentloc"];
if (!annView) {
MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentloc"];
annView.pinColor = MKPinAnnotationColorRed;
annView.animatesDrop=TRUE;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
if ([annotation isKindOfClass:[DraggableAnnotationAM class]] ) annView.draggable =YES;
return annView;
}
else {
if ([annotation isKindOfClass:[DraggableAnnotationAM class]] ) annView.draggable = YES;
annView.annotation = annotation;
return annView;
}
Also I have found through reflection that
MKUserLocationView
is the class that is used to display the current location, but because its not public its not safe to use and my app keeps crashing and Im sure theres a easier way.
Is it possible to do what I want, or should I just never remove the user location annotation of the mapView?
Thanks in advance
If you only want to show a blue dot for the user's location in stead of a regular pin, return nil in this mapView delegate function. Alternatively return your own MKAnnotationView (subclass) object:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil // or return a custom MKAnnotationView
} else { // etc.
Try doing it with an NSMutableArray. Hope this helps you solving your problem!
NSMutableArray *annotation = [[NSMutableArray alloc] init];
for (id <MKAnnotation> annot_ in [mapView annotations])
{
if ( [annot_ isKindOfClass:[MKUserLocation class]] ) {
}
else {
[annotation addObject:annot_];
}
}
[mapView removeAnnotations:annotation];
[annotation release];
annotation = nil;

Showing the Pin Information by Default

I am working with MKPinAnnotationView annotations pins over a MKMapView. Into the view that I am developing, there is a map centered on a pin. When I call the view the pin drops down and if I make a click over it, it shows the annotation information (canShowCallout = YES). How I can get this callout just when I open the view? Without making a click over the annotation pin.
Thanks for reading.
Edited:
I am using this code.
- (void)viewDidLoad {
[super viewDidLoad];
AddressAnnotation *annotation = [[[AddressAnnotation alloc] initWithCoordinate:self.currentAnnotationCoordinate] autorelease];
[self.mapView addAnnotation:annotation];
[self zoomCoordinate:self.currentAnnotationCoordinate];
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation {
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
else { // Handles the other annotations.
// Try to dequeue an existing pin view first.
static NSString *AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView) {
// If an existing pin view was not available, creates one.
MKPinAnnotationView *customPinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
// Adds a detail disclosure button.
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
} else
pinView.annotation = annotation;
}
return nil;
}
- (void)mapViewDidFinishLoadingMap:(MKMapView *)theMapView {
AddressAnnotation *annotation = [[[AddressAnnotation alloc] initWithCoordinate:self.currentAnnotationCoordinate] autorelease];
for (id<MKAnnotation> currentAnnotation in mapView.annotations) {
if ([currentAnnotation isEqual:annotation]) {
[mapView selectAnnotation:currentAnnotation animated:FALSE];
}
}
}
Edited:
Calling didAddAnnotationViews: instead of mapViewDidFinishLoadingMap: (as aBitObvious has commented), it works fine.
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
id<MKAnnotation> myAnnotation = [self.mapView.annotations objectAtIndex:0];
[self.mapView selectAnnotation:myAnnotation animated:YES];
}
Possible duplicate: How to trigger MKAnnotationView's callout view without touching the pin?
You want to call [mapView selectAnnotation:].