UITapGestureRecognizer breaks UITableView didSelec

2019-01-02 16:59发布

I have written my own function to scroll text fields up when the keyboard shows up. In order to dismiss the keyboard by tapping away from the text field, I've created a UITapGestureRecognizer that takes care of resigning first responder on the text field when tapping away.

Now I've also created an autocomplete for the textfield that creates a UITableView just below the text field and populates it with items as the user enters text.

However, when selecting one of the entries in the auto completed table, didSelectRowAtIndexPath does not get called. Instead, it seems that the tap gesture recognizer is getting called and just resigns first responder.

I'm guessing there's some way to tell the tap gesture recognizer to keep passing the tap message on down to the UITableView, but I can't figure out what it is. Any help would be very appreciated.

16条回答
浮光初槿花落
2楼-- · 2019-01-02 17:36

I may have a better solution to add a tap gesture over a table view but allowing cell selection at the same time:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if gestureRecognizer is UITapGestureRecognizer {
        let location = touch.locationInView(tableView)
        return (tableView.indexPathForRowAtPoint(location) == nil)
    }
    return true
}

I just look for a cell at the point of the screen where the user is tapping. If no index path is found then I let the gesture receive the touch otherwise I cancel it. For me it works great.

查看更多
牵手、夕阳
3楼-- · 2019-01-02 17:37

While it's late and many people find that the above suggestions work fine, I could not get Jason's or TMilligan's methods to work.

I have a Static tableView with multiple cells containing textFields that receive Number inputs using only the Number Keyboard. This was ideal for me:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if(![touch.view isKindOfClass:[UITableViewCell class]]){

        [self.firstTF resignFirstResponder];
        [self.secondTF resignFirstResponder];
        [self.thirdTF resignFirstResponder];
        [self.fourthTF resignFirstResponder];

        NSLog(@"Touches Work ");

        return NO;
    }
    return YES;
}

Ensure that you have implemented this <UIGestureRecognizerDelegate> in your .h file.

This line ![touch.view isKindOfClass:[UITableViewCell class]] checks whether a tableViewCell was tapped and dismisses any active keyboard.

查看更多
公子世无双
4楼-- · 2019-01-02 17:38

I had a different situation where I wanted the touch gesture function to be called only when the user tapped outside of the table view. If the user tapped inside the table view, then the touch gesture function shouldn't be called. Additionally, If the touch gesture function is called, it should still pass the touch event to the view that was tapped on rather than consuming it.

The resulting code is a combination of Abdulrahman Masoud's answer, and Nikolaj Nielsen's answer.

extension MyViewController: UIGestureRecognizerDelegate {
    func addGestureRecognizer() {
        let tapOnScreen = UITapGestureRecognizer(target: self,
                                                action: #selector(functionToCallWhenUserTapsOutsideOfTableView))

        // stop the gesture recognizer from "consuming" the touch event, 
        // so that the touch event can reach other buttons on view.
        tapOnScreen.cancelsTouchesInView = false

        tapOnScreen.delegate = self

        self.view.addGestureRecognizer(tapOnScreen)
    }

    // if your tap event is on the menu, don't run the touch event.
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if touch.view?.isDescendant(of: self.tableView) == true {
            return false
        }
        return true
    }

    @objc func functionToCallWhenUserTapsOutsideOfTableView() {

        print("user tapped outside table view")
    }
}

And in the MyViewController class, the class which has the UITableView, in the onViewDidLoad(), I made sure to call addGestureRecognizer():

class MyViewController: UIViewController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        self.addGestureRecognizer()
        ...
    }
    ...
}
查看更多
公子世无双
5楼-- · 2019-01-02 17:39

A similar solution is to implement gestureRecognizer:shouldReceiveTouch: using the view's class to determine what action to take. This approach has the advantage of not masking taps in the region directly surrounding the table (these area's views still descend from the UITableView instances, but they do not represent cells).

This also has a bonus that it works with multiple tables on a single view (without adding extra code).

Caveat: there is an assumption that Apple won't change the classname.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    return ![NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"];
}
查看更多
梦寄多情
6楼-- · 2019-01-02 17:40

The easiest way to solve this problem is to:

UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc] 
    initWithTarget:self action:@selector(tap:)];
[tapRec setCancelsTouchesInView:NO];

This lets the UIGestureRecognizer recognize the tap and also pass the touch to the next responder. An unintended consequence of this method is if you have a UITableViewCell on-screen that pushes another view controller. If the user taps the row to dismiss the keyboard, both the keyboard and the push will be recognized. I doubt this is what you intend, but this method is adequate for many situations.

Also, expanding on Robert's answer, if you have a pointer to the tableview in question, then you can directly compare its class instead of having to convert to a string and hope Apple doesn't change the nomenclature:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
     shouldReceiveTouch:(UITouch *)touch
{
    if([touch.view class] == tableview.class){
        return //YES/NO
    }

    return //YES/NO

}

Remember, you must also declare the UIGestureRecognizer to have a delegate with this code in it.

查看更多
萌妹纸的霸气范
7楼-- · 2019-01-02 17:41

I think there is no need to write blocks of codes just simply set cancelsTouchesInView to false for your gesture object , by default it's true and you just have to set it false . If you are using UITapGesture object in your code and also using UIScrollView(tableview , collectionview)then set this property false

let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
查看更多
登录 后发表回答