UITapGestureRecognizer on a text field not as expected - ios

In my class I have 11 UITapGestureRecognizers in an array textViewRecognizer attached to 11 out of 100 UITextFields in an array boxArray. When a Textfield is tapped containing a UIGestureRecognizer it runs tappedTextView where I try to get the index of the first responder.
However, due to some weird ordering in how things are executed, the action function only gives me the first responder of the previous first responder to the one that was just tapped.
Also, I have to double tap to even select the text field I was going for! I need to use the tap function and not the text delegates so this has been a real headache.
I have...
#objc func tappedTextField(_ sender: UITapGestureRecognizer) {
for i in 0...99 {
if (boxArray[i]?.isFirstResponder)! {
if let index = boxArray.index(of: boxArray[i]) {
print(index)
break
}
}
}
}
in my viewDidLoad I have
for i in 0...10 {
textFieldTapRecognizer[i].addTarget(self, action: #selector(self.tappedTextField(_:)))
}
In my class I have
I want to set 11 out of 100 textFields to have this a tap recognizer depending on some conditions (I'm just going to use a regular for loop here)
for i in 0...10 {
boxArray[i]?.addGestureRecognizer(textFieldTapRecognizer[i])
}
Is there anyway I can get it to give me the actual first responder, after the tap was made?
Is there anyway to go around the double tap to select the text field that has a UITapGesture?
Any help is greatly appreciated.
Edited: properly named functions

It sounds like you want to remove the automatic editing behavior on a UITextView. You can grab more control over that with the textViewShouldBeginEditing(_ textView: UITextView) -> Bool UITextViewDelegate method, documented here.
If you return false for that method, this should avoid needing a double tap to get to your gesture recognizer. Depending on your use case, you can then "allow" the tap to go to the text view by returning true for the textView you want to be actually edited.
While I'm not 100% clear on the first responder part of your question, since the textView won't be grabbing first responder if it's not starting it's editing mode, this should address that concern I believe. Good luck!

I would add a Tag to my UITextView and set the UITextViewDelegate to my ViewController.
Then I would add the following Delegate method:
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
print("Textview tag: ", textView.tag)
return false
}

Related

IOS Swift IBAction behaviour when combining actions

I have a custom keyboard extension which works as expected but I am coming across some odd behaviour which I can't explain. It is designed primarily for data input into Excel spreadsheets, so the fewer the keystrokes the better.
I have 2 IBActions.
Keypressed takes the value of the keypresses and inserts it into the current cell.
Returnpressed emulates the enter key which moves the cursor onto the next cell.
These work as described above, which is all good, but I am now trying to combine the actions, so that the user only has to press the first key and it inserts the text and then moves onto the next cell.
So when I simply extend the code in the Keypressed IBAction to include the code in the Returnpressed action, it simply inserts a carriage return into the text and stays in the same cell.
What am I missing please?
Here is a code snippet:
extension UIKeyInput{
func `return`() -> Void{
insertText("\n")
}
}
class KeyboardViewController: UIInputViewController, AVAudioPlayerDelegate {
#IBAction func KeyPressed(_ sender: Any) {
let string = (sender as AnyObject).titleLabel??.text
(textDocumentProxy as UIKeyInput).insertText("\(string!)")
**//THIS IS THE LINE THAT FIXED THIS FOR ME
textDocumentProxy.adjustTextPosition(byCharacterOffset: -1)**
self.EnterPressed(nil)
}
#IBAction func EnterPressed(_ sender: Any?) {
//default action for a return key press
textDocumentProxy.return()
}
I think you need to override the UITextInputDelegate textDidChange method (UIInputViewController implements UITextInputDelegate).It turns out that textDidChange is called when the text changes. And make the first responder to the next text field of your cell.
I managed to fudge this by determining what action s caused textDidChange to fire. It turns out that by simply adjusting the cursor portion, between inserting the text and firing the Return action works.
Not really sure how, but achieves what I want without the the user knowing it is a kludge and no overhead. I have changed the original code snippet to show the fix.

How to disable UIControlEventEditingChanged

I have a UIViewController with several UITextFields. When tap one text field, it should present the barcode scanning view controller. Once the scanning is completed, my barcode scanning viewcontroller is disappearing (used "dismissViewcontroller") and the scanned value should entered into the text field I tapped. This is working fine. I have set the delegate for each text field like this.
[field addTarget:metrixUIViewControllerIn action:#selector(executeScriptOnTextFieldChange:) forControlEvents:UIControlEventEditingChanged];
The problem is this :
Lets say I have set an alert to display inside this executeScriptOnTextFieldChange method. Once I tapped on the 1st text field, then the barcode scanner comes. Once I scanned barcode scanner closes and set the value for the first text field and fire the alert.Thats ok. But then if scanned by tapping the 2nd textfield and the string will set to that textfield and fire the alert related to 2nd textfield also fire the alert related to first textfield as well. I want to stop happening this. Is there any way to disable the delegate for one textfield? This happens because I am refreshing the view in the viewDidAppear. But I have to do that as well. Please help me.
UIControlEventEditingChanged for a textField can fire at many different events that are not even directly related to that textField, but related inderectly.
For instance, when your ViewController is presenting the barcodeScanner it may trigger a "resignFirstResponder" event on the textField. Also when the 2nd textField is tapped, cause the 2nd becomes first responder and the 1st suffers a "resignFirstResponder".
I suggest trying to use a UITapGestureRecognizer in your textField instead. Example:
Swift 4
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.textField.tag = 1
self.textField.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(fireTextField(_:))))
}
#objc func fireTextField(_ sender: UIGestureRecognizer){
let view = sender.view
guard view != nil else{
//Do nothing
return
}
let condition = view!.tag == 1
if condition{
//or do whatever other stuff you need
self.textField.becomeFirstResponder()
}else{
//Whatever for other textFields
}
}
This way, you could use the "tag" attribute to determine which textField is firing and so adjust "condition". You could also filter the flow with a switch using the "tag".
Not sure if any of this will really help as I would need more info about the flow you need to accomplish. Hope it does help!

