IOS Swift IBAction behaviour when combining actions - ios

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.

Related

UITapGestureRecognizer on a text field not as expected

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
}

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!

iOS: Good way to pass value from image button to func in the class

Initially I had two buttons with titles: "+" and "-". Both buttons are located in table cell. I have used protocol and delegate to pass value from button title to function call which depends on input params. If input parameter is "-" - decrease value, if "+" - increase.
But later I removed titles and replaced buttons with respective images. And here I faced an issue - I cannot call function properly because title is blank.
I have implemented the following workaround. I have set Accessibility Identifier for both buttons. For example for +:
And in cell class I used accessibilityIdentifier:
#IBAction func didPressOrderCellButton(_ sender: UIButton) {
cellDelegate?.didPressOrderCellButton(order: order!, menuItem: menuItem!, action: sender.accessibilityIdentifier!)
}
But... I have some doubts if it's proper way to do this. Will it create any issues in future if I decide to work with accessibility feature?
I do not want to use title and add code to ViewController class to hide it every time view is loaded or appeared. I want to avoid such solutions in my code because it's too difficult to support such solutions I believe.
I have tried to find another button identifier that I can use, but didn't succeed.
The simple approach would be just set the tag value inside your button and then check the same tag value and perform your operation Plus or Minus accordingly.
For instance:-
if sender.tag == 0 {
//Do minus
} else if sender.tag == 1 {
//Do plus
}

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 Swift - Manually trigger textfield events

How do I trigger the events manually given the UITextField object?
textFieldShouldBeginEditing(_:)
textFieldDidBeginEditing(_:)
textFieldShouldEndEditing(_:)
textFieldDidEndEditing(_:)
In the current scenario. There is some logic which goes into the above callbacks. And there are times when I manually update a textfield
textfield.text = <new text>
And I would like the
textFieldDidBeginEditing
to be trigger, like (hopefully)
textfield.trigger("textFieldDidBeginEditing")
While my approach might not be entirely the right way, the above solution is what would most likely works for me. Although, If I can word my question differently, it would be:
How can I run some logic (code) when a UITextFields value (text) is changed via the UI or via textfield.text = <new value>
Brushing aside the situation you're trying to solve; regardless of wether this is the right approach; you can trigger UITextField events like this:
textField.sendActions(for: UIControl.Event.editingChanged)
To elaborate on my answer and my reasoning:
I don't think it is possible to trigger those kind of events "by hand" - neither should you be able to. It would just cause possibly more problems than it solves. E.g. what should happen with the caret for example, should it be displayed in the textfield?
Therefore I suggest you extract the logic into a second method and call that method from your current code and from the handler
Instead of
func textFieldDidEndEditing(textField: UITextField) {
// your logic is here
let k = 1 + 2
}
func yourCurrentCode() {
// somehow trigger the event "textFieldDidEndEditing"
}
do something like
func textFieldDidEndEditing() {
myLogic()
}
func yourCurrentCode() {
myLogic()
}
func myLogic() {
// your logic should be here
let k = 1 + 2
}
If you would like to be notified every time the value of the UITextField changes you should use the notification
UITextFieldTextDidChangeNotification

Resources