can we get the touched subview properties from scroll view in ios - ios

I have added 20 subviews to scrollview line by line as rows
yPos=0;
for (int i=0; i<24; i++) {
UIView *timeView=[[UIView alloc]initWithFrame:CGRectMake(71, yPos, 909, 60)];
timeView.userInteractionEnabled=TRUE;
timeView.exclusiveTouch=YES;
timeView.tag=i;
NSLog(#"sub vieww tag=:%d",timeView.tag);
timeView.backgroundColor=[UIColor whiteColor];
UILabel *lbltime=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 70, 60)];
lbltime.text=#"VIEW HERE";
lbltime.textColor=[UIColor grayColor];
// [timeView addSubview:lbltime];
[scrlView addSubview:timeView];
yPos=yPos+61;
}
Now when ever I tap on a subview I am not getting the tapped subview properties.
like coordinates. It is giving parent view coordinates
I enabled subview UserInteractionEnabled to Yes.
Can any one tell me how to get tapped subview coordinate and tag value?

DO NOT subclass from UIScrollView, that's exactly why there are gesture recognizers. Also, DO NOT add a separate gesture recognizer to each view.
Add one gesture recognizer to your scroll view, and when it's clicked use the x,y values of the touch to calculate which view was clicked.
You'll need to do a small calculation: (y value of the click + UIScrollView y offset) / 60.
60 is the height of each view. This should return the index of the clicked view.
EDIT:
Code example:
- (void)viewTapped:(UIGestureRecognizer*)recognizer
{
CGPoint coords = [recognizer locationInView:recognizer.view];
int clickedViewIndex = (self.offset.y + coords.y) / 60;
// now clickedViewIndex contains the index of the clicked view
}

UIView *v = recognizer.view;
int tagNum = [v tag];
Using the tagNum you can do your further operatins.
Or v is your object of tapped view.
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)];
tap.numberOfTapsRequired = 1;
[timeview addGestureRecognizer:tap];
Add this in for loop only.

Make a class extending UIScrollView :
For example :
.h file :
#protocol CustomScrollViewDelegate <NSObject>
#optional
// optional becuase we always don't want to interact with ScrollView
- (void) customScrollViewTouchesEnded :(NSSet *)touches withEvent:(UIEvent *)event;
- (void) customScrollViewDidScroll;
#end
#interface CustomScrollView : UIScrollView
#property (weak, nonatomic) id<CustomScrollViewDelegate> touchDelegate;
// delegate was giving error becuase name is already there in UIScrollView
#end
Code for .m file :
#import "CustomScrollView.h"
#implementation CustomScrollView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesEnded");
if (!self.dragging) {
//NSLog(#"touchesEnded in custom scroll view");
if (_touchDelegate != nil) {
if ([_touchDelegate respondsToSelector:#selector(customScrollViewTouchesEnded:withEvent:)]) {
[_touchDelegate customScrollViewTouchesEnded:touches withEvent:event];
}
}
}
else {
// it wouldn't be called becuase at the time of dragging touches responding stops.
[super touchesEnded:touches withEvent:event];
}
}
#end
Implementing this use subview of scroll view, it will work
for (UILabel *label in [customScrollView subviews]) { // change name of table here
if ([label isKindOfClass:[UILabel class]]) {
if (label.tag == savedtag) { // use your tag
// write the code as desired
}
}
}

Related

UITapGestureRecognizer Is Only Detected Inside Modal UIViewController On iOS 8

