I have a lot of textfields arranged vertically in a scrollview and at the very bottom one textview. All are wired in storyboard to outlets in a .h file and also to action methods that trigger a method to enable a save button. All have their delegates set in viewdid load. However, the bottom textfields--those below the bottom of the screen when it appears onscreen are not responding to touches.
The textfields visible when you open screen respond to touches. The fields below do not. This is baffling. I am using autolayout but have tried all sorts of settings for the sizes of the view, contentview and scrollview without success. Right now I have them all the same size (667 height).
Using the visual debugger I do not see any views blocking the textfields. I do see the scrollview being tapped. Since the scrollview has a gesture recognizer, I have put in code to screen against touches to the textfields with no luck.
Can anyone suggest what might be wrong?
Here is some code that might be relevant:
//in view will appear
//for dismissing keynoard from scroll
UITapGestureRecognizer *tapScroll = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapped)];
tapScroll.cancelsTouchesInView = TRUE;//was no
[self.scrollView addGestureRecognizer:tapScroll];
- (void) tapped
{
NSLog(#"tapped fired");
[self.view endEditing:YES];
}
//setting delegates
self.titleField.delegate=self;
self.firstField.delegate=self;
self.lastField.delegate = self;
self.emailField.delegate=self;
self.email2Field.delegate=self;
self.telField.delegate=self;
self.mobField.delegate=self;
self.telmainField.delegate=self;
self.teltollField.delegate=self;
self.telhField.delegate=self;
self.telfaxField.delegate=self;
self.jobtitleField.delegate = self;
self.coField.delegate = self;
self.addr1Field.delegate = self;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
NSLog(#"gesture recognizer should receive touch");
// Disallow recognition of tap gestures in the segmented control.
if ((touch.view == _titleField)||(touch.view == _firstField)||(touch.view == _lastField)||(touch.view == _emailField)||(touch.view == _email2Field)
||(touch.view == _telField)||(touch.view == _mobField)||(touch.view == _telmainField)||(touch.view == _teltollField)||(touch.view == _telhField)||(touch.view == _telfaxField)||(touch.view == _jobtitleField)||(touch.view == _coField)||(touch.view == _addr1Field)) {//change it to your condition
return NO;
}
else {
return YES;
}
}
- (void)viewDidLayoutSubviews {
self.scrollView.contentSize = CGSizeMake(320, 1200);
}
Related
I added a UIPanGestureRecognizer on my UITableViewCell subclass so that when the user swipes to the left, the buttons underneath are revealed (kind of like in the Mail app). I do this in awakeFromNib:
// Add a pan gesture recognizer to the pannable view.
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panScrollView:)];
panGesture.delegate = self;
[self.pannableView addGestureRecognizer:panGesture];
I want to allow the table view to scroll despite my custom gesture recognizer so I also have the following in the same UITableViewCell subclass:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
My problem now is I don't know how to allow only one gesture recognizer at a time, so that when the user is side-swiping, the table view doesn't scroll, and vice versa. Help?
What I've tried that doesn't work
Method 1
Implement UIGestureRecognizerDelegate in the table view's view controller and require the failure of any other gesture recognizer besides the vertical one which is for scrolling the table view.
Set the table view's panGestureRecognizer's delegate to the view controller that contains it (say it's ContainingViewController).
self.tableView.panGestureRecognizer.delegate = self;
Make ContainingViewController implement UIGestureRecognizerDelegate.
Implement shouldRequireFailureOfGestureRecognizer: and return YES (I'm assuming that the otherGestureRecognizer will be the horizontal panning gesture).
Error: 'UIScrollView's built-in pan gesture recognizer must have its scroll view as its delegate.'
Create a bool that allows your panGesture to action inside panScrollView
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.blockPanGesture = YES;
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
self.blockPanGesture = NO;
}
Then
-(void)panScrollView:(UIPanGestureRecognizer *)panGestureRecognizer
{
if(self.blockPanGesture == NO)
{
// do the stuff
}
}
If you are panning the entire tableview personally I'd put the gesture recogniser on the tableView itself... Otherwise there's also
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return YES if you want the specified item to be editable.
return YES;
}
which handles all this for you... but I'm assuming there's a reason you don't want to use this. You can also build on this logic, for example you might want to tweak when the scrollView can drag as well, by putting similar checks in -(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView etc.
Using Magoo solution, I did something simillar which works for me. Hope it helps someone.
In Controller class
#pragma mark - UIScrollViewDelegate methods
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
ApplicationDelegate.blockPanGesture = YES;
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
ApplicationDelegate.blockPanGesture = NO;
}
Inside Cell:
- (IBAction)panGestureRecognizer:(UIPanGestureRecognizer *)panGesture
{
if(ApplicationDelegate.blockPanGesture)
return;
// do your stuff
}
Just FYI: have dragged UIPanGestureRecognizer in Cell's xib file and created above IBOutletAction |panGestureRecognizer:|
Have you tried setting the bounces property to NO?
I have UIView and I attached to it a UIPanGestureRecognizer.
inside the UIView I have UIScrollView with paging enabled and I set the content size so scrollView could be scrolled just to the left.
the problem:
I want when user try to drag scrollView To the right, to send the event up to UIView so 'UIPanGestureRecognizer' can handle the touch event
at last, after 6 hours I figure it out
I subclassed UIScrollView and implemented gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer delegate method
when user drag over the scrollView this method get called (BOOL)gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer and by default it returns NO
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (self.contentOffset.x > 0 && self.contentOffset.x <= self.contentSize.width - self.frame.size.width) {
//user is in middle of dragging the scrollView don't allow any other gestureRecognizer to respond
return NO;
}else{
//scrollView contentOffset is 0 and user is not dragging the scrollView so let other gestureRecognizer to respond
return YES;
}
}
In trying to use homam's answer, I realized that the scrollView.panGestureRecognizer was being cut off after a return NO. What I wanted was for my custom pan recognizer to be cut off and the scrollView to still scroll. I think that might've been the result of the gestures being added at different levels, I'm not sure.
I found a solution, however, by changing my custom gesture recognizer to check for the scrollView.contentOffset and not activate unless it was greater than zero. I also turned off the scrollView.bounce property once the scrollView.contentOffset got to zero. When scrolling down on my scrollView it would hit the end and immediately my gesture recognizer would take over and move the superview out of the way like I wanted.
Here's a skeleton of my custom pan gesture
- (void)customPan:(UIPanGestureRecognizer*)panRecognizer
{
if(panRecognizer.state == UIGestureRecognizerStateBegan)
{
//do begin stuff
}
if(panRecognizer.state == UIGestureRecognizerStateChanged)
{
if(self.scrollView.contentOffset.y > 0)
{
//do begin stuff to make sure it’s initialized properly when it does start to change
self.scrollView.bounces = YES;
return;
}
else
{
self.scrollView.bounces = NO;
}
//do change stuff
}
if(panRecognizer.state == UIGestureRecognizerStateEnded)
{
if(self.scrollView.contentOffset.y > 0)
{
//do begin stuff to make sure it’s initialized properly when it does start to change
self.scrollView.bounces = YES;
return;
}
else
{
self.scrollView.bounces = NO;
}
//do end stuff
}
}
[myScrollView setContentSize:CGSizeZero];
I have a view with a UIToolbar with a few UIBarButtonItems and a UITableView containing some UITextFields.
I would like to dismiss the keyboard for a textfield with a tap anywhere. Therefore I added a TapGestureRecognizer to the view. To avoid that the TapgestureRecognizer handles taps on the UIBarButtonItems I added the following method (delegate is set).
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
UIView *view = touch.view;
while (view) {
NSLog(#"Class of view: %#", NSStringFromClass([view class]));
view = view.superview;
}
// Disallow recognition of tap gestures in the toolbar
if ([touch.view isKindOfClass:[UIToolbar class]]) {
return NO;
}
if ([touch.view.superview isMemberOfClass:[UIToolbar class]]) {
return NO;
}
return YES;
}
A UIBarButtonItem is not a view itself, but it has UIToolbar as its superview. When I use the above method, the check for isKindOfClass:[UIToolbar class] does not seem to work for all taps on the toolbar. However the check for the superview with isMemberOfClass:[UIToolbar class] works.
I don't understand this. Maybe someone can explain this behavior?
You shouldn't rely on the view hierarchy around private view classes. It could change at any time.
A better approach is to add the gesture to the table view (or other appropriate view which represents the area you're interested in). Just be sure to enable and disable the gesture at appropriate times so as not to block the usual table operation.
I have a simple MapKit app working fine in iOS. It has annotation and when the user clicks on them, the little gray default popup is displayed with the title / subtitle. I even added a UIButton view into it.
So the problem is, I have a search bar above my map. I wanted to resignFirstResponder from the search box whenever the user clicks on the MapView, so I added a simple tap gesture responder. Worked great except now the little gray detail popups no longer show up (only the annotation pins)! I can still tap, zoom, move around etc. Just no popups.
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
tap.cancelsTouchesInView = NO;
tap.delaysTouchesBegan = NO;
tap.delaysTouchesEnded = NO;
[mapView addGestureRecognizer:tap];
-(IBAction)tapped:(UITapGestureRecognizer *)geture {
[searchBar resignFirstResponder];
}
Is it possible to have the best of both worlds?
I used a delegate method similar to the following to arbitrate between touches that should go to my custom view's pan gesture recognizer and touches that should go to the scroll view that contained my custom view. Something like it might work for you.
// the following UIGestureRecognizerDelegate method returns YES by default.
// we modify it so that the tap gesture recognizer only returns YES if
// the search bar is first responder; otherwise it returns NO.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ((gestureRecognizer == self.tapGestureRecognizer) &&
(gestureRecognizer.view == self.mapView) &&
[searchBar isFirstResponder])
{
return YES; // return YES so that the tapGestureRecognizer can deal with the tap and resign first responder
}
else
{
return NO; // return NO so that the touch is sent up the responder chain for the map view to deal with it
}
}
I have a textfield inside a UIScrollView and i want to show a clear button when user starts editing. Also i need to hide keyboard when user taps the background of UIScrollview (but not the textfield). Displaying that clear button isn't a problem, the problem is that when clear button is tapped keyboard gets hidden and the text field doesn't get cleared. Obviously the problem is with the gesture recognizer, because method dealing with this gets fired when the clear button is clicked (but it's not fired when the text field is tapped). Here's my code :
//adding gesture recognizer so i can hide keyboard when user taps scrollview
- (void) textFieldDidBeginEditing:(UITextField *)textField
{
if (self.tapOutside == nil) self.tapOutside = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(textFieldTouchOutSide:)];
[self.scrollView addGestureRecognizer:self.tapOutside];
}
//This hides keyboard BUT IS ALSO CALLED WHEN CLEAR BUTTON IS TAPPED
- (void)textFieldTouchOutSide:(id)sender
{
[self.textfield resignFirstResponder];
}
//NEVER GETS CALLED
- (BOOL) textFieldShouldClear:(UITextField *)textField {
return YES;
}
Any ideas how to solve this? Maybe better way to add gesture recognizer? I can't think of no elegant solution ... Thanks a lot in advance...
I had the same problem and solved it implementing the following method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Disallow recognition of gestures in unwanted elements
if ([touch.view isMemberOfClass:[UIButton class]]) { // The "clear text" icon is a UIButton
return NO;
}
return YES;
}
Don't forget to conform to the "UIGestureRecognizerDelegate" protocol and set the delegate (using your vars):
self.tapOutside.delegate = self;
Cheers
I was just having this issue and this solution worked, however if you do have other buttons on the view that you allow the user to tap while filling out the form you can do the following:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Disallow recognition of gestures in unwanted elements
if ([touch.view isMemberOfClass:[UIButton class]] && [touch.view.superview isMemberOfClass:[UITextField class]]) {
// The "clear text" icon is a UIButton
return NO;
}
return YES;
}
This will narrow down the case to only return No if the button is a subview of a UITextField, as is the case with the clear button, but still hide the keyboard if they touch a normal button that would normally execute your gesture code.