Using UITextView in UITableViewController - tap outside to dismiss keyboard - ios

I'm using a text view inside a table cell. Other cells are associated with segues so I need the following behaviour:
(Default) Tapping on other table rows triggers relevant segue
Tapping inside the text view begins editing and shows the keyboard
If the keyboard is visible, tapping outside the text view dismisses the keyboard but does not follow segues
Once the keyboard has been dismissed, subsequent taps on segues are followed.

1. Adopt the textView delegate method and
textView.delegate = self
2. Add global property
var tap = UITapGestureRecognizer()
3. Implement the textView delegate methods
func textViewDidBeginEditing(textField: UITextView) {
self.tableView.allowsSelection = false
tap = UITapGestureRecognizer(target: self, action: #selector(self.Tapped))
self.view.addGestureRecognizer(tap)
}
func textViewDidEndEditing(textField: UITextView) {
self.tableView.allowsSelection = true
self.view.removeGestureRecognizer(tap)
}
4. Implement the Tapped()
func Tapped(){
view.endEditing(true)
}
Note : This also works for UITextField, replacing View with Field throughout

Add the UITextViewDelegate protocol to your class and add these variables
var textViewIsEditing:Bool = false
var tap: UITapGestureRecognizer!
Override the viewdidload function so that the tableViewController listens for events from the text view.
Also add a UITapGestureRecognizer which will trigger a custom handleTap function
override func viewDidLoad() {
super.viewDidLoad()
myTextView.delegate = self
tap = UITapGestureRecognizer(target: self, action: #selector(MyClass.handleTap))
tap.cancelsTouchesInView = false
self.view.addGestureRecognizer(tap)
}
Add two functions to toggle textViewIsEditing based on whether the textview is being edited
func textViewDidEndEditing(textView: UITextView) {
textViewIsEditing = false
}
func textViewDidBeginEditing(textView: UITextView) {
textViewIsEditing = true
}
Now add a custom handleTap function to decide how to handle taps:
func handleTap(thisGestureRecognizer:UITapGestureRecognizer){
if(textFieldIsEditing){
// ignore this tap if editing text field but end editing
tap.cancelsTouchesInView = true
view.endEditing(true)
}else{
// proceed as normal
tap.cancelsTouchesInView = false
}
}

Related

configure tap gesture recognizer to also respond to ibactions at the tap location

I have a message chat box similar to most chat apps. The box goes up when when you start editing the messagetextView.
As is standard, there is a tap gesture recognizer that is called when the user taps anywhere else, which dismisses the keyboard.
I have another button that is visible next to the chat box.
Currently when the user taps the button, instead of triggering that ibaction, it dismisses the keyboard. Then when they tap on it again the ibaction is called. So the user has to tap twice to trigger the button when the keyboard is up.
Is there a way configure the button or gesture recognizer so that they both get called when the user taps in that location?
alternatively, is there a better design solution to solve something like this?
note: I read that in this situation ios will either choose the responder tree or gesture recognizer tree. So perhaps traversing both. How?
There are several ways to solve this problem.
It seems like the view your add tap gesture is above the button.
Set tapGesture's delegate and implement the UIGestureRecognizerDelegate.
Disable the tap gesture when tap the button.
class MessageViewController: UIViewController {
private lazy var button = UIButton(type: .custom)
weak var tapGesture: UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
}
#objc private func dismissKeyboard(_ sender: Any?) {
// dismiss the keyboard
}
#objc private func nextButtonOnClicked(_ sender: Any?) {
// dismiss the keyboard if need
// go next
}
}
extension MessageViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let tapGesture = tapGesture,
gestureRecognizer == tapGesture
else { return true }
if button.frame.contains(tapGesture.location(in: view)) {
return false
} else {
return true
}
}
}
Or just set the tapGesture's view below the button.

Tap gesture recognizer getting in the way of selecting collection view cells

