On my loginViewController there is a textField and the button for searching. I want to make sure that when entering text in the textField my interface is not overlapped by the keyboard, but scrolled to the size of this keyboard and there was access to all the elements. For this I wrote this code:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(kbDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(kbDidHide), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
}
#objc func kbDidShow(notification: Notification) {
guard let userInfo = notification.userInfo else { return }
let kdFrameSize = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
(self.view as! UIScrollView).contentSize = CGSize(width: self.view.bounds.size.width, height: self.view.bounds.size.height + kdFrameSize.height)
(self.view as! UIScrollView).scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: kdFrameSize.height, right: 0)
}
#objc func kbDidHide() {
(self.view as! UIScrollView).contentSize = CGSize(width: self.view.bounds.size.width, height: self.view.bounds.size.height)
}
When I run the application I have a side view of the scrollbar, but the interface itself does not scroll.
What could be the problem?
Try using IQKeyboardManager this will do your job automatically
Link - https://github.com/hackiftekhar/IQKeyboardManager
You just need to add this line in AppDelegate
All of the textfield will be adjusted automatically, in every view.
IQKeyboardManager.sharedManager().enable = true
Change the content offset rather than setting the contentSize.
#objc func kbDidShow(notification: Notification) {
guard let userInfo = notification.userInfo else { return }
let kdFrameSize = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
(self.view as! UIScrollView).contentOffset.y += kdFrameSize.size.height
(self.view as! UIScrollView).scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: kdFrameSize.height, right: 0)
}
Related
I have tried literally every solution on the internet to get this answer, and none of them work without bugs. I have a stackview embedded in a scrollview, and I want the signInButton to be right above the keyboard.
Here is my current attempted solution:
#objc func keyboardWillShow(notification: NSNotification) {
guard let userInfo: NSDictionary = notification.userInfo as NSDictionary?,
let keyboardInfo = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
return
}
let keyboardSize = keyboardInfo.cgRectValue.size
scrollView.isScrollEnabled = false
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var rect = self.view.frame
rect.size.height -= keyboardSize.height
var frame = self.signInButton.frame
frame.size.height += 50
self.scrollView.scrollRectToVisible(frame, animated: true)
}
#objc func keyboardWillHide(notification: NSNotification) {
let contentInset:UIEdgeInsets = UIEdgeInsets.zero
scrollView.contentInset = contentInset
scrollView.isScrollEnabled = false
}
But this does not come close to working on all screen sizes. While the button always appears sometimes it appears way above the keyboard, and other times slightly above. And when the keyboard disappears on some screens it doesn't work either. I'm curious if anyone knowledgeable can give a robust answer that many can use for this ubiquitous problem. Swift 5 please.
I know I can use a tableview, but I find it incredibly sad that iOS does not have a clear, alternative working solution without embedding everything in a tableview.
Thanks!
I have had similar problems and i fixed it by using this. (probably not the best way, but it works)
Add these in viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboarddismissedlogin(notification:)), name: Notification.Name("keyboarddismissedlogin"), object: nil)
Define those 3 functions
// MARK: - KeyboardON
#objc func keyboardWillShow(notification:NSNotification) {
let userInfo:NSDictionary = notification.userInfo! as NSDictionary
let keyboardFrame:NSValue = userInfo.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
TableBottomConstraint.constant = TableBottomConstraint.constant + keyboardHeight
extraFunctions.keyboardHeight = Int(keyboardHeight)
}
// MARK: - keyboardOFFheight
#objc func keyboardWillHide(notification:NSNotification) {
let userInfo:NSDictionary = notification.userInfo! as NSDictionary
let keyboardFrame:NSValue = userInfo.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
if TableBottomConstraint.constant > keyboardHeight
{
TableBottomConstraint.constant = TableBottomConstraint.constant - keyboardHeight
}
}
// MARK: - Keyboard Dismiss
#objc func keyboarddismissedlogin(notification: Notification) {
TableBottomConstraint.constant = TableBottomConstraint.constant - CGFloat(extraFunctions.keyboardHeight)
}
Here you have to make an outlet of your table or scroll view's bottom constraint.
extraFunctions.keyboardHeight
is simply a struct that i created to store the keyboard height. (you can simply create a variable instead of struct - > variable. I did it just for the sake of naming it)
This will simply move your view UP from the bottom when keyboard appears and changes back to original position when keyboard disappears.
All this works , assuming that your BUTTON is at the bottom of your view.
I donot fully get your question. In my case, I put the constraint to the bottom of the button, and set its constant when the keyboard appear/ dissappaear.
#IBOutlet weak var edittingBottomConstraint: NSLayoutConstraint!
#objc func showKeyboard(notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyBoardHeight = keyboardRectangle.size.height
edittingBottomConstraint.constant = -keyBoardHeight
}
}
I am using swift and having issues with TouchUpInside: if I'm using UIKeyboardWillChangeFrame or UIKeyboardWillShow/UIKeyboardWillHide, & the keyboard is showing, & the button I'm trying to press is behind the keyboard when keyboard is shown initially. (If I scroll down to the button till visible and press, no touchUpInside called).
TouchDown seems to work consistently whether the keyboard is showing or not, but TouchUpInside is not called. If the button is above the top of the keyboard when the keyboard is initially shown, TouchUpInside works. I'm using keyboardNotification to set the height of a view below my scrollView in order to raise up my scrollView when keyboard is showing. From what I can see it's only usually when the button is the last element in the scrollView (and therefore likely to be behind the keyboard when keyboard shown).
#IBOutlet var keyboardHeightLayoutConstraint: NSLayoutConstraint?
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var saveButton: UIButton!
#IBAction func saveTouchUpInside(_ sender: UIButton) {
print("touchupinside = does not work")
}
#objc func saveTouchDown(notification:NSNotification){
print("touchdown = works")
}
viewWillAppear:
textField.delegate = self
NotificationCenter.default.addObserver(self,selector:#selector(self.keyboardNotification(notification:)),name:
NSNotification.Name.UIKeyboardWillChangeFrame,object: nil)
self.saveButton.addTarget(self, action:#selector(ViewController.saveTouchDown(notification:)), for: .touchDown)
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func keyboardNotification(notification: NSNotification) {
if let userInfo = notification.userInfo {
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let endFrameY = endFrame?.origin.y ?? 0
let duration:TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
if endFrameY >= UIScreen.main.bounds.size.height {
self.keyboardHeightLayoutConstraint?.constant = 0.0
} else {
self.keyboardHeightLayoutConstraint?.constant = endFrame?.size.height ?? 0.0
}
UIView.animate(withDuration: duration, delay: TimeInterval(0),options: animationCurve, animations: { self.view.layoutIfNeeded() }, completion: nil)
}
}
I would like to dismiss the keyboard and call saveTouchUpInside at the same time, without using TouchDown.
I abstract the keyboard interaction as a separate class so that my controllers do not get bloated(also follows separation of concerns). Here is the keyboard manager class that I use.
import UIKit
/**
* To adjust the scroll view associated with the displayed view to accommodate
* the display of keyboard so that the view gets adjusted accordingly without getting hidden
*/
class KeyboardManager {
private var scrollView: UIScrollView
/**
* -parameter scrollView: ScrollView that need to be adjusted so that it does not get clipped by the presence of the keyboard
*/
init(scrollView: UIScrollView) {
self.scrollView = scrollView
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self,
selector: #selector(adjustForKeyboard),
name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self,
selector: #selector(adjustForKeyboard),
name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
}
/**
* Indicates that the on-screen keyboard is about to be presented.
* -parameter notification: Contains animation and frame details on the keyboard
*
*/
#objc func adjustForKeyboard(notification: Notification) {
guard let containedView = scrollView.superview else { return }
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let keyboardViewEndFrame = containedView.convert(keyboardScreenEndFrame, to: containedView.window)
let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber
let rawAnimationCurveValue = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).uintValue
UIView.animate(withDuration: TimeInterval(truncating: duration),
delay: 0,
options: [UIView.AnimationOptions(rawValue: rawAnimationCurveValue)],
animations: {
if notification.name == UIResponder.keyboardWillHideNotification {
self.scrollView.contentInset = UIEdgeInsets.zero
} else {
self.scrollView.contentInset = UIEdgeInsets(top: 0,
left: 0,
bottom: keyboardViewEndFrame.height,
right: 0)
}
self.scrollView.scrollIndicatorInsets = self.scrollView.contentInset
},
completion: nil)
}
deinit {
let notificationCenter = NotificationCenter.default
notificationCenter.removeObserver(self)
}
}
Its usage is like this
create a reference to the keyboard manager
private var keyboardManager: KeyboardManager!
and assign the keyboard manager class like below in viewDidLoad where self.scrollView is the scrollView that you are working with
self.keyboardManager = KeyboardManager(scrollView: self.scrollView)
This should take care of the issue. If that does not work, probably a sample project might help to take a deep dive into that.
so I've set up keyboard observers using KeyboardDidShow so that I can shift the view up only when the keyboard is shown. However, KeyboardDidShow runs at the launch of every view and also at random times. I've tried monitoring the keyboard frames and only shifting the view if the frame changes, but every so often the view is still shifted even without the keyboard being shown. Usually, it happens whenever the view is first launched, so I tried adding a delay but it's not very dependable.
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
}
#objc func keyboardDidShow(_ notification: Notification) {
let userInfo = notification.userInfo!
let beginFrameValue = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)!
let beginFrame = beginFrameValue.cgRectValue
let endFrameValue = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)!
let endFrame = endFrameValue.cgRectValue
if beginFrame.equalTo(endFrame) {
return
} else {
let indexPath = IndexPath(item: 0, section: 0)
if UIScreen.main.bounds.height == 812 {
collectionView?.contentInset = UIEdgeInsets(top: 318 + view.safeAreaInsets.bottom, left: 0, bottom: 73, right: 0)
}
collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
}
}
The problem is that you have configured the wrong notification. Do not use UIKeyboardDidShow. Use UIKeyboardWillShow, and examine the old frame, the new frame, and whether the new frame will cover your view.
Arriving at a robust implementation is not trivial, but it is certainly a well established previously solved problem that has been explained here many times.
For UIViewController instances you need observer these notifications only when view is visible and not subscribe to notification multiple times. Best way to do so is:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardWillChangeFrame(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
And here is working code from my project to handle keayboard frame
#objc private func onKeyboardWillChangeFrame(_ notification: NSNotification) {
// extract values
if let userInfo = notification.userInfo,
let keyboardFrameEnd = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let animationCurveInt = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.intValue {
/*
СУКАБЛЯТЬ
With upgrate to Swift 4.2 UIView.AnimationCurve(rawValue: 7) returns actual instance of
UIView.AnimationCurve which crashes on access, mod by 4 limits value to max
*/
let animationCurve = UIView.AnimationCurve(rawValue: animationCurveInt % 4) ?? .easeIn
// View chanages
let topPoint = self.view.convert(keyboardFrameEnd.origin, from: self.view.window)
let height = self.view.bounds.size.height - topPoint.y
... update your constraints or manually update vars that affect layoutSubviews()...
let animationDuration: TimeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
var animationOptions: UIView.AnimationOptions = []
switch animationCurve {
case .easeInOut: animationOptions = .curveEaseInOut
case .easeIn: animationOptions = .curveEaseIn
case .easeOut: animationOptions = .curveEaseOut
case .linear: animationOptions = .curveLinear
}
// run animation
UIView.animate(withDuration: animationDuration, delay: 0, options: animationOptions, animations: {
self.view.layoutIfNeeded()
})
}
}
it's possible to add GestureRecognizer or set on touch to PDFAnnotation
func setDocumentAnnotation() {
let anotation:PDFAnnotation = PDFAnnotation(bounds: CGRect(x: pointX, y: pointY, width: pointW, height: pointH), forType: .highlight, withProperties: nil)
anotation.color = .yellow
anotation.endLineStyle = .circle
guard let page = pdfView.currentPage else {return}
page.addAnnotation(anotation)
}
#objc func annotationTapping(_ sender: UITapGestureRecognizer){
print("------- annotationTapping ------")
}
Hopefully this method will work for you..
NotificationCenter.default.addObserver(self, selector: #selector (titleTapped), name:Notification.Name.PDFViewAnnotationHit , object: nil)
#objc func titleTapped() {
-Provide the action you want---
}
It's not possible to add the gesture recognizer to the annotation itself. However you can add it to the PDFView and then check if the user tapped on an annotation. Here's a simple snippet:
let tappedPage = pdfView.page(for: tapLocation, nearest: false)
if let page = tappedPage {
let tapPageLocation = pdfView.convert(tapLocation, to: page)
let annotation = page.annotation(at: tapPageLocation)
}
I hope this helps.
I have done keyboard appearing below textfield using
on View did Load adding a observer()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(Gold_Loan_First_ViewController.keyboardDidShow(_:)), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(Gold_Loan_First_ViewController.keyboardWillBeHidden(_:)), name: UIKeyboardWillHideNotification, object: nil)
And then updating the frame
weak var activeField: UITextField?
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = nil
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField==txtOTP {
txtOTP.errorMessage=""
}
return true
}
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func keyboardDidShow(notification: NSNotification)
{
if let activeField = self.activeField,
let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect = self.view.frame
aRect.size.height -= keyboardSize.size.height
if (!CGRectContainsPoint(aRect, activeField.frame.origin)) {
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification)
{
let contentInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
}
But how do I do it for a textView. I tried the same code with didBeginEditing of textView with no positive effect
One of the easy and no code of line solution is to use the following pods in your app.
IQKeyboardManger
Later you need to just import that in App Delegate and add this two lines of code in didfinishLaunching method:
IQKeyboardManager.sharedManager().enable = true
Your problem will be solved for whole app.
For Swift 5:
IQKeyboardManager.shared.enable = true
I have gone through such a situation. For this first I added extension in my UiViewController :
extension CCViewController : UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
// write code as per your requirements
}
func textViewDidEndEditing(_ textView: UITextView) {
// write code as per your requirements
}
func textViewDidChange(_ textView: UITextView) {
self.p_adjustMessageFieldFrameForTextView(textView)
}
}
// Defining the p_adjustMessageFieldFrameForTextView method
fileprivate func p_adjustMessageFieldFrameForTextView(_ textView : UITextView) {
var finalheight : CGFloat = textView.contentSize.height + 10
var kMaxMessageFieldHeight : CGFloat = 50
if (finalheight > kMaxMessageFieldHeight) {
finalheight = kMaxMessageFieldHeight;
} else if (finalheight < kCommentTextViewHeight){
finalheight = kCommentTextViewHeight;
}
scrollView!.view.frame = CGRect(x: 0, y: kNavBarHeight + statuBarHeight, width: SCREEN_WIDTH,height: SCREEN_HEIGHT - finalheight - keyboardRect!.size.height - kNavBarHeight - statuBarHeight)
// It is there for understanding that we have to calculate the exact frame of scroll view
commentTextView!.frame = CGRect(x: 0, y: bottomOfView(scrollView!.view), width: SCREEN_WIDTH, height: finalheight)
self.p_setContentOffsetWhenKeyboardIsVisible()
}
// Defining the p_setContentOffsetWhenKeyboardIsVisible method
fileprivate func p_setContentOffsetWhenKeyboardIsVisible() {
// here you can set the offset of your scroll view
}
Also there is a method named bottomView() :
func bottomOfView(_ view : UIView) -> CGFloat {
return view.frame.origin.y + view.frame.size.height
}
You can simply use TPKAScrollViewController.h & TPKAScrollViewController.m files by using Bridging Header.
While dragging these objective-C file to your swift project, it will automatically ask for Create Bridging. Create Bridging Header and Import #import "TPKAScrollViewController.h" to YourApp-Bridging-Header.h file.
After that, simply select your scrollView in XIB and change its class to TPKeyboardAvoidingScrollView as showing below.
Salman Ghumsani is Right.
Please change UIKeyboardFrameBeginUserInfoKey to UIKeyboardFrameEndUserInfoKey.
Apple Debeloper Document: Keyboard Notification User Info Keys
UIKeyboardFrameEndUserInfoKey
The key for an NSValue object containing a CGRect that identifies the ending frame rectangle of the keyboard in screen coordinates. The frame rectangle reflects the current orientation of the device.
UIKeyboardFrameBeginUserInfoKey
The key for an
NSValue
object containing a
CGRect
that identifies the starting frame rectangle of the keyboard in screen coordinates. The frame rectangle reflects the current orientation of the device.
Conclusion
So, if u use UIKeyboardFrameBeginUserInfoKey u might get the keyboard height to 0. Causing the system thought UITextView was not covered.
Find the correct height of keyboard and assign it to the bottom constraint of the textView or reduce the y position of textView.
Like:
Step-1:
make a property keyboardHeight in you viewController.
var keyboardHeight: CGFloat = 0.0
Step-2: Make #IBOutlet of bottom constraint of the textView.
#IBOutlet weak var textViewBottomConstraint: NSLayoutConstraint!
Step-3:
fileprivate func addKeyboardNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
fileprivate func removeKeyboardNotification() {
IQKeyboardManager.shared().isEnabled = true
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Copy & paste these functions to your view controllers, and call self.addKeyboardNotification() in viewDidLoad() and of you viewController
Step-4:
deinit {
self.removeKeyboardNotification()
}
also add this code to your viewController.
Step-5:
func keyboardWillShow(_ notification: Notification) {
if let keboardFrame = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue, self.keyboardHeight <= 0.0 {
self.keyboardHeight = keboardFrame.height + 45.0 //(Add 45 if your keyboard have toolBar if not then remove it)
}
UIView.animate(withDuration: 0.3, animations: {
self.textViewBottomConstraint.constant = self.keyboardHeight
}, completion: { (success) in
})
}
func keyboardWillHide(_ notification: Notification) {
UIView.animate(withDuration: 0.3, animations: {
self.textViewBottomConstraint.constant = 0.0
}, completion: { (success) in
})
}
That's it to manage the textView with keyboard.
If you don't want to use textViewBottomConstraint the you can work with the y position of the textView.