Dismiss keyboard in an iOS Notification Content Extension - ios

I'm presenting custom action buttons in my iOS 11 notification window via a Notification Content Extension target. One of them is a 'comment' button. If I press it the keyboard shows up properly, but I am not able to figure out how to have the keyboard go away and get back to the other buttons on the notification. There's not really anything I can see to call resignFirstResponder on. Am I just missing something really obvious?

There is more than one way to do this.
Without A Content Extension
The first does not even require a notification content extension! The UNTextInputNotificationAction does all of the work for you. When initializing the action you specify parameters for the text field that will be presented when the action is triggered. That action is attached to your notification category during registration (i.e. inside willFinishLaunchingWithOptions):
userNotificationCenter.getNotificationCategories { (categories) in
var categories: Set<UNNotificationCategory> = categories
let inputAction: UNTextInputNotificationAction = UNTextInputNotificationAction(identifier: "org.quellish.textInput", title: "Comment", options: [], textInputButtonTitle: "Done", textInputPlaceholder: "This is awesome!")
let category: UNNotificationCategory = UNNotificationCategory(identifier: notificationCategory, actions: [inputAction], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: "Placeholder", options: [])
categories.insert(category)
userNotificationCenter.setNotificationCategories(categories)
}
This will produce an experience like this:
Note that by default, the "Done" button dismisses the keyboard and notification.
With more than one action you get this:
There is no going back to the action buttons that were presented with the notification - notifications can't do that. To see those actions choices again would require showing another notification.
With a Content Extension
First, the above section works with a content extension as well. When the user finishes entering text and hits the "textInputButton" the didReceive(_:completionHandler:) method of the content extension is called. This is an opportunity to use the input or dismiss the extension. The WWDC 2016 session Advanced Notifications describes this same use case and details ways it can be customized further.
This may not meet your needs. You may want to have a customized text entry user interface, etc. In that case it is up to your extension to handle showing and hiding the keyboard. The responder that handles text input - a UITextField, for example - should become first responder when the notification is received. Doing so will show the keyboard. Resigning first responder will hide it. This can be done inside a UITextField delegate method.
For example, this:
override var canBecomeFirstResponder: Bool {
get {
return true
}
}
func didReceive(_ notification: UNNotification) {
self.label?.text = notification.request.content.body
self.textField?.delegate = self
self.becomeFirstResponder()
self.textField?.becomeFirstResponder()
return
}
// UITextFieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.textField?.resignFirstResponder()
self.resignFirstResponder()
return true
}
Produces a result like this:
Keep in mind that on iOS 10 and 11 any taps on the notification itself - like on your text field - may result in it being dismissed! For this and many other reasons going this route is probably not desirable.

Related

How to make editingDidEnd not trigger when user tap back navigation in swift?

Currently i have an issue in editingDidEnd method of swift textfield.
I have a module inside editingDidEnd func to check some validation in B ViewController. If the validation is wrong then it shows popup / alert.
In this case, while user is typing and still focus on the textField, users tap back on navigation bar. it makes editingDidEnd function is also called. So the page is showing A ViewController and also showing pop up.
Is there any workaround to handle this issue? I don't want the alert is showing when i tap back in navigation bar. My expectation is if user press back on navigation. it's not call editingDidEnd function
Thanks Before
e.g.
B View Controller
extension bviewcontroller: textfielddelegate {
func editingDidEnd(_ value: String, textField: SearchTextField) {
//showingalert
}
}
You can add a guard statement to your editingDidEnd method like below.
func editingDidEnd(_ value: String, textField: SearchTextField) {
guard navigationController?.topViewController is bviewcontroller else {
print("DON'T SHOW ALERT")
return
}
print("SHOW ALERT")
}
I have tested the above code with a simple view controller containing a UITextField. However, your delegate method appears to be something different than the standard UITextFieldDelegate's textFieldDidEndEditing(_:) method.
Otherwise I would have suggested that you could also try experimenting with the textFieldDidEndEditing(_:reason:) method.

UIButton selector not working after button tapped within WKWebView

I have a custom titleView with 2 custom UIButtons with arrow images that allow navigation to the next view controller in the paging structure. They work perfectly fine until a button is tapped within the WKWebView. Then they don't work anymore and the selector is not called. Note that other buttons in the nav bar still work (UIBarButtonItems). The buttons work properly again after the user swipes over to the next view controller.
After looking into it some, it looks like a WKCompositingView becomes first responder and if I override becomeFirstResponder() in a WKWebView subclass, the issue goes away. I'm still a little baffled though, and would like to understand the root of the problem.
class NonFirstRespondableWebView: WKWebView {
override func becomeFirstResponder() -> Bool {
return false
}
}
Does anyone have any insight into why this is happening?
Most UI elements in swift have a UIResponder. Unhandled events are passed up the responder chain to enclosing views. My guess is that the WKWebView is absorbing all touch events once the window has become active. You can learn more about the responder chain here
Regarding a first responder. From the docs:
The first responder is usually the first object in a responder chain to receive an event or action message. In most cases, the first responder is a view object that the user selects or activates with the mouse or keyboard.
Assuming you want to keep interactivity with the WKWebView fully functional (e.g. you need to bring up a keyboard or something), you can use
webView.resignFirstResponder()
To resign the responder at any time.
Otherwise, an extension that would give you the same functionality might look something like this:
extension WKWebView {
open override func becomeFirstResponder() -> Bool {
if self.superview?.superview is UIWebView {
return false
} else {
return super.becomeFirstResponder()
}
}
}

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!

Show/Hide UIButton in another controller

I have a button in my ViewController.swift:
#IBOutlet weak var exampleButton: UIButton!
I would like to show/hide that button from the AppDelegate, when something specific happens (i.e. the app enter background etc.).
How can i do that?
One Approach can be
- You can use Notifications for this
Add Observer in your view controller where button needs to be hidden
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "hideButton",
name: #"HIDE_BUTTON_NOTIFICATION",
object: nil)
func hideButton() -> Void {
// Hide your button here
// Remember to hide on main thread
}
From wherever you want to hide the button (like from AppDelegate), you can post this notification
NSNotificationCenter.defaultCenter().postNotificationName(#"HIDE_BUTTON_NOTIFICATION", object: nil)
Rather than let another object directly manipulate it, use a notification to indicate a state change. The state dictates whether the button appears or not.
Make sure in your notification listener that you only touch the button on the main thread.
Notification is a great idea, but what if your ViewController is not your initial ViewController, or hasn't been initialized yet? It will not be able to catch this notification. Possible solution (maybe not elegant) in extension to other answers is to provide a flag. Your ViewController will be checking it, e.g. in viewDidLoad(). Your flag could be stored in a singleton object which will be catching notification from AppDelegate.
To sum up, you should add notification observer in your ViewController, to catch event from AppDelegate. (like in other answers)
Create singleton class to store appropriate information.
Check condition in viewDidLoad:
if YOUR_SINGLETON.YOUR_FLAG == true {
showButton()
} else {
hideButton()
}
Don't forget to add notification observer also in your singleton class.

Resources