I've added a toolbar above keyboard to show Done button to dismiss keyboard. I've added it on my login screen. When keyboard is showing and I tap on saved Password icon to select saved password, keyboard hides but toolbar doesn't hide. Toolbar sits at the bottom of screen and then moves up with keyboard when keyboard shows again. It looks bad.
How do I fix it so that Toolbar doesn't show on it's own and shows/hide only with keyboard?
override func viewDidLoad() {
super.viewDidLoad()
self.emailTextField.addDoneButton(title: "Done", target: self, selector: #selector(tapDone(sender:)))
self.passwordTextField.addDoneButton(title: "Done", target: self, selector: #selector(tapDone(sender:)))
}
#objc func tapDone(sender: Any) {
self.view.endEditing(true)
}
extension UITextField {
// Add done button above keyboard
func addDoneButton(title: String, target: Any, selector: Selector) {
let toolBar = UIToolbar(frame: CGRect(origin: .zero, size: CGSize(width: UIScreen.main.bounds.size.width, height: 44.0)))
let flexible = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let barButton = UIBarButtonItem(title: title, style: .plain, target: target, action: selector)
barButton.setTitleTextAttributes([NSAttributedString.Key.font : UIFont.main, NSAttributedString.Key.foregroundColor : UIColor.red], for: [])
toolBar.setItems([flexible, barButton], animated: false)
self.inputAccessoryView = toolBar
}
}
I personally handle this in a different way as I'm not using any toolbar but custom views above the keyboard. As I want those views to animate and appear/disappear depending on the keyboard position, I first listen to keyboard changes:
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged(notification:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
And then handle the keyboard current size and position manually here like so:
func keyboardChanged(_ userInfo: Dictionary<AnyHashable, Any>?) {
if let userInfo = userInfo {
let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let duration:TimeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
let animationCurve:UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw)
if (endFrame?.origin.y)! >= UIScreen.main.bounds.size.height {
// keyboard is masked, you can mask/move your toolbar here
} else {
// update your toolbar visibility and/or position/constraints here using 'endFrame?.size.height'
}
// animate your toolbar or any other view here:
UIView.animate(withDuration: duration,
delay: TimeInterval(0),
options: animationCurve,
animations: {
// animate what you need here
self.view.layoutIfNeeded()
},
completion: nil)
}
}
So in your case I would create the toolbar first and constraint it to the bottom of the screen. Then I would use the code above to handle its position and visibility.
Then, whenever the keyboard position is updated, you can handle the toolbar (position and visibility) in the keyboard notification handler shown above.
Might not be a direct answer to this question but I highly suggest you to take a look at the IQKeyboardManager library. By default, it is a one liner keyboard handler but you can add your accessory views easily and it manages them well
https://github.com/hackiftekhar/IQKeyboardManager
Related
I am trying to use a TextEditor() in SwiftUI and will need .keyboardType(.numberPad). Unfortunately though, whenever I set this option, for some reason my device and simulator seem to ignore it entirely! It just shows the default keyboard no matter what I set as the keyboard option.
Does anyone know why this might be? I have tested iOS 14.0 and 14.2 Beta with Xcode 12 Beta 2.
Here's an ugly workaround using SwiftUI-Introspect
(FB8816771 is the feedback ID I logged with Apple.)
private extension View {
func keyboardType_FB8816771(_ type: UIKeyboardType) -> some View {
let customise: (UITextView) -> () = { uiTextView in
uiTextView.keyboardType = type
}
return introspect(selector: TargetViewSelector.siblingContaining, customize: customise)
}
}
In case this helps anyone with the fact that TextEditor does not have a Done button...expanding on the answer from Luke Howard above...
import Introspect
extension View {
func addDoneButton() -> some View {
let helper = MainViewHelper()
let customise: (UITextView) -> () = { uiTextView in
let toolBar = UIToolbar(frame: CGRect(x: 0.0,
y: 0.0,
width: UIScreen.main.bounds.size.width,
height: 44.0))//1
let flexible = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let barButton = UIBarButtonItem(title: "Done", style: .plain, target: uiTextView, action: #selector(helper.close))
toolBar.setItems([flexible, barButton], animated: false)//4
uiTextView.inputAccessoryView = toolBar
uiTextView.keyboardAppearance = .light
}
return introspect(selector: TargetViewSelector.siblingContaining, customize: customise)
}
}
class MainViewHelper {
#objc func close() {
}
}
I am trying to dismiss the keyboard on swipe down but my input accessory view (custom uiview class) is not sticking to it on swipe down. It leaves a space between it and the keyboard and is not syncing up with it and goes down only after the keyboard disappears. It works fine when activating the keyboard by tapping on the text view/cmd+k to toggle it up and down, but not when swiping it down. From the image I am swiping down to dismiss the keyboard but there is a giant gap. [1]: https://i.stack.imgur.com/xLV6u.jpg (Sorry if my formatting is bad, I'm still getting used to actually posting on stackoverflow)
Here is my code so far pertaining to it:
// accessory view is anchor to the bottom to start
func layoutInputAccessoryView() {
view.addSubview(inputFieldAccessoryView)
inputFieldAccessoryView.anchor(right: view.rightAnchor, left: view.leftAnchor)
inputFieldAccessoryViewBottomAnchor = inputFieldAccessoryView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
inputFieldAccessoryViewBottomAnchor?.isActive = true
}
// swiping to dismiss in collection view
collectionView.keyboardDismissMode = .interactive
// listener for when the keyboard shows/hides
func addKBObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(handleKBWilShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKBWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
// method for when the keyboard shows
#objc func handleKBWilShow(notification: Notification) {
if let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let rect = frame.cgRectValue
let height = rect.height
inputFieldContainerBottom?.constant = -height
view.layoutIfNeeded()
}
}
// selector method called when keyboard will hide
#objc func handleKBWillHide(notification: Notification) {
if let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double {
UIView.animate(withDuration: duration) {
self.inputFieldContainerBottomAnchor?.constant = 0
self.view.layoutIfNeeded()
}
}
}
I have an textview at bottom of screen and search bar at top of screen. Following is my code to solve the problem of keyboard when textview is pressed
extension UIView {
func bindToKeyboard(){
NotificationCenter.default.addObserver(self, selector: #selector(UIView.keyboardWillChange(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardWillChange(_ notification: NSNotification) {
let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let deltaY = targetFrame.origin.y - curFrame.origin.y
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
self.frame.origin.y += deltaY
},completion: {(true) in
self.layoutIfNeeded()
})
}
}
But when I press search bar then the screen moves up and search bar disappears. If I do view.bindToKeyboard() then the edittext is proper after displaying the keyboard.
One solution which I tried was binding the outlet of textview to keyboard but the textview disappears as soon as I start typing.
I think the problem is that you are trying to know when the keyboard is going to appear. Searchbar has a textfield. So when it's tapped it opens the keyboard like your textview and the keyboardWillChange is called.
keyboardWillChange(_ notification: NSNotification)
So the keyboard appears and hides your searchbar. You can detect searchbar tap and cancel the notification there.
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool
{
//Dismiss your keyboard notification here
return true
}
You should use a library to handle this situation. I'm suggesting IQKeyboardManager. IQKeyBoardManager Github.
I edit my earlier respond... have you try to put your searchBar into navigationBar?
something like this (just put on viewDidLoad):
navigationItem.titleView = searchBar
I'm using UIView Extension for button to slide it up with keyboard.
extension UIView {
func bindToKeyboard() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardWillChange(_ notification: NSNotification) {
let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
let startingFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let endingFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let deltaY = endingFrame.origin.y - startingFrame.origin.y
let options = UIViewAnimationOptions(rawValue: curve << 16)
UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
self.frame.origin.y += deltaY
self.layoutIfNeeded()
}, completion: nil)
}
}
Then in ViewController just using :
func setUpView() {
okayButton.bindToKeyboard()
self.isHeroEnabled = true
}
But the problem is when I press other button on the screen:
Save button disappears after tapping on other button, when it is in the "upper position", and appears when it's on the bottom. What am I doing wrong? How to prevent/fix it?
Edit: There is no action on any of these buttons! (+,-,save)
Thanks!
You don't necessarily need to update self.view . What you can do is create a IBOutlet bottom spacing for the save button.
#IBOutlet weak var saveButtonBottomSpacing: NSLayoutConstraint!
When keyboard is open, set bottom spacing constant to keyboard's height.
When keyboard is dismissed, restore the bottom spacing. May be 0 or your desired value.
You can make this changes within UIView animation block.
Hide (resign) your keyboard upon successful action on 'Save' button.
Here is sample code you need to update in your Save button action.
#IBAction func btnSave(sender: Any){
// add this line in your upon, successful action on save button
yourTextView.resignFirstResponder()
}
I am trying to dismiss keyboard in text view using accessory views with done button but keyboard is not showing done button in it.Actually i have writen my code in textViewDidBeginEditing using inputAccessoryView.Here is my code.
func textViewDidBeginEditing(textView: UITextView) {
currentTextView = textView //This is to tell the current position in text view
var indexPath:NSIndexPath = NSIndexPath(forRow: 0, inSection: 2)
let cell:EventTableTableViewCell = EventTableview.cellForRowAtIndexPath(indexPath) as! EventTableTableViewCell
cell.messageTextView.autocorrectionType = UITextAutocorrectionType.No
let keyboardDoneButton = UIToolbar()
keyboardDoneButton.sizeToFit()
let item = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("doneButton"))
var toolbarButtons = [item]
//Put the buttons into the ToolBar and display the tool bar
keyboardDoneButton.setItems(toolbarButtons, animated: false)
keyboardDoneButton.userInteractionEnabled = true
//cell.messageTextView.inputAccessoryView = keyboardDoneButton
textView.inputAccessoryView = keyboardDoneButton
}
}
func doneButton()
{
UIApplication.sharedApplication().sendAction("resignFirstResponder", to:nil, from:nil, forEvent:nil)
}
_textView.returnKeyType = UIReturnKeyType.Done
Did you set the keyboard in IB?
Storyboard > Textfield > Attributes Inspector > Return Key > Done
Implement the delegate method:
- textfieldShouldReturn:
Found here: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITextFieldDelegate_Protocol/#//apple_ref/occ/intfm/UITextFieldDelegate/textFieldShouldReturn: