Dismiss modal form sheet view on outside tap iOS 8 - ios

I've been trying to dismiss the modal form sheet view on outside tap on iOS 8 with no luck,
I've tried this code
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];
- (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 dismissModalViewControllerAnimated:YES];
}
}
}
But it doesn't detect outside view clicks, any suggestions ?

There are actually two problems in iOS 8. First, the gesture recognition does not begin.
I solved this by adding the UIGestureRecognizerDelegate protocol and implementing
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
{
return YES;
}
Also, don't forget to register the delegate with
recognizer.delegate = self;
Now the gesture recognizer should recognize gestures and the target method (handleTapBehind:) will be called.
Here comes the second problem in iOS 8: locationInView: doesn't seem to take the device orientation into account if nil is passed as a view. Instead, passing the root view works.
Here's my target code that seems to work for iOS 7.1 and 8.0:
if (sender.state == UIGestureRecognizerStateEnded) {
UIView *rootView = self.view.window.rootViewController.view;
CGPoint location = [sender locationInView:rootView];
if (![self.view pointInside:[self.view convertPoint:location fromView:rootView] withEvent:nil]) {
[self dismissViewControllerAnimated:YES completion:^{
[self.view.window removeGestureRecognizer:sender];
}];
}
}

In iOS 8, You can look at using the new UIPresentationController class. It gives you better control over the container around your custom view controller presentation (allowing you to correctly add a gesture recogniser of your own).
Here is a link to quite a simple tutorial as well: http://dativestudios.com/blog/2014/06/29/presentation-controllers/
Then add the dimming view tap-to-dismiss:
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[self.dimmingView addGestureRecognizer:singleFingerTap];
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}

Swift 3.1 solution that works in both portrait and landscape.
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
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
}
}

Related

Run action if a existing screen interaction passes over and stops on a UIView

How do I add an action to a uiview that will run if a screen interaction (long press) moves to a uiview and then stays there for a select period of time.
EDIT: I have a drop down menu sort of system and a long press on the button reveals the menu. Then a user would slide down onto an option and then release the tap. I want to tell which menu item that happened on and act accordingly.
Add the below gesture to your UIView for long press functionality.
var longTap = UILongPressGestureRecognizer(target: self, action: #selector(self.longTouch))
longTap.numberOfTapsRequired = 0 // Set your own number here
longTap.minimumPressDuration = 1.0 //Set your duration here
longTap.delegate = self// Add the <UIGestureRecognizerDelegate> protocol
self.view.addGestureRecognizer(longTap)
Add the delegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Below is selector will be called
func longTouch(_ recognizer: UILongPressGestureRecognizer) {
if recognizer.state == .began {
print("longTouch UIGestureRecognizerStateBegan")
}
if recognizer.state == .ended {
print("longTouch UIGestureRecognizerStateEnded")
}
}
Objective C version
UILongPressGestureRecognizer *longTap = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longTouch:)];
[longTap setNumberOfTapsRequired:0]; // Set your own number here
[longTap setMinimumPressDuration:1.0]; // Set your duration here
[longTap setDelegate:self]; // Add the <UIGestureRecognizerDelegate> protocol
[self.view addGestureRecognizer:longTap];
Delgate:-
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Selector:-
- (void) longTouch: (UILongPressGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan)
{
NSLog(#"longTouch UIGestureRecognizerStateBegan");
}
if (recognizer.state == UIGestureRecognizerStateEnded)
{
NSLog(#"longTouch UIGestureRecognizerStateEnded");
}
}

How do I prevent taps on a subview from triggering a UITapGestureRecognizer on a parent view?

I added a tap recognizer to a view:
UITapGestureRecognizer* tgr = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector( onTap )];
[view addGestureRecognizer: tgr];
The problem is that taps on subviews of view trigger onTap. How do I prevent that?
Suppose your parentView has a subView. You implement the following UIGestureRecognizerDelegate method, if the touch is inside the bounds of subView, you return no.
tgr.delegate = self;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
CGPoint locationInView = [touch locationInView:self.parentView];
if (CGRectContainsPoint(self.subView.frame, locationInView) ) {
return NO;
} else {
return YES;
}
}
Add subview to the background of the view, and attach tap gesture recogniser to the subview:
UIView* subview = [[UIView alloc] initWithFrame:view.bounds];
subview.backgroundColor = [UIColor clearColor];//or view.backgroundColor
[view addSubview:subview];
[view sendSubviewToBack:subview];
[subview addGestureRecognizer:tapRecognizer];
Swift 4.2 answer for an AlertView
Add the gesture recogniser and set the delegate:
let backgroundViewTapGesture = UITapGestureRecognizer(target: self, action: #selector(dismiss))
backgroundViewTapGesture.delegate = self
self.backgroundView.addGestureRecognizer(backgroundViewTapGesture)
self.backgroundViewTapGesture = backgroundViewTapGesture
Then add the extension to handle the tap
extension AlertView: UIGestureRecognizerDelegate {
// Only handle this for the background tap gesture
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
guard gestureRecognizer == backgroundViewTapGesture, let backgroundView = gestureRecognizer.view, let alertView = self.alertContentView else {
return true
}
let touchLocation = touch.location(in: backgroundView)
return !alertView.frame.contains(touchLocation)
}
}
In Swift:
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let locationInView = touch.location(in: mainView)
if debugOptionsView.isHidden {
return true
}
return !debugOptionsView.frame.contains(locationInView)
}

