Leaving inputAccessoryView visible after keyboard is dismissed - ios
What I'm trying to do is to create something similar to the "find on page" search function in Safari on iPad.
I'm using a UIToolbar with some items in it and attached it to the keyboard by setting it as an inputAccessoryView on the UITextField. Works like a charm, but there is one thing I can't figure out. In Safari, when you search for something, the keyboard disappears but the tool bar remains on the bottom of the screen.
Does anyone have a clue on how to accomplish this? The only solution I can think of is to respond to a keyboard dismissed event and then pull out the UIToolBar and create a custom animation that moves it to the bottom of the screen. But this is hacky. I am looking for a more elegant solution. Something that can make me decide what to do with the input accessory view when the keyboard gets dismissed.
It's done like this:
Assign your UIToolbar to a property in your view controller:
#property (strong, nonatomic) UIToolbar *inputAccessoryToolbar;
In your top view controller, add these methods:
- (BOOL)canBecomeFirstResponder{
return YES;
}
- (UIView *)inputAccessoryView{
return self.inputAccessoryToolbar;
}
And then (optionally, as it usually shouldn't be necessary), whenever the keyboard gets hidden, just call:
[self becomeFirstResponder];
That way, your inputAccessoryToolbar will be both your view controller's and your text view's input accessory view.
I've ended up with UIToolBar that is not assigned as input accessory view, and slide up and down on UIKeyboardWillShowNotification / UIKeyboardWillHideNotification
Update to Swift 4, based on prior answers. If you add toolbar via storyboards you can do this
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBOutlet var toolbar: UIToolbar!
override var canBecomeFirstResponder: Bool {
get {
return true
}
}
override var inputAccessoryView: UIView {
get {
return self.toolbar
}
}
override func viewDidLoad() {
super.viewDidLoad()
textField.inputAccessoryView = toolbar
}
}
In this case, whenever text field resigns first responder, it defaults first responder to main view. Keep in mind, you might want to explicitly resign first responder, and set main view as first responder if there are multiple UI elements and first responder defaults to undesired view after resignation.
Adding to #arik's answer, here is the Swift version:
class ViewController: UIViewController {
#IBOutlet var textField: UITextField!
// Input Accessory View
private var inputAccessoryToolbar: UIToolBar?
override func canBecomeFirstResponder() -> Bool {
return true
}
override var inputAccessoryView: UIView? {
return inputAccessoryToolbar
}
override func viewDidLoad() {
super.viewDidLoad()
inputAccessoryToolbar = UIToolbar(frame: CGRectMake(0, 0, view.frame.size.width, 50))
textField.inputAccessoryView = inputAccessoryToolbar
}
// UITextFieldDelegate
func textFieldShouldReturn(textField: UITextField) -> Bool {
becomeFirstResponder()
return true
}
}
Thanks for the clean solution!
You may also need to work around the bug with the inputAccessoryView not respecting the safe area margins and thus not making room for home indicator thing on iPhone X: iPhone X how to handle View Controller inputAccessoryView?
I found the easiest solution when you have a UIToolbar from a xib and you are also using that UIToolbar as the inputAccessoryView of a text field is to embed the toolbar in a UIView when you return it from your overridden inputAccessoryView, and make the containing UIView taller by the safeAreaInsets.bottom. (Other solutions suggest constraining the bottom of the toolbar to the safe area in a subclass, but this leads to constraint conflicts and also means the area under the toolbar is the wrong colour.) However, you have to also bear in mind that the text field can have focus even when there is no keyboard on the screen (for instance if there is an external keyboard), so you need to change the inputAccessoryView of the text view to this toolbar-within-a-UIView in that case as well. In fact it will probably make things simpler to just always use the containing view and adjust the size of it appropriately. Anyway, here's my override of inputAccessoryView:
override var inputAccessoryView: UIView? {
if toolbarContainerView == nil {
let frame=CGRect(x: toolBar.frame.minX, y: toolBar.frame.minY, width: toolbar.frame.width, height: toolBar.frame.height+view.safeAreaInsets.bottom)
toolbarContainerView = UIView(frame: frame)
}
if (toolbar.superview != toolbarContainerView) {
//this is set to false when the toolbar is used above the keyboard without the container view
//we need to set it to true again or else the toolbar will appear at the very top of the window instead of the bottom if the keyboard has previously been shown.
toolbar.translatesAutoresizingMaskIntoConstraints=true
toolbarContainerView?.addSubview(toolbar)
}
return toolbarContainerView
}
It would probably be a good idea to override viewSafeAreaInsetsDidChange to adjust the size of toolbarContainerView in that case, too.
Related
Swift button click to show keyboard with toolbar view
My scenario, I am trying to implement keyboard with toolbar view using Swift. Here, I need to do whenever I click the button need to show keyboard with toolbar view view without using textview or textfield. Below My code I am using Simply I drag UIView to the top of the bar of current ViewController I declared below code for IBOutlet #IBOutlet private var toolbarView: UIView! #IBOutlet private var textView: UITextView! In ViewDidload I used below code override func viewDidLoad() { super.viewDidLoad() textView.inputAccessoryView = toolbarView } In Button action now I am calling like below #IBAction func CircleClick(_ sender: Any) { self.textView.becomeFirstResponder() }
You need to define an outlet for textfield for referencing it. #IBOutlet weak var textfield:UITextField! You can activate any text field by calling becomeFirstResponder() on the UITextfield object. textfield.becomeFirstResponder() You can call this function in the IBAction of any action of UIButton. #IBAction func btnShowKeyboardClicked(){ textfield.becomeFirstResponder() } For maintaining the custom toolbar as the accessory view, you need to set it to the text field object. First either create the custom toolbar view from code or load using XIB, and assign it to the inputAccessoryView of textfield. let customInputAccessoryView = CustomView() // Load from XIB or from Code textfield.inputAccessoryView = customInputAccessoryView
I would like demonstrate it from scratch, so you can learn from this guide. First off all you should create a view, simply click Command+N button or choose File-New-File from the menu of Xcode. Choose a View template under User Interface section as shown below. Give it a name, in this case a Header (you can name it whatever you want). And create a class to it, again click Command+N or from menu as I mentioned above. In this case choose a Cocoa Touch Class under Source section as shown below. And name it, as a HeaderView. Subclass of UIView Open up your Header.xib file from the Project Manage. Click your view, give it a size as a freeform, Top bar to none, and Bottom bar to none. Add a UIButton to this view, I guess you know already, how to do it. And click this view, go to the Identity Inspector. And give it a class, in this a HeaderView.swift is our class. Connect your button as shown below and we are done: Now all of our focus in code, insert following line of code to the viewDidLoad(). override func viewDidLoad() { super.viewDidLoad() // Load the view using bundle. // Make sure a nib name should be correct // And cast it to the class, something like this if let headerView = Bundle.main.loadNibNamed("Header", owner: self, options: nil)?.first as? HeaderView { // Do some stuff, configuration of the view headerView.frame = CGRect.init(x: 0, y: 0, width: self.view.frame.width, height: 44) headerView.button.setTitle("Done", for: .normal) headerView.button.addTarget(self, action: #selector(self.doneAction(sender:)), for: .touchUpInside) // Add this view as an accessory to the text field or text view, in this case I have added this to the text field self.textField.inputAccessoryView = headerView } } Create a custom function to the done button: #objc func doneAction(sender: UIButton) { // Do something // Resing your text field or text view self.textField.resignFirstResponder() } Congrats! You're done. I hope it will help.
iPhone X issues when using a UITextView in an inputAccessoryView
I'm trying to make a user interface similar to Messages where an input bar is pinned to the top of the keyboard, yet still appears at the bottom of the screen when the keyboard is dismissed. The approach I'd like to use involves setting the 'inputAccessoryView' property of my view controller with a view that contains a UITextView. This seems to work well on devices other than an iPhone X. Sometimes on the iPhone X, the keyboard when raised, shows the bottom row ('123', 'space' and 'return') of keys displayed at the very bottom of the screen below the safe area. The Globe and Microphone icons are drawn over the bottom row. I've included a screenshot showing this issue below. Pressing 'return' to add an extra line in the text view seems to reset the keyboard to its proper layout. I'm wondering if there is a good workaround for this issue. The following code can be used to demonstrate the problem (replace the contents of ViewController.Swift of the project generated from the "Single View App" project template): import UIKit class ViewController: UIViewController { private var textViewInputAccessoryView = UITextView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 375, height: 44))) override var canBecomeFirstResponder: Bool { return true } override var inputAccessoryView: UIView? { return textViewInputAccessoryView } override func viewDidLoad() { super.viewDidLoad() textViewInputAccessoryView.backgroundColor = UIColor.lightGray } }
UITextView's not displaying content during push animation when is in inputAccessoryView
I have UIViewController that has inputAccessoryView overrided with custom UITextView, lets call that view controller A. And I have another view controller that push A to navigation stack. So, when I push A first time everything is ok - the UITextView appeared with text immediately. The strange thing starts with next push - the UITextView's text does not appear until view controller push transition animation end. => The code of viewController A is there: class NextViewController: UIViewController { private var userInputView: UIView! override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.grayColor() let textView = UITextView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 30) ), textContainer: nil) textView.text = "asd" userInputView = textView } override var inputAccessoryView: UIView? { return userInputView } override func canBecomeFirstResponder() -> Bool { return true } } Tested in iOS 9.3 simulator. After some coding I noticed that if inputAccessoryView is not deallocated (stored somewhere in global scope) the appearance during animation is defined by last UITextView state. But this solution is not good for me is ok actually. But is there any native-like solution, am I missing something? Because the desired behaviour is rather standard in my opinion.
So I will stick with the solution where the inputAccessoryView is stored until next appearance.
Insert subview above inputAccessoryView
My problem is this: I have a UIViewController with an input accessory view: class ChatViewController: UIViewController { override var inputAccessoryView: UIView! { get { return customToolbar } } } I would like to be able to add a subview above that inputAccessoryView, something like this: let customView = UIView() customView.backgroundColor = UIColor.blueColor() self.view.insertSubview(customView, aboveSubview: self.inputAccessoryView) But for some reasons, the inputAccessoryView is always on top. I have thought of hiding it each time i want to add a subview (full screen for instance), but this is not super clean, and I'd have to put it back each time i remove my subview. Here's what I have right now, I just want my toolbar to be behind the transparent overlay.
Try to add your transparent overlay to keyWindow in the Objective-C it's look like [[[UIApplication sharedApplication].windows lastObject] addSubview :customView];
Resize UITextField inputAccessoryView?
I have a UIViewController with a UITextField on which I call becomeFirstResponder() immediately in viewDidLoad(). Prior to calling that, I instantiate a UIButton and override: override var inputAccessoryView: UIButton { return self.nextButton } override func canBecomeFirstResponder() -> Bool { return true } I cannot figure out how to resize the input accessory view. I want to make it taller but it is always 44 points tall. It is just a big UIButton - nothing special. Any suggestions?