Keyboard management blocking UITextView - ios

So I followed apple guidelines on moving text above the keyboard, this works fine when the textfields are on scrollview, however the scrollview contains a collection view and that collection view loads a nib with textfields populated within it, they are all assigned as the delegate and when they are pressed the didEdit/endEdit functions of the delegate do fire however the keyboard management code doesn't work as expected... here is the keyboard management code
http://creativecoefficient.net/swift/keyboard-management/
heres a link to the code am using..
func keyboardWillBeShown(sender: NSNotification) {
print("KEYBOARD SHOWN")
let info: NSDictionary = sender.userInfo!
let value: NSValue = info.valueForKey(UIKeyboardFrameBeginUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.CGRectValue().size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0,keyboardSize.height, 0.0)
print(keyboardSize.height)
ScrollView.contentInset = contentInsets
ScrollView.scrollIndicatorInsets = contentInsets
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
let activeTextFieldOrigin: CGPoint? = activeTextFieldRect?.origin
if (!CGRectContainsPoint(aRect, activeTextFieldOrigin!)) {
let dHeight = displayView.frame.height
ScrollView.scrollRectToVisible(activeTextFieldRect!, animated:true)
}
The problem with this code is that the activeTextfield works well with the textfields of the view, i get these points printed when i click on the textfield
activetextfield Frame
(0.0, 20.5, 150.0, 20.5)
but when i click on the collection view nib textfields i get these points
0.0, 0.0, 259.5, 30.0
I believe this is the reason the keyboard is blocking the textfields, the activetextfieldRect is giving the wrong coordinates to
ScrollView.scrollRectToVisible(activeTextFieldRect!, animated:true)
can someone give me some direction on how to go about fixing this?

Yes, you're right. scrollRectToVisible receive wrong coordinates, and this is why it is not work.
To achieve your goal consider using scrollToItemAtIndexPath(_:atScrollPosition:animated:). All what you need is IndexPath of cell to move. And I guess you can put that info in tag property of your textField (for instance).
Something like this:
// inside cellForItem..
cell.textField.tag = indexPath.item
// inside keyboardWillShown
let indexPath = NSIndexPath(forItem: activeTextField.tag inSection: 0) // assume that you have one section
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Top, animated: true) // or try .Center position

func keyboardWillBeShown(sender: NSNotification) {
print("KEYBOARD SHOWN")
let info: NSDictionary = sender.userInfo!
let value: NSValue = info.valueForKey(UIKeyboardFrameBeginUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.CGRectValue().size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0)
ScrollView.contentInset = contentInsets
ScrollView.scrollIndicatorInsets = contentInsets
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
var aRect: CGRect = self.ScrollView.frame
aRect.size.height -= keyboardSize.height
let activeTextFieldRect: CGRect? = activeTextField?.frame
let activeTextFieldOrigin: CGPoint? = TextfieldPoint
if (!CGRectContainsPoint(aRect, activeTextFieldOrigin!)) {
ScrollView.scrollRectToVisible(testBox, animated:true)
}
}
// Called when the UIKeyboardWillHideNotification is sent
func keyboardWillBeHidden(sender: NSNotification) {
print("KEYBOARD HIDDEN")
let contentInsets: UIEdgeInsets = UIEdgeInsetsZero
ScrollView.contentInset = contentInsets
ScrollView.scrollIndicatorInsets = contentInsets
}
func textFieldDidBeginEditing(textField: UITextField) {
let point: CGPoint = textField.convertPoint(CGPointZero, toView: self.ScrollView);
TextfieldPoint = point
testBox = CGRectMake(point.x, point.y, textField.frame.width, 100 );
//let indexPath:NSIndexPath? = collview1.indexPathForItemAtPoint(point)
print("TEXTFIELD EDIT")
activeTextField = textField
print(activeTextField)
ScrollView.scrollEnabled = true
}
func textFieldDidEndEditing(textField: UITextField) {
print("TEXTFIELD STOP EDIT")
activeTextField = nil
ScrollView.scrollEnabled = false
}
it works just really messy i guess, i got the point where textfield was, made CGRect out of it, if the keyboard overlaps the text, the view adjusts, tested on iPhone 4s to 6s plus, works fine

Related

How to calculate proper keyboard contentInset for UIScrollView inside of a modally presented form sheet UIViewController