I have a UICollectionView, which I can search through using a UISearchBar. I set it up so that when the user taps anywhere on the screen, the keyboard is dismissed.
In viewDidLoad():
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
Then:
#objc override func dismissKeyboard() {
view.endEditing(true)
searchBar.endEditing(true)
}
It works at dismissing the keyboard but this tap gesture recognizer is getting in the way of selecting UICollectionView cells. The didSelectItemAt method just won't work.
Looking at another answer here, I managed to fix it somewhat by removing the gesture recognizer and just adding dismissKeyboard() in the didSelectItemAt. However, now it only dismisses if you tap the cell, and then the item selects (which I don't want, I just want the keyboard to dismiss).
How do I make it so that tapping anywhere on the screen when the keyboard is showing dismisses it, after which the UICollectionView cells work and can be selected?
Thanks!
you need to extend UIGestureRecognizerDelegate in your viewcontroller and add this snips of code. then tap gesture will not work for collectionview and act normally for rest of view.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view != self.yourCollectionView{
return false
}else{
return true
}
}
In didSelectItemAt You can check, is Your UISearchBar is first responder.
if searchBar.isFirstResponder {
searchBar.endEditing(true)
} else {
//do what You want
}
If You have another things, apart from cells, add Your gesture recognizer to dismiss keyboard
Ended up fixing it by adding a transparent view on top of everything and applying the gesture recognizer to it. On viewDidLoad() I set the view to isHidden = true.
Then added these:
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
gestureView.isHidden = false
return true
}
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
gestureView.isHidden = true
return true
}
Perhaps try tapGesture.cancelsTouchesInView = false
override func viewWillAppear(_ animated: Bool) {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
}
#objc func hideKeyboard() {
searchController.searchBar.resignFirstResponder()
view.endEditing(true)
}

UILabel set onClick

I'm building an application in Swift 3, so I want to call a function if I click on a particular UILabel, so I'm write this code but not works:
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapFunction))
self.labelTemp.isUserInteractionEnabled = true
self.labelTemp.addGestureRecognizer(tap)
How can I render UILabel clickable ?
Set user interaction enabled for the UILabel and add the below code in the viewDidLoad()
self.label.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(self.labelTapped))
self.label.addGestureRecognizer(tap)
Add the tap action function as below :
#objc func labelTapped(_ gestureRecognizer: UITapGestureRecognizer) {
print("Label clicked")
}
Please make user that there is no other transparent view overlapping the UILabel in the view. If the UILabel is a part of another view then please make sure that the container View's user interaction is enabled.
Hope this helps.
Your selector should be an #objc func within self.
<#YourLabel#>.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleLabelTap)))
And when the user taps the label it will trigger:
#objc func handleLabelTap() {
// handle label tap here
}
You are lacking a function to trigger when the gesture touch is recognized. You need to add following:
let tap = UITapGestureRecognizer(target: self, action: #selector(tapFunction(_:)))
self.labelTemp.isUserInteractionEnabled = true
self.labelTemp.addGestureRecognizer(tap)
#objc func tapFunction(_ gestureRecognizer: UITapGestureRecognizer) {
// handle label tap here
}
Please ensure that You have connected the outlet to UILabel because I have created simple demo code by copy-paste your code and it is worked as expected.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(tapFunction))
self.labelTemp.isUserInteractionEnabled = true
self.labelTemp.addGestureRecognizer(tap)
}
#objc func tapFunction() {
print("tapFunction")
}
I suggest, Please remove UILabel from UIViewController and add it again.
Download sample code
Note: - Please ensure that user-interaction of UILabelis enabled
First you need to add Tap Gesture into storyboard
Create Action of that particular gesture
override func viewDidLoad() {
super.viewDidLoad()
let tapOnLabel = UITapGestureRecognizer(target: self, action: #selector(self.tapGestireAction))
self.labelTemp.isUserInteractionEnabled = true
self.labelTemp.addGestureRecognizer(tapOnLabel)
}
#IBAction func tapGestureAction(_ sender: UITapGestureRecognizer) {
//Perform action
}

GestureRecognizer taking all Touch Input

