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()
}
}
}
Related
This is my view controller.
I am trying to move the textFields up while the keyboard is covering textFields of the bottom of the screen. Here is the code I've done :
override func viewDidLoad() {
super.viewDidLoad()
txtFName.delegate = self
txtLName.delegate = self
txtCompany.delegate = self
txtStreet1.delegate = self
txtStreet2.delegate = self
txtTown.delegate = self
txtPin.delegate = self
txtPhone.delegate = self
txtEmail.delegate = self
NotificationCenter.default.addObserver(
self,
selector: #selector(self.keyBoardWillChange(notification:)),
name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.keyBoardWillChange(notification:)),
name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.keyBoardWillChange(notification:)),
name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
deinit {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
print("Return Tapped")
txtFName.resignFirstResponder()
txtLName.resignFirstResponder()
txtCompany.resignFirstResponder()
txtStreet1.resignFirstResponder()
txtStreet2.resignFirstResponder()
txtTown.resignFirstResponder()
txtPin.resignFirstResponder()
txtPhone.resignFirstResponder()
txtEmail.resignFirstResponder()
view.frame.origin.y = 0
return true
}
#objc func keyBoardWillChange(notification: Notification) {
print("Keyboard will show: \(notification.name.rawValue)")
view.frame.origin.y = -250
}
Now while I am tapping on any of the textFields, the whole view is moving up. "txtFName", "txtLName".. these textFields are not being visible.
I want to move up the view only when I would tap on "txtPin", "txtPhone", "txtEmail". Rest textfields would remain in the default position even when the keyboard appears.
what the required changes are?
Generally you can use your isFirstResponder property on your text field. When true, it means that this is the text field you wish to focus on and move upwards when it starts being edited.
#objc private func keyBoardWillChange(notification: Notification) {
let possibleTextFields: [UITextField] = [txtLName, txtCompany, txtStreet1, txtStreet2, txtTown, txtPin, txtPhone, txtEmail]
let focusedTextField: UITextField? = possibleTextFields.first { $0.isFirstResponder }
...
}
Now that you have that you would still need to calculate the offset that is needed to move your view
Getting the frame of keyboard:
guard let info = notification.userInfo else { return }
guard let value: NSValue = info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let newFrame = value.cgRectValue
Computing the difference
There are tools to check frames which allow you to convert frames to different coordinate systems (views)
private func getVerticalDifference(keyboardFrame: CGRect, viewToFocus: UIView, panel: UIView) -> CGFloat {
let keyboardInPanel = panel.convert(keyboardFrame, from: nil)
let viewInPanel = panel.convert(viewToFocus.bounds, from: viewToFocus)
return viewInPanel.maxY - keyboardInPanel.minY
}
I suggest you use the view of your view controller for your panel parameter it should be something that is not being changed (Which is not the case in your current code. Avoid changing frame of the view of your view controller).
Applying the difference:
To apply the difference I suggest that you use constraints. Put all your text fields on a single "panel" view. This view may best also put into a scroll view so user may scroll through it on smaller devices. Now panel (or scroll view) can have a low priority (500 for instance) bottom constraint to view controller. Then another high priority bottom constraint set to greaterThan meaning that bottom will be fixed to "greater than X" where X can be setup later.
Now you can drag an outlet to your code from this greaterThan constraint. And then all you need to do is
bottomConstraintOutlet.constant = max(0.0, getVerticalDifference(...))
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 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()
}
Previously if one presented a keyboard on one's own app one would embed everything in a UIScrollView and adjust the contentInset to keep content from being obscured by the keyboard.
Now with split view multitasking on iOS 9 the keyboard may appear at any moment and stay visible even while the user is no longer interacting with the other app.
Question
Is there an easy way to adapt all view controllers that were not expecting the keyboard to be visible and without start embedding everything in scrollviews?
The secret is to listen to the UIKeyboardWillChangeFrame notification that is triggered whenever the keyboard is shown/hidden from your app or from another app running side by side with yours.
I created this extension to make it easy to start/stop observing those events (I call them in viewWillAppear/Disappear), and easily get the obscuredHeight that is usually used to adjust the bottom contentInset of your table/collection/scrollview.
#objc protocol KeyboardObserver
{
func startObservingKeyboard() // Call this in your controller's viewWillAppear
func stopObservingKeyboard() // Call this in your controller's viewWillDisappear
func keyboardObscuredHeight() -> CGFloat
#objc optional func adjustLayoutForKeyboardObscuredHeight(_ obscuredHeight: CGFloat, keyboardFrame: CGRect, keyboardWillAppearNotification: Notification) // Implement this in your controller and adjust your bottom inset accordingly
}
var _keyboardObscuredHeight:CGFloat = 0.0;
extension UIViewController: KeyboardObserver
{
func startObservingKeyboard()
{
NotificationCenter.default.addObserver(self, selector: #selector(observeKeyboardWillChangeFrameNotification(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
func stopObservingKeyboard()
{
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
func observeKeyboardWillChangeFrameNotification(_ notification: Notification)
{
guard let window = self.view.window else {
return
}
let animationID = "\(self) adjustLayoutForKeyboardObscuredHeight"
UIView.beginAnimations(animationID, context: nil)
UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: (notification.userInfo![UIKeyboardAnimationCurveUserInfoKey]! as AnyObject).intValue)!)
UIView.setAnimationDuration((notification.userInfo![UIKeyboardAnimationCurveUserInfoKey]! as AnyObject).doubleValue)
let keyboardFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue
_keyboardObscuredHeight = window.convert(keyboardFrame!, from: nil).intersection(window.bounds).size.height
let observer = self as KeyboardObserver
observer.adjustLayoutForKeyboardObscuredHeight!(_keyboardObscuredHeight, keyboardFrame: keyboardFrame!, keyboardWillAppearNotification: notification)
UIView.commitAnimations()
}
func keyboardObscuredHeight() -> CGFloat
{
return _keyboardObscuredHeight
}
}