SWRevealViewController: Remove interaction on frontview when rearview is revealed

I need to disable user interaction on front view when rear view is revealed. Found some others asking the same thing but can't really understand where or how to implement the code that I've seen.
Ex: I found this code from link,
- (void)revealController:(SWRevealViewController *)revealController
willMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
- (void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
Also found few other links
Link1
Link2
Link3
I have this code, but not really sure about the correct place to insert this code. I've tried adding it in my front/rear views and also in the SWRevealViewController method with no success
Appreciate if someone can point me in the right direction.
I've recently come up with a solution that I wanted to share
(sorry if it's 2 months late).
To disable user interaction on the Front View while the Menu is open, I added the following codes on my MenuViewController:
on viewWillAppear:
[self.revealViewController.frontViewController.view setUserInteractionEnabled:NO];
and on viewWillDisappear:
[self.revealViewController.frontViewController.view setUserInteractionEnabled:YES];
This will disable all user interactions on the Front View Controller, which means that the slide / tap gestures to CLOSE the menu will also be DISABLED.
Now, I have created a ParentViewController and made all the view controllers (the menu items) a subclass of it.
on my viewDidLoad, I put the following codes:
SWRevealViewController *revealController = [self revealViewController];
[revealController panGestureRecognizer];
[revealController tapGestureRecognizer];
If you run your app at this point, it would appear that the Tap Gesture works (a tap on the Front View will close the Menu), but NOT the Pan Gesture. I'm not sure why this is so, but in order to enable the slide gesture to CLOSE your menu, add the following code in your MenuViewController:
on viewWillAppear:
[self.revealViewController.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
To summarize, here's what you need:
On your MenuViewController:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.revealViewController.frontViewController.view setUserInteractionEnabled:NO];
[self.revealViewController.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.revealViewController.frontViewController.view setUserInteractionEnabled:YES];
}
And on your menu items' view controller (you can make a ParentViewController for all of them):
-(void)viewDidLoad {
[super viewDidLoad];
SWRevealViewController *revealController = [self revealViewController];
[revealController panGestureRecognizer];
[revealController tapGestureRecognizer];
}
Hope this helps!
I have used another approach to achieve the same outcome not sure if it helps.
Assign SWRevealViewControllerDelegate to AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
SWRevealViewController* reveal = (SWRevealViewController*)self.window.rootViewController;
reveal.delegate = self;
// other bootstrapping code
}
and then in the delegate method -(void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position as below:
-(void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position
{
if(position == FrontViewPositionLeft){
[revealController.frontViewController.view setUserInteractionEnabled:YES];
[revealController.frontViewController.revealViewController tapGestureRecognizer];
}else{
[revealController.frontViewController.view setUserInteractionEnabled:NO];
}
}
UPDATED: added this line [revealController.frontViewController.revealViewController tapGestureRecognizer] to close the revealed controller when tap on frontviewcontroller
Swift version to #hardluckbaby answer:
In MenuViewController(Rear view controller):
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.revealViewController().frontViewController.view.userInteractionEnabled = false
self.revealViewController().view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.revealViewController().frontViewController.view.userInteractionEnabled = true
}
In FrontViewController(You can make a ParentViewController for all of your front view controllers as #hardluckbaby said):
override func viewDidLoad() {
super.viewDidLoad()
if let revealController = self.revealViewController() {
revealController.panGestureRecognizer()
revealController.tapGestureRecognizer()
}
}
As John Explained: Although these solutions do work, I don't think any of them address the original question, which is actually quite simple:
There are 2 steps involved:
Add the following methods to your FrontViewController.m:
(void)revealController:(SWRevealViewController *)revealController
willMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
(void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
Make your front view controller be a delegate of SWRevealViewController in the FrontViewController.h file:
(e.g. #interface HomeViewController : UIViewController )
where my FrontViewController was named HomeViewController
and also in the FrontViewController.m file with the following on ViewDidLoad:
self.revealViewController.delegate = self;
Problem solved! Much easier than creating parent classes, etc.
This will help you solve the user interactions for the FrontView controller lovely I would just add the following change taken from Xun's response also above and you will solve both the user interactions and the hide menu when user taps FrontViewController.
- (void)revealController:(SWRevealViewController *)revealController
willMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
- (void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
//Hides the menu when user taps FrontViewController
[revealController.frontViewController.revealViewController tapGestureRecognizer];
}
}
Swift 3.0 simple and fast method.
Frontviewcontoller code here...
override func viewDidLoad() {
super.viewDidLoad()
if self.revealViewController() != nil {
let rewel:SWRevealViewController = revealViewController()
rewel.panGestureRecognizer()
rewel.tapGestureRecognizer()
}
}
SideDrowerviewcontoller code here...
override func viewWillAppear(_ animated: Bool) {
let rewel = self.revealViewController()
rewel?.frontViewController.view.isUserInteractionEnabled = false
rewel?.frontViewController.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
override func viewWillDisappear(_ animated: Bool) {
let rewel = self.revealViewController()
rewel?.frontViewController.view.isUserInteractionEnabled = true
}
Add a subview to front view when rear view is open.
In viewWillAppear method of your menu items controller, Just create an overlay button on the front view and set action to revealToggle: of revealViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
overlayView = [UIButton buttonWithType:UIButtonTypeCustom];
overlayView.frame = self.revealViewController.frontViewController.view.bounds;
overlayView.backgroundColor = [UIColor colorWithWhite:0.5 alpha:0.8];
overlayView.tag = 999;
[overlayView addTarget:self.revealViewController action:#selector(revealToggle:) forControlEvents:UIControlEventTouchUpInside];
[overlayView addTarget:self.revealViewController action:#selector(revealToggle:) forControlEvents:UIControlEventTouchDragOutside];
[self.revealViewController.frontViewController.view addSubview:overlayView];
}
In revealTogglle method remove the overlay button if any:
- (void)revealToggleAnimated:(BOOL)animated
{
UIButton *overlayView = (UIButton*)[self.view viewWithTag:999];
if (overlayView) {
[overlayView removeFromSuperview];
overlayView = nil;
}
// rest of the code...
}
Although these solutions do work, I don't think any of them address the original question, which is actually quite simple:
There are 2 steps involved:
1) Add the following methods to your FrontViewController.m:
- (void)revealController:(SWRevealViewController *)revealController
willMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
- (void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
2) Make your front view controller be a delegate of SWRevealViewController in the FrontViewController.h file:
(e.g. #interface HomeViewController : UIViewController <SWRevealViewControllerDelegate>)
where my FrontViewController was named HomeViewController
and also in the FrontViewController.m file with the following on ViewDidLoad:
self.revealViewController.delegate = self;
Problem solved! Much easier than creating parent classes, etc.
Another way is to have an overlay view when the rear view is revealed. You can use this updated library https://github.com/NSRover/SWRevealViewController and make sure you include shouldUseFrontViewOverlay = true when the rear view is revealed.
class SideMenuViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.revealViewController().delegate = self
}
}
extension SideMenuViewController: SWRevealViewControllerDelegate {
func revealController(revealController: SWRevealViewController!, willMoveToPosition position: FrontViewPosition) {
if position == .Left {
revealController.frontViewController.view.userInteractionEnabled = true
}
if position == .Right {
revealController.frontViewController.view.userInteractionEnabled = false
}
}
}
On MenuTableViewController/ Rear VC, add SWRevealViewControllerDelegate.
override func viewDidLoad() {
super.viewDidLoad()
self.revealViewController().delegate = self
if self.revealViewController() != nil {
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
}
Add this delegate method.
func revealController(revealController: SWRevealViewController!, didMoveToPosition position: FrontViewPosition) {
if(position.rawValue == 4)
{
//move to rear
self.revealViewController().frontViewController.view.userInteractionEnabled = false
}
else if (position.rawValue == 3)
{
//move to front - dashboard VC
self.revealViewController().frontViewController.view.userInteractionEnabled = true
}
}
func revealController(revealController: SWRevealViewController!, willMoveToPosition position: FrontViewPosition) {
//will perform the same function as the above delegate method.
}
Consider following solution, works perfect
private let DimmingViewTag = 10001
extension UIViewController: SWRevealViewControllerDelegate {
func removeInteractionFromFrontViewController() {
revealViewController().delegate = self
view.addGestureRecognizer(revealViewController().panGestureRecognizer())
}
//MARK: - SWRevealViewControllerDelegate
public func revealController(revealController: SWRevealViewController!, didMoveToPosition position: FrontViewPosition) {
if case .Right = position {
let dimmingView = UIView(frame: view.frame)
dimmingView.tag = DimmingViewTag
view.addSubview(dimmingView)
view.bringSubviewToFront(dimmingView)
} else {
view.viewWithTag(DimmingViewTag)?.removeFromSuperview()
}
}
}
Simple usage in UIViewController:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
removeInteractionFromFrontViewController()
}
Addition to hardluckbaby answer.
If you run your app at this point, it would appear that the Tap
Gesture works (a tap on the Front View will close the Menu), but NOT
the Pan Gesture. I'm not sure why this is so, but in order to enable
the slide gesture to CLOSE your menu, add the following code in your
MenuViewController:
on viewWillAppear:
[self.revealViewController.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
It adds some undesired behavior, e.g. pan gesture will close rear view when starts on it.
Default pan gesture may not work if you add it to your own view somewhere, something like on viewDidLoad of your front view controller:
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
Remove such lines and this should works as expected, for pan and tap gestures
SWRevealViewController *revealController = [self revealViewController];
[revealController panGestureRecognizer];
[revealController tapGestureRecognizer];
I was using the viewWillAppear and viewWillDisappear functions but as I have subviews for almost every item in the side menu I had. My issue was that I had a input field active (keyboard displaying) and accessed the side menu. In the root menu the keyboard hid but after I entered a submenu keyboard showed up again. To solve this I changed the approach to enable and disable the interaction in revealController like this:
- (void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if (position == FrontViewPositionRight) {
[self.revealViewController.frontViewController.view setUserInteractionEnabled:NO];
} else if (position == FrontViewPositionLeft) {
[self.revealViewController.frontViewController.view setUserInteractionEnabled:YES];
}
}
Firstly just set your delegate :
self.revealViewController.delegate = self;
and the delegate method are given below :
- (void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position
{
static NSInteger tagLockView = 123456789;
if (revealController.frontViewPosition == FrontViewPositionRight)
{
UIView *lockView = [self.view viewWithTag:tagLockView];
[UIView animateWithDuration:0.3 animations:^{
lockView.alpha = 0;
} completion:^(BOOL finished) {
[lockView removeFromSuperview];
}];
}
else if (revealController.frontViewPosition == FrontViewPositionLeft)
{
UIView *lockView = [[UIView alloc] initWithFrame:self.view.bounds];
lockView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
lockView.tag = tagLockView;
lockView.backgroundColor = [UIColor blackColor];
lockView.alpha = 0;
[lockView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self.revealViewController action:#selector(revealToggle:)]];
[self.view addSubview:lockView];
[UIView animateWithDuration:0.3 animations:^{
lockView.alpha = 0.5;
}];
}
}

iOS MapView: Add Annotation on tap, but not if existing Annotation Tapped

I have a UITapGestureRecognizer setup to add an annotation onto the map where the user clicks. The problem I'm running into is when the user taps an existing annotation to view the tooltip, the tooltip pops up, but another annotation is added to the map behind the clicked annotation. Is there a way to detect if an annotation was tapped and return before adding the annotation?
This is my viewDidLoad:
UITapGestureRecognizer *singleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(foundTap:)];
singleTapRecognizer.numberOfTapsRequired = 1;
[self.mapView addGestureRecognizer:singleTapRecognizer];
And my on touch function:
-(IBAction)foundTap:(UITapGestureRecognizer *)recognizer
{
CGPoint point = [recognizer locationInView:self.mapView];
CLLocationCoordinate2D tapPoint = [self.mapView convertPoint:point toCoordinateFromView:self.view];
AGIAnnotation * annotation = [[AGIAnnotation alloc] initWithCoordinate:tapPoint];
// Some other stuff
[self.mapView addAnnotation:annotation];
}
Any help is appreciated.
There is a UIGestureRecognizerDelegate Protocol
Implement gestureRecognizer:shouldReceiveTouch: and return NO if the touch is on an existing tooltip. Something like this:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isKindOfClass:[MKPinAnnotationView class]])
{
return NO;
}
return YES;
}
#mbehan answer in Swift 3+:
in ViewDidLoad:
let singleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(YourClass.foundTap(_:)))
singleTapRecognizer.delegate = self
mapView.addGestureRecognizer(singleTapRecognizer)
and the UIGestureRecognizerDelegate extension:
extension YourClass: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return !(touch.view is MKPinAnnotationView)
}
}