Getting tag or index for a tap gesture recognizer

I have a uitapgesturerecognizer, for a uiview. I have 30 of these uiviews in a scrollview. Also in each of these views there is a label with different words in it for each view. Now when I hit a tap gesture recognizer, I want to access the text from the label for that particular view. Whats happening is I'm printing the words of the last view in the index. Can someone show me how I can access each view in each index.
open var Name: UILabel!
open func ynCategoryButtonClicked(_ sender: UITapGestureRecognizer) {
guard let text = Name.text else { return }
ynSearch.appendSearchHistories(value: text)
self.delegate?.ynCategoryButtonClicked(text: text)
}
The place where you are assigning your tapGesture to your "UIButtonView", add this line:
yourButton.accessibilityHint = "YourText"
And In your "buttonTapped()" method get the text like this:
let yourText = "\(sender.accessibilityHint)"
Hope this will help!
Honestly, it sounds like you should be using a UITableView. Either way though, you should alter your delegate method ynCategoryButtonClicked to also pass a reference to itself (so something like delegate?.ynCategoryButtonClicked(text: text, ynCategory: self)). Then if you go the TableView route you can find the index by using indexPath(for:UITableViewCell). Or, if you stick with the scrollview, you should be able to maintain an array of the views, and just search through them for the ynCategory.

textViewDidChangeSelection method thinks textView.text is empty

For various reasons, I need to access the text in a textview whenever a selection is changed, so I have implemented the delegate method textViewDidChangeSelection() to do so. For some reason though, when I try to access textView.text from within this method, sometimes it comes back as empty even when it's not. Take this bit of code for example.
func textViewDidChangeSelection(_ textView: UITextView) {
print("Called textViewDidChangeSelection")
if textView.text.isEmpty {
print("textview is empty")
} else {
print("textview is not empty")
}
}
Using this example in my code, I click on a textView that is NOT empty and sometimes it comes back with "textview is empty." It seems like it tends to happen most often immediately after re-running the simulator and clicking on any textView, but I've also seen it happen when just clicking on a textView for the first time (after having clicked on some other textView) or when I segue back to my UIView containing the textViews from some other view.
Does anyone know why this happens?
textViewDidChangeSelection(_ textView: UITextView)
is called when the text selection changes, not necessarily just when another textView is selected. In fact in the olden days the selectedRange property of the textView used to return zero (indicating an insertion) but now (according to Apple) the length of the selection range may be non-zero. When you click away from a textView your delegate method may be firing for the initial textView (the one you're leaving) which would give you the result you're seeing, if it's empty.
Try giving your textView tags and using:
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
print("Selected \(textView.tag)")
return true
}
To see if it solves your problem. Of course tags are just a quick and dirty check and you'd probably use a more flexible textView identification in your app.

IOS Dismiss/Show keyboard without Resigning First Responder

My application is used with a barcode scanner connected via Bluetooth. When the scanner is connected you can double tap a button on the scanner to dismiss/show the on screen keyboard. 90% of the time the user will want the keyboard to be hidden as they will be scanning a barcode to input data. There are a few exceptions that I know of ahead of time that the keyboard will need to be enabled, I would like to save them the effort of pressing the scanner button to bring up the keyboard and instead force the keyboard to show up.
The scanner does not use resignfirstresponder to dismiss the keyboard, this is evident because the cursor is still visible and scanning a barcode will input data into the current text field.
Does anyone know how to dismiss/show the on screen keyboard without using resignfirstresponder?
For reference I am using this scanner http://ww1.socketmobile.com/products/bluetooth-scanners/how-to-buy/details.aspx?sku=CX2864-1336
To end editing completely in the view you can use the following
[self.view endEditing:YES];
This will remove the keyboard for you in the view.
For anyone still struggling with this, you can achieve this in Swift by making the inputView of the textfield equals UIView()
That is:
yourtextfield.inputview = UIView()
I ran into this today and have found a solution. The trick is to use a secondary text field that is off-screen or hidden with a custom empty inputView set and make that field become the first responder. That field captures text from the hardware scanner while the software keyboard hides.
However I got this working using a very similar approach and instead making the view controller itself the first responder as the scanning input view.
Example:
class SearchViewController: UIViewController, UIKeyInput {
let searchField = UITextField()
let softwareKeyboardHider = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(searchField)
inputAssistantItem.leadingBarButtonGroups = []
inputAssistantItem.trailingBarButtonGroups = []
}
override var canBecomeFirstResponder: Bool {
return true
}
override var inputView: UIView? {
return softwareKeyboardHider
}
var hasText: Bool {
return searchField.hasText
}
func insertText(_ text: String) {
searchField.insertText(text)
}
func deleteBackward() {
searchField.deleteBackward()
}
}
Now, when you want to hide the software keyboard make SearchViewController the first responder.
To show the software keyboard make SearchViewController.searchField the first responder.

Resources