I have been trying to implement a UITapGestureRecognizer that will dismiss a modal view controller by detecting a tap outside the modal view controller on iOS 8.
The issue I am seeing is that in landscape mode on iPad that the tap gesture is only recognised inside parts of the view controller. My modal view is 380 width x 550 height. When orientation is in landscape (with the home button at the right) the tap gesture is detected inside the bottom half of the modal view. When orientation is in landscape but with the home button at the left hand side the tap gesture is detected inside the top half of the modal view.
Problem 1: The tap gesture is only detected inside the modal view when it should be detected outside.
Problem 2: No detection in some areas of the view in landscape orientation.
Here is the code I used:
UIViewController+DismissOnTapOutside.h
#import <UIKit/UIKit.h>
#interface UIViewController (DismissOnTapOutside)
- (void)registerForDismissOnTapOutside; // Call in viewDidAppear
- (void)unregisterForDismissOnTapOutside; // Call in viewWillDisappear
#end
UIViewController+DismissOnTapOutside.m
#import "UIViewController+DismissOnTapOutside.h"
#import <objc/runtime.h>
static char gestureRecognizerKey;
static char gestureRecognizerDelegateKey;
#interface SimpleGestureRecognizerDelegate : NSObject <UIGestureRecognizerDelegate>
#end
#implementation SimpleGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return [otherGestureRecognizer isMemberOfClass:[UITapGestureRecognizer class]];
}
#end
#interface UIViewController ()
#property (assign, nonatomic) UIGestureRecognizer *gestureRecognizer;
#property (strong, nonatomic) SimpleGestureRecognizerDelegate *gestureRecognizerDelegate;
#end
#implementation UIViewController (DismissOnTapOutside)
- (void)setGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
objc_setAssociatedObject(self, &gestureRecognizerKey, gestureRecognizer, OBJC_ASSOCIATION_ASSIGN);
}
- (UIGestureRecognizer *)gestureRecognizer
{
return objc_getAssociatedObject(self, &gestureRecognizerKey);
}
- (void)setGestureRecognizerDelegate:(SimpleGestureRecognizerDelegate *)delegate
{
objc_setAssociatedObject(self, &gestureRecognizerDelegateKey, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIGestureRecognizer *)gestureRecognizerDelegate
{
id delegate = objc_getAssociatedObject(self, &gestureRecognizerDelegateKey);
if (!delegate)
{
delegate = [[SimpleGestureRecognizerDelegate alloc] init];
self.gestureRecognizerDelegate = delegate;
}
return delegate;
}
- (void)handleDismissTap:(UIGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateEnded)
{
UIView *view = self.navigationController.view ?: self.view;
// Passing nil gives us coordinates in the window
CGPoint location = [gesture locationInView:nil];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// Then we convert the tap's location into the local view's coordinate system
location = [view convertPoint:location fromView:self.view.window];
if (![view pointInside:location withEvent:nil])
{
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
}
- (void)registerForDismissOnTapOutside
{
// This approach is attributed to Danilo Campos:
// http://stackoverflow.com/a/6180584/456434
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleDismissTap:)];
recognizer.delegate = self.gestureRecognizerDelegate;
recognizer.numberOfTapsRequired = 1;
recognizer.cancelsTouchesInView = NO;
self.gestureRecognizer = recognizer;
[self.view.window addGestureRecognizer:recognizer];
}
- (void)unregisterForDismissOnTapOutside
{
[self.view.window removeGestureRecognizer:self.gestureRecognizer];
self.gestureRecognizer = nil;
}
#end
The key method that should set the correct location for tap detection does not seem to be setting the location correctly in landscape orientation in iOS 8:
- (void)handleDismissTap:(UIGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateEnded)
{
UIView *view = self.navigationController.view ?: self.view;
// Passing nil gives us coordinates in the window
CGPoint location = [gesture locationInView:nil];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// Then we convert the tap's location into the local view's coordinate system
location = [view convertPoint:location fromView:self.view.window];
if (![view pointInside:location withEvent:nil])
{
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
}
All of the examples I have looked at on StackOverflow in relation to this issue are adamant that the above code works. But in my case it simply does not detect taps outside the modal view controller.
How can a tap outside a modal be detected in iOS 8 on iPad in landscape orientation?

How to add drag gesture to uiview in uiscrollview ios

I'm trying to add the move gesture of uiview inside uiscrollview but I can not disable the srcoll event of uiscrollview. I have implemented the UIScrollview with paging enable in Main class. In another class, i added uiview in it, add gesture for it but i dont know how to disable scrolling of uiscrollview.
Please give me some advice. Thanks in advance.
You need to communicate from your UIView class with gesture in it by means of delegate to the main class asking scroll view to stop scrolling and later on enabling it. I have enclosed the code.
Your UIView.h file
#protocol MyUIViewProtocol <NSObject>
- (void)setScrollViewScrollEnabled:(BOOL)enabled;
#end
#interface MyUIView : UIView
#property (weak, nonatomic) id<MyUIViewProtocol> delegate;
#end
Your UIView.m file
#implementation MyUIView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self setBackgroundColor:[UIColor redColor]];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureMade:)];
[self addGestureRecognizer:panGesture];
}
return self;
}
- (void)panGestureMade:(UIPanGestureRecognizer *)recognizer
{
CGPoint pointsToMove = [recognizer translationInView:self];
[self setCenter:CGPointMake(self.center.x + pointsToMove.x, self.center.y + pointsToMove.y)];
[recognizer setTranslation:CGPointZero inView:self];
//Disable the scroll when gesture begins and enable the scroll when gesture ends.
if (self.delegate && [self.delegate respondsToSelector:#selector(setScrollViewScrollEnabled:)]) {
if (recognizer.state == UIGestureRecognizerStateBegan) {
[self.delegate setScrollViewScrollEnabled:NO];
}
else if (recognizer.state == UIGestureRecognizerStateCancelled || recognizer.state == UIGestureRecognizerStateEnded) {
[self.delegate setScrollViewScrollEnabled:YES];
}
}
}
Your main class file with scroll view in it.
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 568)];
[self.scrollView setBackgroundColor:[UIColor yellowColor]];
[self.scrollView setPagingEnabled:YES];
[self.scrollView setContentSize:CGSizeMake(320 * 3, 568)];
[self.view addSubview:self.scrollView];
MyUIView *view = [[MyUIView alloc] initWithFrame:CGRectMake(40, 100, 100, 100)];
view.delegate = self;
[self.scrollView addSubview:view];
}
- (void)setScrollViewScrollEnabled:(BOOL)enabled
{
[self.scrollView setScrollEnabled:enabled];
}
Hope this answer helps you.