How to dismiss keyboard when user tap other area outside textfield?

today I tried to run my code on my iPod (iOS 6.1.3) and I found something interesting here...
first, when I tap on textfield the keyboard shows up but it won't hide when I tap somewhere else outside textfield.
so I decided to Googling and found this solution :
_fieldEmail.delegate = self;
_fieldEmail.returnKeyType = UIReturnKeyDone;
_fieldPassword.delegate = self;
_fieldPassword.returnKeyType = UIReturnKeyDone;
_fieldRegisterName.delegate = self;
_fieldRegisterName.returnKeyType = UIReturnKeyDone;
_fieldRegisterEmail.delegate = self;
_fieldRegisterEmail.returnKeyType = UIReturnKeyDone;
_fieldRegisterPassword.delegate = self;
_fieldRegisterPassword.returnKeyType = UIReturnKeyDone;
it works... it gives a 'DONE' button on the bottom of keyboard and now the keyboard can be hidden by pressing it.
but I have 2 problems here :
the keyboard only hide when 'DONE' button is tapped. not by tapping other area outside text field. I don't know if this normal on iOS world, but usually I see lot of apps don't act like this.
is there any way to loop this process so I don't have manually add that delegate one by one of all textfield that I have? how to do that?
that's all I need to know
The below code will work on all the components in the UIView for all the UITextField
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UIView * txt in self.view.subviews){
if ([txt isKindOfClass:[UITextField class]] && [txt isFirstResponder]) {
[txt resignFirstResponder];
}
}
}
OR
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
Simply add an UITapGestureRecogniser to your view that will lead to calling resignFirstResponder
In viewDidLoad :
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(hideKeyBoard)];
[self.view addGestureRecognizer:tapGesture];
And then :
-(void)hideKeyBoard {
[yourTextField resignFirstResponder];
}
2.You could subclass UITextField but unless you have 1000 textFields it is ok to do like you currently do.
This is regards to Swift programming for Xcode 6.0.1
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let tapRecognizer = UITapGestureRecognizer(target: self, action: "handleSingleTap:")
tapRecognizer.numberOfTapsRequired = 1
self.view.addGestureRecognizer(tapRecognizer)
}
func handleSingleTap(recognizer: UITapGestureRecognizer) {
self.view.endEditing(true)
}
Use Either
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[self.view addGestureRecognizer:singleTap];
and code of method
-(void)handleSingleTap:(UITapGestureRecognizer *)sender{
[self.TextFiledName resignFirstResponder];
}
OR _ And The best Other option is
Just add
[self.view endEditing:YES];
And key board will hide when you tapped anywhere from view:)
Dismissing the keyboard
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.view endEditing:YES];
[super touchesBegan:touches withEvent:event];
}
Simple,
Use IQKeyboardManager and put this line of code in AppDelegate.m
[[IQKeyboardManager sharedManager] setShouldResignOnTouchOutside:YES];
What I usually do is call
[field resignFirstResponder];
from an invisible button over the view. I'm sure theres a nicer way to do it. It's been a while since I've done this in an app.
Here's how I do it:
In the myView's .h file:
#property (strong) UITextField * savedTextField;
In the myView's .m file:
#implementation myView
#synthesize savedTextField;
In the myView's init routine:
[self setSavedTextField: nil];
In the myView's textField delegate area I save the id of whichever textField is currently activating:
- (BOOL) textFieldShouldBeginEditing: (UITextField *) textField
{
[self setSavedTextField: textField];
.
.
.
return( YES );
}
And in the myView's routine that routes all the traffic for the other controls when they are accessed, I have code that looks to see if a textField was active and, if one was, it calls on it to resign its FirstResponder status and then drops a nil into the savedTextField property:
- (void) handleCtrlEvents: (UIControl *) aCtrl
{
if ( [self savedTextField] != nil )
{
[[self savedTextField] resignFirstResponder];
[self setSavedTextField: nil];
}
.
.
.
}
This works cleanly for me.
I ran into the exact same problem. Usually what you want is, that wherever you click inside your App (which is displayed in your main / current UIView) but not on the keyboard to close it. After searching the web for a smart solution and testing several, I think it is the best to extend/subclass/implement the UIGestureRecognizerDelegate
Since many controls such as UITableView etc. can respond to gestures (select, scrolling etc.) and if you simply add a UITapGestureRecognizer to the whole (main-)ViewController's view to close the keyboard, the user experience is not the best, since the user needs to tap twice if he didn't intend to close the keyboard but e.g. select a row from a UITableView inside the App's current "main" View.
Since I am quite new to the whole iOS and swift thing and swift code examples are rare, here's my full keyboard control snipped:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
override func viewDidLoad(){
self.initializeCloseKeyboardTap()
}
func initializeCloseKeyboardTap() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "onKeyboardOpen:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "onKeyboardClose:", name: UIKeyboardDidHideNotification, object: nil)
let tapRecognizer = UITapGestureRecognizer(target: self, action: "handleOnTapAnywhereButKeyboard:")
tapRecognizer.delegate = self //delegate event notifications to this class
self.view.addGestureRecognizer(tapRecognizer)
}
func onKeyboardClose(notification: NSNotification) {
println("keyboardClosed")
}
func onKeyboardOpen(notification: NSNotification) {
println("keyboardOpen")
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
self.view.endEditing(true)
return false
}
you can nest it inside the ViewController and call initiallizeCloseKeyboardTa() in viewDidLoad implementation... It also implements a norfication observer to receive events as soon as the keyboard (finished to be) opened or closed via NSNotificationCenter UIKeyboardDidShowNotification and UIKeyboardDidHideNotification
Hope that save's other people some time :-)
|*| Dismissing the UITextField’s Keyboard : override the method touchesBegan:withEvent: for the ViewController
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
view.endEditing(true)
super.touchesBegan(touches, withEvent: event)
}
|OR|
override func viewDidLoad()
{
super.viewDidLoad()
// For tapping outside text box
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(DismisKeyPadFnc)))
}
// For tapping outside text box
func DismisKeyPadFnc()
{
view.endEditing(true)
}
|*| Dismissing the UITextField’s Keyboard on induvidual textbox :
class NamVcc: UIViewController, UITextFieldDelegate
{
#IBOutlet var NamTxtBoxVid: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
// For hitting return key
NamTxtBoxVid.delegate = self
// For tapping outside text box
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(DismisKeyPadFnc)))
}
/* For hitting return key */
func textFieldShouldReturn(NamTxtBoxPsgVid: UITextField) -> Bool
{
NamTxtBoxPsgVid.resignFirstResponder()
return true
}
/* For tapping outside text box */
func DismisKeyPadFnc()
{
view.endEditing(true)
}
}
|*| Implementing Next button Click :
1) You will be needing to set the tags in the incremental sequence of the textfields in xib/Storyboard or in code.
2) Set the delegate for each of the textfields.
3) Then paste the following code in your view controller to have the desired effect:
func textFieldShouldReturn(TxtBoxPsgVar: UITextField) -> Bool
{
let NxtTxtBoxTagVal : Int = TxtBoxPsgVar.tag + 1
let NxtTxtBoxVal: UIResponder? = TxtBoxPsgVar.superview?.superview?.viewWithTag(NxtTxtBoxTagVal)
if let TxtBoxVal = NxtTxtBoxVal
{
// Found next responder, so set it.
TxtBoxVal.becomeFirstResponder()
}
else
{
// Not found, so remove keyboard.
TxtBoxPsgVar.resignFirstResponder()
}
return true
}
I prefer IQKeyboardManager. You can simply handle keyboard in any state. To active IQKeyboard just add one line in AppDelegate and to dismiss keyboard on outside touch add code as below,
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
IQKeyboardManager.shared.enable = true
IQKeyboardManager.shared.shouldResignOnTouchOutside = true
return true
}
I think
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
is convenient but not the best solution.
and the TapGestureRecongnizer is better but hard to use,you have to set delegates and add and remove the Recongnizer.
So I wrote a simple subclass which can be easily used:
class TapHideTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
tapGr = UITapGestureRecognizer(target: self, action:"tap")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didBeginEditing", name:UITextFieldTextDidBeginEditingNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didEndEditing", name:UITextFieldTextDidEndEditingNotification, object: nil)
}
deinit{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
var tapGr:UITapGestureRecognizer!
func tap(){
self.endEditing(true)
}
func didBeginEditing(){
self.superview!.addGestureRecognizer(tapGr)
}
func didEndEditing(){
self.superview!.removeGestureRecognizer(tapGr)
}
}
you should concern about the superview and the cancelTouchesInView property.
or in GitHub:https://github.com/lilidan/TapHideText/blob/master/TapHideTextField.swift
If you're using scroll view (or table view) controller, add these lines there:
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
view.endEditing(true)
}
-(void) textFieldDidBeginEditing: (UITextField *) textField{
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(DismissKeyboard:)]];
}
-(void) DismissKeyboard:(UITapGestureRecognizer *) sender{
[self.view endEditing:YES];
[self.view removeGestureRecognizer:sender];
}

Resources