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
Related
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'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
I have written a functionscrollToVisible() to scroll text in UItextview because some part of text is covered by the keyboard, or the cursor isn't in visible. But UItextview can scroll the text automatically when the cursor is not in the whole view but not visible, it can still be covered by keyboard by automatically scroll.The UItextview's auto scroll can interrupt my scrollToVisible().
Thus, can I ban the UItexview to scroll automatically? Or another way to solve "keyboard cover" problem?
My scrollToVisible() function
func scrollToVisible()
{
let cursortop = self.EditArea.convert(self.EditArea.caretRect(for: (self.EditArea.selectedTextRange?.start)!).origin, to: self.view)
var cursorbottom = cursortop
cursorbottom.y += self.EditArea.caretRect(for: (self.EditArea.selectedTextRange?.start)!).height
let bottom = UIScreen.main.bounds.size.height - self.EditArea.textContainerInset.bottom
var contentOffset = self.EditArea.contentOffset
if cursortop.y <= 85
{
contentOffset.y = contentOffset.y - 85 + cursortop.y
self.EditArea.setContentOffset(contentOffset, animated: true)
}
else if cursorbottom.y >= bottom
{
contentOffset.y = contentOffset.y - bottom + cursorbottom.y
self.EditArea.setContentOffset(contentOffset, animated: true)
}
}
PS:this EditArea is the textview
I have a similar problem: when you open the keyboard, the text view is not adjusted and the cursor hides behind the keyboard (or as you say "covers" the cursor). So if I hit enter to start a new line, it also doesn't visibly auto scroll (actually it does, but it's behind the keyboard). I found a solution, which works perfectly for me on this website: https://www.hackingwithswift.com/example-code/uikit/how-to-adjust-a-uiscrollview-to-fit-the-keyboard
Solution extracted from above website using swift 4:
Subscribe for the events when the keyboard appears and disappears in your viewDidLoad() function:
// For avoiding that the text cursor disappears behind the keyboard, adjust the text for it
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: .UIKeyboardWillHide, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: .UIKeyboardWillChangeFrame, object: nil)
Adjust the textview using this function, add it anywhere in your class:
// Adjusts the textView, so that the text cursor does not disappear behind the keyboard
#objc func adjustForKeyboard(notification: Notification) {
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == Notification.Name.UIKeyboardWillHide {
textView.contentInset = UIEdgeInsets.zero
} else {
textView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
textView.scrollIndicatorInsets = textView.contentInset
let selectedRange = textView.selectedRange
textView.scrollRangeToVisible(selectedRange)
}
Here is my solution, where you need not worry to handle any textfield/textview in the app by writing just one line of code in app delegate
If you are using pods, the you can add "IQKeyboardManager" pods by just adding the following pods
pod 'IQKeyboardManagerSwift'
and add this line in didFinishLaunchingWithOptions in app delegate
IQKeyboardManager.sharedManager().enable = true
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 want to show a custom UIView above my keyboard, when my text field is becoming first responder.
However, it seems that I can show view and keyboard both at the same time?
Is there is a way to overcome it?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
phoneInputTextField.becomeFirstResponder()
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillChangeFrame , object: nil)
}
func handleKeyboardNotification(notification: NSNotification) {
guard let userInfo = notification.userInfo,
let frameValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
else { return }
let customView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: frameValue.cgRectValue.width + 20))
customView.backgroundColor = UIColor.black
phoneInputTextField.inputView = customView
}
That code shows only keyboard.
You can use inputView and inputAccessoryView both.
inputView is used to assign a some custom view in replacement of UIKeyboard , like you can use UIPickerView , UIDatePicker etc while editing the textField
inputAccessoryView is also used to assign a some custom view but without replacing a UIKeyboard , it renders above the keyboard . like you can use UIToolbar above the keyboard and many other View as per your requirements.
In your case you can use inputAccessoryView
example :
yourTxtField.inputAccessoryView = yourCustomView()