identify touches on subclass of UIViews created programmatically in iOS

I am creating an app with cards. In my mainViewController, I have this code:
CardView *cardView = [[CardView alloc] initWithFrame:CGRectMake(0, 0, CardWidth, CardHeight)];
cardView.card = [player.closedCards cardAtIndex:t];
[self.cardContainerView addSubview:cardView];
[cardView animateDealingToBottomPlayer:player withIndex:t withDelay:delay];
delay += 0.1f;
where CardView is a subclass of UIView. Each Card is a unique cardView and in CardView.m I do have:
#implementation CardView
{
UIImageView *_backImageView;
UIImageView *_frontImageView;
CGFloat _angle;
}
#synthesize card = _card;
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
self.backgroundColor = [UIColor clearColor];
[self loadBack];
self.userInteractionEnabled=YES;
}
return self;
}
- (void)loadBack
{
if (_backImageView == nil)
{
_backImageView = [[UIImageView alloc] initWithFrame:self.bounds];
_backImageView.image = [UIImage imageNamed:#"Back"];
_backImageView.contentMode = UIViewContentModeScaleToFill;
[self addSubview:_backImageView];
}
}
and the implementations for other functions .
Since in order to win space, one card is placed on top of the others (half of teh card is visible and the rest is covered by the next card and so on), I want to identify touches on each card.
If I place that code in CardView:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Card is touched");
}
it is never called. If I place it in the GameView Controller it is called anywhere that I will touch, but I do not know how to identify which cardView is called. Can you give me a hint?
EDIT:
I decided to use gestures. Therefore in my mainViewController changed the code to this:
for (PlayerPosition p = startingPlayer.position; p < startingPlayer.position + 4; ++p)
{
CardView *cardView = [[CardView alloc] initWithFrame:CGRectMake(0, 0, CardWidth, CardHeight)];
cardView.card = [player.closedCards cardAtIndex:t];
cardView.userInteractionEnabled=YES;
[self.cardContainerView addSubview:cardView];
[cardView animateDealingToBottomPlayer:player withIndex:t withDelay:delay];
delay += 0.1f;
UITapGestureRecognizer *recognizer=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(cardSelected:)];
[cardView addGestureRecognizer:recognizer];
}
but this is never called.
-(void)cardSelected:(UITapGestureRecognizer *)recognizer
{
NSLog(#"Card Selected with gestures");
}
Why is that?
EDIT 2:
I have also tried adding this:
self.cardContainerView.userInteractionEnabled=YES;
or changing this:
UITapGestureRecognizer *recognizer=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(cardSelected:)];
to this:
UITapGestureRecognizer *recognizer=[[UITapGestureRecognizer alloc]initWithTarget:self .cardContainerView action:#selector(cardSelected:)];
but none of them worked.
If you instead add a tap gesture recogniser to each card view you can get a callback to a method you specify and the gesture is connected to the view so you can directly get a reference to it.
Your CardView (which I guess is a subclass of UIView?) has 2 subviews, which are image views:
UIImageView *_backImageView;
UIImageView *_frontImageView;
You may want to set userInteractionEnabled to YES on one or both of them (depending on when the card should be tappable and when the subviews are shown and hidden).
You can also act as the delegate of the gesture (if you need to, or just temporarily to debug that the gesture is getting triggered but blocked by something other gesture).

Custom Container using UIScrollView with two fingers

I'm creating a container view controller where the child view controllers are shown like a paged scrollView. In this container controller I want to change between pages scrolling horizontally using two fingers. So I used a UIScrollView and I have set it like that:
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.delegate = self;
[self.scrollView setContentOffset:[self rectForPage:1].origin];
for (UIGestureRecognizer *gestureRecognizer in self.scrollView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
}
}
I also have implemented the - (void)scrollViewDidScroll:(UIScrollView *)sender method to avoid the vertical scrolling
- (void)scrollViewDidScroll:(UIScrollView *)sender {
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x +pageWidth/2) / pageWidth);
self.pageControl.currentPage = page;
[self.scrollView setContentOffset: CGPointMake(self.scrollView.contentOffset.x, 0)];
}
Now the problem is that the childViewControllers subviews can't receive the touches from the user. I want the single touch to be passed throw the view hierarchy. I have read about subclassing UIScrollView to overwrite the method - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view but it's only called if the subviews are UIControl. How I can do it?
The purpose is that this ChildViewControllers have UIButtons inside and ScrollViews that should be used with singleTouches
maybe not exactly, but this might help you. Create a scroll view subclass and implement this;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// UIView will be "transparent" for touch events if we return NO
for (id subview in self.subviews) {
if ([subview isKindOfClass:[YourViewController class]]) {
YourViewController *VC = (YourViewController*) subview;
if (CGRectContainsPoint(YourViewController.frame, point)) {
return YES;
}
}
}
return NO;
}

Dismiss keyboard by touching background of UITableView

I have a UITableView with UITextFields as cells. I would like to dismiss the keyboard when the background of the UITableView is touched. I'm trying to do this by creating a UIButton the size of the UITableView and placing it behind the UITableView. The only problem is the UIButton is catching all the touches even when the touch is on the UITableView. What am I doing wrong?
Thanks!
This is easily done by creating a UITapGestureRecognizer object (by default this will detect a "gesture" on a single tap so no further customization is required), specifying a target/action for when the gesture is fired, and then attaching the gesture recognizer object to your table view.
E.g. Perhaps in your viewDidLoad method:
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(hideKeyboard)];
[self.tableView addGestureRecognizer:gestureRecognizer];
And the hideKeyboard method might look like this:
- (void) hideKeyboard {
[textField1 resignFirstResponder];
[textField2 resignFirstResponder];
...
...
}
Note that the gesture is not fired when touching inside a UITextField object. It is fired though on the UITableView background, footer view, header view and on UILabels inside cells etc.
The UITapGestureRecognizer solution works with table cell selection if you set:
gestureRecognizer.cancelsTouchesInView = NO;
Here is a best way to do this.
Just do this
[self.view endEditing:YES];
or
[[self.tableView superView] endEditing:YES];
You can also do it from Storyboard:
As UITableView is a subclass of UIScrollView, implementing one delegate method below provides an extremely easy, quick solution. No need to even involve resignFirstResponder since view hierarchy introspects and finds the current responder and asks it to resign it's responder status.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self.view endEditing:YES];
}
And remember to add UIScrollViewDelegate to header file.
tableView.keyboardDismissMode = .onDrag
Firstly, listen for scrollViewWillBeginDragging in your UIViewController by adding the UIScrollViewDelegate:
In .h file:
#interface MyViewController : UIViewController <UIScrollViewDelegate>
In .m file:
- (void)scrollViewWillBeginDragging:(UIScrollView *)activeScrollView {
[self dismissKeyboard];
}
Then listen for other interactions:
- (void)setupKeyboardDismissTaps {
UISwipeGestureRecognizer *swipeUpGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard)];
swipeUpGestureRecognizer.cancelsTouchesInView = NO;
swipeUpGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
[self.tableView addGestureRecognizer:swipeUpGestureRecognizer];
UISwipeGestureRecognizer *swipeDownGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard)];
swipeDownGestureRecognizer.cancelsTouchesInView = NO;
swipeDownGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
[self.tableView addGestureRecognizer:swipeDownGestureRecognizer];
UISwipeGestureRecognizer *swipeLeftGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard)];
swipeLeftGestureRecognizer.cancelsTouchesInView = NO;
swipeLeftGestureRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[self.tableView addGestureRecognizer:swipeLeftGestureRecognizer];
UISwipeGestureRecognizer *swipeRightGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard)];
swipeRightGestureRecognizer.cancelsTouchesInView = NO;
swipeRightGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[self.tableView addGestureRecognizer:swipeRightGestureRecognizer];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard)];
tapGestureRecognizer.cancelsTouchesInView = NO;
[self.tableView addGestureRecognizer:tapGestureRecognizer];
}
Then implement dismissKeyboard:
- (void)dismissKeyboard {
NSLog(#"dismissKeyboard");
[yourTextFieldPointer resignFirstResponder];
}
And if, like me, you wanted to dismiss the keyboard for a UITextField inside a custom table cell:
- (void)dismissKeyboard {
NSLog(#"dismissKeyboard");
CustomCellClass *customCell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
[customCell.textFieldInCell resignFirstResponder];
}
Hope that helps anyone searching!!
Here's the swift version for your coding pleasure:
It adds a tap gesture recognizer then dismisses the keyboard. No outlet for the TextField is required!
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap:"))
}
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .Ended {
view.endEditing(true)
}
sender.cancelsTouchesInView = false
}
There is Swift 3 version without blocking taps on cells.
In viewDidLoad() method:
let dismissKeyboardGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
dismissKeyboardGesture.cancelsTouchesInView = false
tableView.addGestureRecognizer(dismissKeyboardGesture)
And hideKeyboard looks like this:
func hideKeyboard() {
view.endEditing(true)
}
I did it like this:
Create a method in your TableViewController to deactivate first responder (which would be your TextBox at that point)
- (BOOL)findAndResignFirstResonder:(UIView *)stView {
if (stView.isFirstResponder) {
[stView resignFirstResponder];
return YES;
}
for (UIView *subView in stView.subviews) {
if ([self findAndResignFirstResonder:subView]) {
return YES;
}
}
return NO;
}
In tableView:didSelectRowAtIndexPath: call the previous method:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
...
[self findAndResignFirstResonder: self.view];
...
}
I had a UITableViewController and implementing touchesBegan:withEvent: didn't work for me.
Here's what worked:
Swift:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
view.endEditing(true)
}
Objective-C:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.view endEditing:YES];
}
#interface DismissableUITableView : UITableView {
}
#end
#implementation DismissableUITableView
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.superview endEditing:YES];
[super touchesBegan:touches withEvent:event];
}
#end
Then make sure that in your Nib file you set the type of your UITableView to DismissableUITableView .....maybe i could have thought of a better name for this class, but you get the point.
If you are targeting iOS7 you can use one of the following:
tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
The former will animate the keyboard off screen when the table view is scrolled and the later will hide the keyboard like the stock Messages app.
Note that these are from UIScrollView, which UITableView inherits from.
Try this:
viewDidLoad(){
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tableView.addGestureRecognizer(tap)
}
//Calls this function when the tap is recognized.
#objc func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
UITableView is a subclass of UIScrollView.
The way I did it was to listen for a scroll event by the user and then resignFirstResponder. Here's the UIScrollViewDelegate method to implement in your code;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
When approaching these sorts of problems I've found the best way is to research the delegate protocols for each object and those of the parent classes (in this case UITableViewDelegate, UIScrollViewDelegate. The number of events NS objects fires is quite large and comprehensive. It's also easier implementing a protocol then subclassing anything.
I had the same problem and here's my solution, it works perfectly for me:
In the view or view controller that you implemented <UITextFieldDelegate>
(In my case I have a custom UITableViewCell called TextFieldCell),
Declare the UITapGestureRecognizer as a property:
#interface TextFieldCell : UITableViewCell <UITextFieldDelegate>
{
UITextField *theTextField;
UITapGestureRecognizer *gestureRecognizer;
}
#property (nonatomic,retain) UITextField *theTextField;
#property (nonatomic,retain) UITapGestureRecognizer *gestureRecognizer;
And initialize it in your view/controller:
self.gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeKeyboard:)];
In the - (void)textFieldDidBeginEditing:(UITextField *)textField method, use superView to move up to your tableView and call addGestureRecognizer:
[self.superview.superview addGestureRecognizer:gestureRecognizer];
And in the - (void)textFieldDidEndEditing:(UITextField *)textField, just remove the gesture recognizer:
[self.superview.superview removeGestureRecognizer:gestureRecognizer];
Hope it helps.
I wanted my cell to open the keyboard when any part of the cell was selected and close it if you clicked anywhere off the cell. To open the keyboard:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
if (selected)
{
[self.textField becomeFirstResponder];
}
}
(NOTE: I've subclassed the cell but you can easily achieve this in the tableView:didSelectRowAtIndexPath: delegate method of UITableView)
Doing this meant that with the top solutions if you clicking on the cell twice the keyboard would shake as, first the gesture recogniser tried to close the keyboard, and second the cell was reselected and tried open the keyboard.
Solution is to check whether the click occurred inside the currently selected cell:
- (void)viewDidLoad
{
[super viewDidLoad];
//gesture recognizer to close the keyboard when user taps away
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissKeyboard:)];
tap.cancelsTouchesInView = NO;
[self.tableView addGestureRecognizer:tap];
}
-(void)dismissKeyboard:(UIGestureRecognizer*)tapGestureRecognizer
{
if (!CGRectContainsPoint([self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]].frame, [tapGestureRecognizer locationInView:self.tableView]))
{
[self.view endEditing:YES];
}
}
I've found a solution that works great.
Is needed to use the UIGestureRecognizerDelegate and the method – gestureRecognizer:shouldReceiveTouch:.
Add the gesture recognizer to the TableView as follows:
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(hideKeyboard)];
tapGestureRecognizer.cancelsTouchesInView = NO;
tapGestureRecognizer.delegate = self;
[self.suggestedTableView addGestureRecognizer:tapGestureRecognizer];
[tapGestureRecognizer release];
Then, implement the shouldReceiveTouch delegate method to reject touches that are performed in UITableViewCell class. The hideKeyboard method only will be called when the touch has been performed outside UITableViewCell class.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if([touch.view isKindOfClass:[UITableViewCell class]]) {
return NO;
}
// UITableViewCellContentView => UITableViewCell
if([touch.view.superview isKindOfClass:[UITableViewCell class]]) {
return NO;
}
// UITableViewCellContentView => UITableViewCellScrollView => UITableViewCell
if([touch.view.superview.superview isKindOfClass:[UITableViewCell class]]) {
return NO;
}
return YES; // handle the touch
}
- (void) hideKeyboard{
[textField resignFirstResponder];
}
UITableView has a handy backgroundView property, with which I achieved this behavior without messing with cell selection, as shown below in Swift:
let tableBackTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
tableView.backgroundView = UIView()
tableView.backgroundView?.addGestureRecognizer(tableBackTapRecognizer)
I was searching for the solution and did not find anything that would fit my code, so I did it like this:
http://82517.tumblr.com/post/13189719252/dismiss-keyboard-on-uitableview-non-cell-tap
It's basically a combination of before-mentioned approaches but does not require to subclass anything or to create background buttons.
Simply using a UITapGestureRecognizer and cancelsTouchesInView = NO means that taps on cells and UITextViews also trigger the hide. This is bad if you have multiple UITextViews and you tap on the next one. The keyboard will start to hide and then the next textView becomes the firstResponder and the keyboard becomes visible again. To avoid this, check the tap location and only hide the keyboard if the tap isn't on a cell:
// init
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapTableView:)];
tapRecognizer.cancelsTouchesInView = NO;
[self.tableView addGestureRecognizer:tapRecognizer];
// Hide on tap
- (void)didTapTableView:(UITapGestureRecognizer *)tap
{
CGPoint point = [tap locationInView:tap.view];
[self.view endEditing:!CGRectContainsPoint([self.tableView rectForRowAtIndexPath:[self.tableView indexPathForRowAtPoint:point]], point)];
}
In order for scrollViewWillBeginDragging: to be triggered, the tableView's scrollEnabled property must be YES
// Hide on scroll
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self.view endEditing:YES];
}
Swift 4/4.2/5
You can also dismiss the keyboard when a cell is tapped - prior to doing anything else.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
view.endEditing(true)
// Do something here
}
tableView.keyboardDismissMode = .onDrag // .interactive
Why do you want to create a table full of textfields? You should be using a detailed view for each row that contains the text fields.
When you push your detailedview, ensure that you call "[myTextField becomeFirstResponder]" so that the user can start editing with just one click away from the table list.
If you're willing to subclass (ugh!) your table view, something like this might work:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
BOOL backgroundTouched = YES;
for (UITouch *touch in touches) {
CGPoint location = [touch locationInView:self];
for (UITableViewCell *cell in self.visibleCells) {
if (CGRectContainsPoint(cell.frame, location)) {
backgroundTouched = NO;
break;
}
}
}
if (backgroundTouched) {
for (UITableViewCell *cell in self.visibleCells) {
// This presumes the first subview is the text field you want to resign.
[[cell.contentView.subviews objectAtIndex:0] resignFirstResponder];
}
}
[super touchesBegan:touches withEvent:event];
}
If you want to dismiss the keyboard while return key is pressed,you can simply add the following code in textField should return method i.e.:
- (BOOL)textFieldShouldReturn:(UITextField *)atextField
{
[textField resignFirstresponder];
}
Some textfields might have a picker view or some other as a subview,so in that case the above method doesn't work so in that case we need to make use of UITapGestureRecognizer class i.e. add the following code snippet to viewDidLoad method i.e.:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
Now simply add the resign responder to the selector method i.e.:
-(void)dismissKeyboard
{
[textField resignFirstResponder];
}
Hope it helps,thanks :)
Many interesting answers. I would like to compile different approaches into the solution that i thought best fit a UITableView scenario (it's the one I usually use):
What we usually want is basically to hide the keyboard on two scenarios: on tapping outside of the Text UI elements, or on scrolling down/up the UITableView. The first scenario we can easily add via a TapGestureRecognizer, and the second via the UIScrollViewDelegate scrollViewWillBeginDragging: method.
First order of business, the method to hide the keyboard:
/**
* Shortcut for resigning all responders and pull-back the keyboard
*/
-(void)hideKeyboard
{
//this convenience method on UITableView sends a nested message to all subviews, and they resign responders if they have hold of the keyboard
[self.tableView endEditing:YES];
}
This method resigns any textField UI of the subviews within the UITableView view hierarchy, so it's more practical than resigning every single element independently.
Next we take care of dismissing via an outside Tap gesture, with:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupKeyboardDismissGestures];
}
- (void)setupKeyboardDismissGestures
{
// Example for a swipe gesture recognizer. it was not set-up since we use scrollViewDelegate for dissmin-on-swiping, but it could be useful to keep in mind for views that do not inherit from UIScrollView
// UISwipeGestureRecognizer *swipeUpGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(hideKeyboard)];
// swipeUpGestureRecognizer.cancelsTouchesInView = NO;
// swipeUpGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
// [self.tableView addGestureRecognizer:swipeUpGestureRecognizer];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(hideKeyboard)];
//this prevents the gestureRecognizer to override other Taps, such as Cell Selection
tapGestureRecognizer.cancelsTouchesInView = NO;
[self.tableView addGestureRecognizer:tapGestureRecognizer];
}
Setting tapGestureRecognizer.cancelsTouchesInView to NO is to avoid the gestureRecognizer from overriding the normal inner workings of the UITableView (for example, not to interfere with cell Selection).
Finally, to handle hiding the keyboard on Scrolling up/down the UITableView, we must implement the UIScrollViewDelegate protocol scrollViewWillBeginDragging: method, as:
.h file
#interface MyViewController : UIViewController <UIScrollViewDelegate>
.m file
#pragma mark - UIScrollViewDelegate
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self hideKeyboard];
}
I hope it helps! =)
Here's how I finally made works. I combined suggestions and codes from different answers.
Features: dismissing keyboard, moving text fields above keyboard while editing and setting "Next" and "Done" keyboard return type.REPLACE "..." with more fields
static const CGFloat ANIMATION_DURATION = 0.4;
static const CGFloat LITTLE_SPACE = 5;
CGFloat animatedDistance;
CGSize keyboardSize;
#interface ViewController () <UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITextField *firstNameTXT;
.....// some other text fields
#property (weak, nonatomic) IBOutlet UITextField *emailTXT;
#end
#implementation ViewController
- (void)viewDidLoad{
.....
// add tap gesture to help in dismissing keyboard
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(tapScreen:)];// outside textfields
[self.view addGestureRecognizer:tapGesture];
// set text fields return key type to Next, last text field to Done
[self.firstNameTXT setReturnKeyType:UIReturnKeyNext];
.....
[self.emailTXT setReturnKeyType:UIReturnKeyDone];
// set text fields tags
[self.firstNameTXT setTag:0];
....// more text fields
[self.emailTXT setTag:5];
// add keyboard notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
}
// dismiss keyboard when tap outside text fields
- (IBAction)tapScreen:(UITapGestureRecognizer *)sender {
if([self.firstNameTXT isFirstResponder])[self.firstNameTXT resignFirstResponder];
...
if([self.emailTXT isFirstResponder])[self.emailTXT resignFirstResponder];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
if(textField.returnKeyType==UIReturnKeyNext) {
// find the text field with next tag
UIView *next = [[textField superview] viewWithTag:textField.tag+1];
[next becomeFirstResponder];
} else if (textField.returnKeyType==UIReturnKeyDone || textField.returnKeyType==UIReturnKeyDefault) {
[textField resignFirstResponder];
}
return YES;
}
// Moving current text field above keyboard
-(BOOL) textFieldShouldBeginEditing:(UITextField*)textField{
CGRect viewFrame = self.view.frame;
CGRect textFieldRect = [self.view.window convertRect:textField.bounds fromView:textField];
CGRect viewRect = [self.view.window convertRect:self.view.bounds fromView:self.view];
CGFloat textFieldBottomLine = textFieldRect.origin.y + textFieldRect.size.height + LITTLE_SPACE;//
CGFloat keyboardHeight = keyboardSize.height;
BOOL isTextFieldHidden = textFieldBottomLine > (viewRect.size.height - keyboardHeight)? TRUE :FALSE;
if (isTextFieldHidden) {
animatedDistance = textFieldBottomLine - (viewRect.size.height - keyboardHeight) ;
viewFrame.origin.y -= animatedDistance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
}
return YES;
}
-(void) restoreViewFrameOrigionYToZero{
CGRect viewFrame = self.view.frame;
if (viewFrame.origin.y != 0) {
viewFrame.origin.y = 0;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
}
}
-(void)keyboardDidShow:(NSNotification*)aNotification{
NSDictionary* info = [aNotification userInfo];
keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
}
-(void)keyboardDidHide:(NSNotification*)aNotification{
[self restoreViewFrameOrigionYToZero];// keyboard is dismissed, restore frame view to its zero origin
}
#end
#mixca's answer is very useful but what if i've something different from UITextField. I think best way to handle it by searching all subviews of main view with recursive function, check example below
- (BOOL)findAndResignFirstResponder {
if (self.isFirstResponder) {
[self resignFirstResponder];
return YES;
}
for (UIView *subView in self.subviews) {
if ([subView findAndResignFirstResponder]) {
return YES;
}
}
return NO;
}
and also you can put this method to your utility class and can use from tap gesture like #mixca's answer..

Resources