I have this Setup in my Storyboard.
In my first ViewController Scene I have a MapView from MapBox. In there I have put a TextField (AddressTextField). On that TextField when touching the view, i'm running self.addressTextField.resignFirstResponder(), but after that neither the mapview, nor any other element in there or in the Embedded Segues react on a touch or click. Probably this is because I didn't completely understand the system of the First Responder. I'm thankful for every help.
Edit 1:
I think I know what's going on now, but I don't know how to fix it. When I add the Gesture Recognizer to the View (or to the mapView, that doesn't matter), the other UIViews and the MapView do not recognize my Tap-Gestures anymore. When I am not adding the Recognizer everything works fine. It seems as if the Gesture Recognizer is recognizing every tap I make on either the UIViews or the MapView and therefore other gestures are not recognized.
Edit 2:
I just added a print() to dismissKeyboard(). As soon as any Touch Event gets recognized on the MapView or the other UIViews, dismissKeyboard() gets called. So I think my thought of Edit 1 was correct. Does anyone know how I can solve this, so that it's not only dismissKeyboard() that gets called ?
Some Code:
func dismissKeyboard(){
self.addressTextField.resignFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
dismissKeyboard()
return true
}
//Class (only partially)
class ViewController: UIViewController, MGLMapViewDelegate, CLLocationManagerDelegate, UITextFieldDelegate {
override func viewDidLoad(){
mapView.delegate = self
addressTextField.delegate = self
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
self.mapView.addGestureRecognizer(tap)
}
}
Others are just #IBActions linked to the Buttons, or other elements.
try this:
func dismissKeyboard(){
view.endEditing(true)
}
hope it helps!
After I knew the real issue I was able to solve the problem. I declared a var keyboardEnabled. Then I added these lines to my class.
class ViewController: UIViewController, UIGestureRecognizerDelegate {
var keyboardEnabled = false
override func viewDidLoad(){
super.viewDidLoad()
//Looks for single tap
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
self.mapView.addGestureRecognizer(tap)
}
/* Setting keyboardEnabled */
//Editing Target did end
#IBAction func editingTargetDidEnd(_ sender: Any) {
keyboardEnabled = false
}
//Editing TextField Started
#IBAction func editingAdressBegin(_ sender: Any) {
keyboardEnabled = true
}
//Call this function when the tap is recognized.
func dismissKeyboard() {
self.mapView.endEditing(true)
keyboardEnabled = false
}
//Implementing the delegate method, so that I can add a statement
//decide when the gesture should be recognized or not
//Delegate Method of UITapGestureRecognizer
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return keyboardEnabled
}
}
With this solution keyboardEnabled takes care of deciding wether my UIGestureRecognizer should react or not. If the Recognizer doesn't react, the Gesture is simply passed on to the UIViews or other Elements that are in my MapView.
Thanks for all your answers!

How to dismiss a keyboard called by a textfield in the navigation bar?

I know how to dismiss a keyboard, I use this extension:
extension UIViewController
{
func hideKeyboardWhenTappedAround()
{
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
view.addGestureRecognizer(tap)
}
func dismissKeyboard()
{
view.endEditing(true)
}
}
And called hideKeyboardWhenTappedAround in viewDidLoad
But my problem now is I added a UITextField to a navigationBar, and this extension no longer works!
This is how I added the UITextField:
let textField = UITextField(frame: CGRectMake(0,0,textfieldW,0.8*ram.navigationBarHeight) )
textField.borderStyle = UITextBorderStyle.RoundedRect
textField.center.y = centerView.center.y
centerView.addSubview(textField)
self.navigationItem.titleView = centerView
How to dismiss a keyboard brought from a UITextField that lurks in a navigation bar?
Make a reference to this text field, like:
var navigationBarField : UITextField?
Then initialize it:
navigationBarField = UITextField(frame: CGRectMake(0,0,textfieldW,0.8*ram.navigationBarHeight) )
textField.borderStyle = UITextBorderStyle.RoundedRect
textField.center.y = centerView.center.y
centerView.addSubview(navigationBarField)
self.navigationItem.titleView = centerView
And when you want to remove keyboard call:
navigationBarField?.resignFirstResponder()
Instead of declare instance you just need to call endEditing method of navigationController's view also like this
func dismissKeyboard()
{
navigationController?.view.endEditing(true)
view.endEditing(true)
}

Resources