I have a ViewController which has textSearchTableView: UITableView and searchBar: UISearchBar
I added UITapGestureRecognizer to dissmis the keyboard
override func viewDidLoad() {
// ...
self.tap = UITapGestureRecognizer(target: self, action: "DissmissKeyboard")
self.tap.delegate = self
self.view.addGestureRecognizer(self.tap)
// ...
}
func DissmissKeyboard()
{
view.endEditing(true)
}
I have added this function to prevent breaking (didSelectRowAtIndexPath) function after selecting the cell
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.view.isDescendantOfView(self.textSearchTableView) {
return false
}
return true
}
But the problem is: when the keyboard is enabled and i want to dissmiss it,
if i click on the textSearchTableView, (didSelectRowAtIndexPath) will run
How can i dissmiss the keyboard if i click on the tableView without calling (didSelectRowAtIndexPath) ? and I don't want to break this function as well
I hope that I describe the problem well
Thanks a lot
Re-write your didSelectRowAtIndexPath like this:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (self.searchBar.isFirstResponder())
{
self.searchBar.resignFirstResponder()
}
else
{
//Do something here
}
}
Alternatively: We usually use this approach in tableviews.
self.tableView.keyboardDismissMode = .OnDrag
This will dismiss keyboard when a drag begins in the tableview.
How about you have your tableview's delegate return nil for
- (NSIndexPath *)tableView:(UITableView *)tableView
willSelectRowAtIndexPath:(NSIndexPath *)indexPath
As willSelectRowAtIndexPath is probably what you want but not what you asked for: dismissing the keyboard is done by sending the first responder resignFirstResponder, for sake of answering the actual question.
Related
I've set up TapGuestureRecognizer within my ViewDidLoad() to dismiss keyboard. My implementation as follows
class AddRegistrationTableViewController: UITableViewController, UITextFieldDelegate, SelectRoomTableViewControllerDelegate {
...
override func viewDidLoad() {
super.viewDidLoad()
emailAddressTextField.delegate = self
...
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tableView.addGestureRecognizer(tapGestureRecognizer)
}
...
#objc func dismissKeyboard() {
emailAddressTextField.endEditing(true)
}
So far so good, this works in dismissing the text field.
But within the Table View Controller, I also have a segue linked up to a cell - which isn't registering the tap (because of the gesture recognizer) to follow through with the segue. I'll need to use 2 fingers to tap on the cell for the segue to be performed. And I've tried removing my above implementation of addGestureRecognizer in the code, and the segue performed as per expectation.
So this led me to believe that the gesture recogniser is inhibiting touch events from registering. Any workarounds or solution that I can implement?
Instead of tap to dismiss, implement scroll to dismiss by setting the table view's keyboardDismissMode to .onDrag. Have your interface work with the framework, not against it.
If you want to dismiss the keyboard why aren't you using the UITableViewDelegatemethod instead of UITapGestureRecognizer.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
emailAddressTextField.endEditing(true)
}
one more thing, the table view is getting the tap gesture and not cell, thats why the segues are not called.
A good solution for this would be implementing the UIGestureRecognizerDelegate:
class AddRegistrationTableViewController: UITableViewController, UIGestureRecognizerDelegate, UITextFieldDelegate, SelectRoomTableViewControllerDelegate {
...
override func viewDidLoad() {
super.viewDidLoad()
emailAddressTextField.delegate = self
...
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tapGestureRecognizer.delegate = self // You set the UIGestureRecognizerDelegate here
tableView.addGestureRecognizer(tapGestureRecognizer)
}
...
#objc func dismissKeyboard() {
emailAddressTextField.endEditing(true)
}
// In this UIGestureRecognizerDelegate's method we will check if the text field is actually being edited
// and if it's not the case, we will cancel this touch for the gesture
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return emailAddressTextField.isEditing
}
I have a couple of UITextfields in each table cell. By hitting the "Done" button or touching outside the keyboard, the keyboard should be dismissed.
My problem: the textFieldShouldEndEditing method is only called when I tap another textfield and the keyboard won't be dismissed.
I think i've implemented the necessary parts (Delegate and protocol methods).
Does anyone know what I'm missing here....?
Here is the code (the relevant part):
class myVC: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {...
This is the cell setup...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let tcell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "tbCell")
let textfield: UITextField = UITextField(frame:...
switch (indexPath.section) {
case 0: ...
case 1:
textfield.text = section2[indexPath.row]
tcell.addSubview(textfield)
textfield.delegate = self
return tcell
and this the protocol method:
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
textField.resignFirstResponder()
print("method is called")
return true
}
Thank you!
I am giving this answer in Objective-C because I don't use Swift
tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
when you will scroll your tableview, and:
tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
when you will touch outside textfield.
Swift 4.0
tableView.keyboardDismissMode = .onDrag
or
tableView.keyboardDismissMode = .interactive
why dont you try textFieldShouldReturn delegate of UITextfield.
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;}
I'm trying to add a swipe gesture (left/right) in order to hide/show my side menu.
I got it working perfectly on a UIView, however, I'm having trouble with an UITableView.
Here's my code to add my swipe gestures to my TableView:
// Add right swipe gesture recognizer
let rightSwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "toggleSideMenu")
rightSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Right
self.timelineTableView.addGestureRecognizer(rightSwipeGestureRecognizer)
// Add left swipe gesture recognizer
let leftSwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "toggleSideMenu")
leftSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Left
//sideMenuContainerView.addGestureRecognizer(rightSwipeGestureRecognizer)
self.timelineTableView.addGestureRecognizer(leftSwipeGestureRecognizer)
Here's my selector method :
func toggleSideMenu() {
println("ENTER SWIPE")
toggleSideMenuView()
}
I've also done this :
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.None
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return false
}
However, my selector menu "toggleSideMenu()" is never called when I swipe left or right.
P.S: I've also tried to add those swipe gesture on my UITableViewCell directly but it doesn't work as well.
Anyone has an idea? Thanks a lot for your time!
Thanks to Kirit Modi. Here's the solution to my problem:
Add :
leftSwipeGestureRecognizer.delegate = self
rightSwipeGestureRecognizer.delegate = self
Then add the UIGestureRecognizerDelegate delegate method :
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
return true
}
For me helped
(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
because i had two gestures. One from slide menu and one from current VC. So that one from slide menu killed VC's gestures.
My app calls a block in tableView:didSelectRowAtIndexPath and in the block it presents a view controller. If I click the cell second time when the first click is in progress, it crashes.
How can I prevent the cell to be clicked second time?
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
[dataController fetchAlbum:item
success:^(Album *album) {
...
...
[self presentViewController:photoViewController animated:YES completion:nil];
}];
At the beginning of didSelectRow, turn off user interaction on your table.
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
tableView.userInteractionEnabled = NO;
...
You may want to turn it back on later in the completion of fetchAlbum (Do this on the main thread) so that if the user comes back to this view (or the fetch fails), they can interact with the table again.
For swift 3 :
When user select a row, turn off user interactions :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.isUserInteractionEnabled = false
Don't forget to turn it on back whenever the view appear :
override func viewDidAppear(_ animated: Bool) {
tableView.isUserInteractionEnabled = true
}
You could either prevent multiple clicks (by disabling the table or covering it up with a spinner) or you could make didSelectRowAtIndexPath present your view controller synchronously and load your "album" after it's been presented. I'm a fan of the latter as it makes the UI feel more responsive.
For a cleaner approach:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
defer {
tableView.isUserInteractionEnabled = true
}
tableView.isUserInteractionEnabled = false
}
This way you're much less prone to errors due to forgetfulness.
You want to disable user interaction on the cell:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.userInteractionEnabled = NO;
As Stonz2 points out, you probably want to do it to the entire tableview though, rather than the specific cell if you're presenting a VC.
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell?.userInteractionEnabled = false
then
set back to true before you navigate to another view, otherwise when you return back to your table the cell will be still disabled.
I have a similar approach like Skaal answered but in a different way. This solution will work for any swift version .
Create a property named isPresentingVC in your view controller and set it to true.
var isPresentingVC: Bool = true
Inside didSelect row, try this
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if isPresentingVC {
isPresentingVC = false
//do your work like go to another view controller
}
}
Now in viewWillAppear or viewDidDisappear reset its value to true
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
isPresentingVC = true
}
My UITableView has the swipe to delete feature enabled. Each cell has a UIButton on it that performs an action (in this case, perform a segue).
I'd expect that if I swipe the cell by touching the button, the button's action would be canceled/ignored, and only the swipe would be handled. What actually happens, however, is that both gestures (swipe + tap) are detected and handled.
This means that if I just want to delete one cell and "accidentally" swipe by touching the button, the app will go to the next screen.
How can I force my app to ignore the taps in this case?
august's answer was nice enough for me, but I figured out how to make it even better:
Checking if the table was on edit mode to decide if the button should perform its action will make it behave as it should, but there will still be an issue in the user experience:
If the user wants to exit the editing mode, he should be able to tap anywhere in the cell to achieve that, including the button. However, the UIButton's action is still analyzed first by the app, and tapping the button will not exit editing mode.
The solution I found was to disable the button's user interaction while entering edit mode, and reenabling it when it's done:
// View with tag = 1 is the UIButton in question
- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath {
[(UIButton *)[[tableView cellForRowAtIndexPath:indexPath] viewWithTag:1] setUserInteractionEnabled:NO];
}
- (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
[(UIButton *)[[tableView cellForRowAtIndexPath:indexPath] viewWithTag:1] setUserInteractionEnabled:YES];
}
This way, dragging the button to enter edit mode will not trigger the button's action, and taping it to exit edit mode will indeed exit edit mode.
One elegant way would be to ignore button taps as long as a cell has entered editing mode. This works because the swipe to delete gesture will cause willBeginEditingRowAtIndexPath to be called before the button tap action is invoked.
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
self.isEditing = YES;
}
- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
self.isEditing = NO;
}
// button tapped
- (IBAction)tap:(id)sender
{
if (self.isEditing) {
NSLog(#"Ignore it");
}
else {
NSLog(#"Tap");
// perform segue
}
}
It's possible to do it also in your cell's subclass:
override func setEditing(editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
actionButton?.userInteractionEnabled = !editing
}
Because my function being called from a button press was a delegate onto my main UITableViewController's class and connected to the UITableViewCell as an IBAction, the button in my UITableViewCell was still firing on a swipe that had the UIButton pressed as part of the swipe.
In order to stop that I used the same UITableView delegates as the accepted answer, but had to set a file level variable to monitor if editing was occurring.
// in custom UITableViewCell class
#IBAction func displayOptions(_ sender: Any) {
delegate?.buttonPress()
}
// in UITableViewController class that implemented delegate
fileprivate var cellSwiped: Bool = false
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
cellSwiped = true
}
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
cellSwiped = false
}
func displayContactsSheet(for contact: Contact) {
if cellSwiped {
return
}
// proceed with delegate call button press actions
}