I am having an issue in which relying on convertRect to properly report a y position to use to calculate a contentInset is not working on iOS 12. This approach used to work on earlier iOS versions:
#objc func keyboardVisibilityChanged(notification: Notification) {
guard let userInfo = notification.userInfo else {
assertionFailure()
return
}
let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let keyboardViewEndFrame = scrollView.convert(keyboardScreenEndFrame, from: view.window!)
if notification.name == UIResponder.keyboardWillHideNotification {
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
} else {
let insets = UIEdgeInsets(top: 0, left: 0, bottom: (keyboardViewEndFrame.origin.y - keyboardViewEndFrame.size.height) , right: 0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
}
}
However, this code, while achieving extremely close visual results, is not exact and also breaks on iPhone, where the modal is presented fullscreen.
Apple states in their documentation:
Note: The rectangle contained in the UIKeyboardFrameBeginUserInfoKey
and UIKeyboardFrameEndUserInfoKey properties of the userInfo
dictionary should be used only for the size information it contains.
Do not use the origin of the rectangle (which is always {0.0, 0.0}) in
rectangle-intersection operations. Because the keyboard is animated
into position, the actual bounding rectangle of the keyboard changes
over time.
So I came up with the following solutions that seems to work well on iOS 13, 12 and 11, including safe areas, modal form sheets, and hardware keyboards):
// MARK: - Keyboard Notifications
#objc func keyboardVisibilityChanged(notification: Notification) {
if notification.name == UIResponder.keyboardWillHideNotification {
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
} else {
guard let userInfo = notification.userInfo,
let value = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
let window = view.window else {
assertionFailure()
return
}
let keyboardEndFrameInWindowCoordinates = value.cgRectValue
let viewFrameInWindowCoordinates = window.convert(scrollView.frame,
from: scrollView.superview)
let contentInsetBottom: CGFloat
// If the keyboard is below us, no need to do anything.
// This can happen when a hardware keyboard is attached to a modal form sheet on iPad
if keyboardEndFrameInWindowCoordinates.origin.y >= viewFrameInWindowCoordinates.maxY {
contentInsetBottom = 0
} else {
let bottomEdgeOfViewInWindowBottomCoordinates = window.frame.maxY - viewFrameInWindowCoordinates.maxY
contentInsetBottom = keyboardEndFrameInWindowCoordinates.height - bottomEdgeOfViewInWindowBottomCoordinates - view.safeAreaInsets.bottom
}
let insets = UIEdgeInsets(top: 0,
left: 0,
bottom: contentInsetBottom,
right: 0)
scrollView.scrollIndicatorInsets = insets
}
}

wrong order of execution of settings in keyboardWillShow

My ViewController view contains scrollView with bounds of it's superview and there is a tableView at the bottom of the scrollView as it's subview. TableView height constraint is set to it's content size height and above it there is textfield. So when the user touches textField keyboard appears. And in order to the textField could be visible I want to scroll the scrollView up, but usually there is not enough space so I want to increase the tableView height. And the problem is that it behaves like first the scrollView is scrolled and later the tableViewConstraint is set so it never scrolls enough when there is no space. Could I 'dispatch'
tableViewConstraint.constant = keyboardHeight in some way that it will be executed first on UI.
override func viewDidLoad() {
tableViewConstraint.constant = TableView.contentSize.height
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:NSNotification.Name.UIKeyboardWillShow, object: nil)
...
}
#objc func keyboardWillShow(notification:NSNotification){
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
tableViewConstraint.constant = keyboardHeight
let boundsOfOffsetView = scrollView.bounds.offsetBy(dx: CGFloat(0), dy: keyboardHeight)
scrollView.scrollRectToVisible(boundsOfOffsetView, animated: true)
}
}
You can do your layout changes in an animate(withDuration:animations:completion:) block and call layoutIfNeeded() to force the changes to occur at the time of your choosing. I would try something like this to start:
// Within your if statement…
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
UIView.animate(withDuration: 1.0/3.0) {
tableViewConstraint.constant = keyboardHeight
scrollView.layoutIfNeeded()
}
let boundsOfOffsetView = scrollView.bounds.offsetBy(dx: CGFloat(0), dy: keyboardHeight)
scrollView.scrollRectToVisible(boundsOfOffsetView, animated: true)

Want ability to manually scroll to pull textfield above keyboard

The problem is apparently when a user sees that the keyboard is blocking textfields that they want to type in, instead of hitting return, or closing the keyboard and tapping on the next field, they try to pan to the next field. Since my content matches the size of the iPad, the scrollview doesn't automatically scroll when the user tries to pan. Honestly, I don't want it to scroll unless the keyboard is on-screen anyway.
However, enabling scrolling on the scrollview doesn't solve the problem; it still won't respond to panning even in that case. Neither does making the viewcontroller the delegate of the scrollview and overriding the function scrollViewDidScroll. How do I get the scrollview to enable panning, particularly only when the keyboard is enabled?
Since a solution has been posted that doesn't quite work, I think I will post my keyboardWillBeShown and keyboardWillBeHidden code:
func keyboardWillBeShown(_ sender: Notification)
{
self.myScrollView.isScrollEnabled = true
let info: NSDictionary = (sender as NSNotification).userInfo! as NSDictionary
let value: NSValue = info.value(forKey: UIKeyboardFrameBeginUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.cgRectValue.size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0)
self.myScrollView.contentInset = contentInsets
self.myScrollView.scrollIndicatorInsets = contentInsets
self.myScrollView.scrollRectToVisible(self.myScrollView.frame, animated: true)
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
let activeTextFieldRect: CGRect? = activeField?.frame
let activeTextFieldOrigin: CGPoint? = activeTextFieldRect?.origin
if activeTextFieldOrigin != nil
{
if (!aRect.contains(activeTextFieldOrigin!))
{
let scrollpoint : CGPoint = CGPoint(x: 0.0, y: activeField!.frame.origin.y - keyboardSize.height)
self.myScrollView.setContentOffset(scrollpoint, animated: true)//.scrollRectToVisible((activeField?.frame)!, animated: true)
}
}
}
func keyboardWillBeHidden(_ sender: Notification)
{
myScrollView.isScrollEnabled = false
let contentInsets: UIEdgeInsets = UIEdgeInsets.zero
myScrollView.contentInset = contentInsets
myScrollView.scrollIndicatorInsets = contentInsets
}
Try this,
Declare variables to be used
var originalViewCGRect: CGRect?
var originalOffset: CGPoint!
in viewDidLoad add keyboard observers
NotificationCenter.default.addObserver(self
, selector: #selector(keyboardWillAppear(_:))
, name: NSNotification.Name.UIKeyboardWillShow
, object: nil)
NotificationCenter.default.addObserver(self
, selector: #selector(keyboardWillDisappear(_:))
, name: NSNotification.Name.UIKeyboardWillHide
, object: nil)
And finally, add these functions
func keyboardWillAppear(_ notification: Foundation.Notification){
self.scrollView.scrollRectToVisible(self.scrollView.frame, animated: true)
let keyboardSize:CGSize = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue.size
let insets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
self.scrollView.contentInset = insets
}
func keyboardWillDisappear(_ notification: Foundation.Notification){
let beginFrame = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let endFrame = ((notification as NSNotification).userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let delta = (endFrame.origin.y - beginFrame.origin.y)
self.view.frame = self.originalViewCGRect!
self.scrollView.setContentOffset(CGPoint(x: 0, y: max(self.scrollView.contentOffset.y - delta, 0)), animated: true)
}
In summary, this will adjust the content inset of the scrollview when the keyboard is shown and/or hidden.
Hope this helps.
EDIT
Fixed adjustments
I figured out how to get what I want. Set myScrollView.contentSize to be as big as the screen plus the keyboard when the keyboard is shown, and reduce the size back to what it was when keyboard is hidden. Then make the view controller a UIScrollViewDelegate, set myScrollView.delegate = self, and implement scrollViewDidScroll so that if a textfield is editing, and that textfield isn't blank, then change which text field is the first responder. Piece of cake once you realize the trick of setting .contentSize!

Scroll UIScrollView on selection UITextField, can't get the right sizes

What I've got is a UIScrollView with a containerview inside it. This has a UITableViewcontroller embedded that contains static cells. I'd like to scroll the UIScrollView when I'm selecting a UITextField inside the static cells. I tried it two ways, 1. calculating the position, 2. calculating the cell index. Both ways I keep getting the same values when I'm selection an any UIField. Can someone help me to get this fixed?
I used this question:
How do I scroll the UIScrollView when the keyboard appears?
func keyboardWasShown(notification : NSNotification){
var info = notification.userInfo
// var kbSize = info(UIKeyboa
if let userInfo = notification.userInfo {
if let kbSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height, right: 0)
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
//first approach
println("---- approach 1 ------")
var activeField = self.container?.groupField.superview?.superview
println(activeField!.frame.origin)
//prints (110.0, 5.0)
//second approach
println("---- approach 2 ------")
var cell = self.container?.groupField.superview?.superview as! UITableViewCell
var table = self.container?.view as! UITableView
var index = table.indexPathForCell(cell)
println("index: \(index?.row)")
//prints always 4
var aRect = self.view.frame
aRect.size.height -= kbSize.height
// println("aRect: \(aRect)")
if (!CGRectContainsPoint(aRect, activeField!.frame.origin) ) {
println("ActiveField: \(activeField!.frame.origin.y)")
var scrollPoint = CGPointMake(0.0, activeField!.frame.origin.y-kbSize.height)
scrollView.setContentOffset(scrollPoint, animated:true)
}
}
}
}
It was a stupid mistake, I selected a default field as activefield
I replaced:
var activeField = self.container?.groupField.superview?.superview
to:
var activeField = self.container?.activeField.superview?.superview

UITableView with Footerview ( emdedded text view) issues

I am building this simple example where there is a UITableView and a footerview associated with the uitableview. To avoid overlapping the keyboard I added the keyboard show an hide notifications and it works fine... Initially when the app load the uitextview in the footerview is placed correctly at the bottom of the UITableView.. But once type in some text and dismiss the keyboard the footerview vanishes and goes far below the viewable region. Any idea why this would happen?
On application load
On editing
After the keyboard is dismissed
At this point the uitextfield vanishes and is hidden below the tab bar. Here is the code for keyboard show and dismiss events.
func keyBoardWillShow(notification: NSNotification) {
println("Keyboard will show notification")
var info:NSDictionary = notification.userInfo!
var keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as NSValue).CGRectValue()
var keyboardHeight:CGFloat = keyboardSize.height
var animationDuration:CGFloat = info[UIKeyboardAnimationDurationUserInfoKey] as CGFloat
var contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, (keyboardSize.height), 0.0);
self.tableView.contentInset = contentInsets;
self.tableView.scrollIndicatorInsets = contentInsets;
}
func keyBoardWillHide(notification: NSNotification) {
self.tableView.contentInset = UIEdgeInsetsZero
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero
}

Resources