UITableView Scrolling/Inset Issue - ios

I have a UITableView with multiple textfields. When I click on a textfield, I setContentOffset to bring the textfield to the top of the page, but I do not have enough inset to do so when the keyboard appears. So I added inset to the bottom of the view. This allows me to scroll all the way down but now when I select a field it scrolls down too far, further than the setContentOffset that I set. I've tried different content inset values, but none work. Is there a way to fix this content inset issue?
func adjustInsetForKeyboardShow(_ show: Bool, notification: Notification) {
if !keyboardAdjusted || !show {
guard let value = (notification as NSNotification).userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardFrame = value.cgRectValue
var adjustmentHeight = (keyboardFrame.height + 100) * (show ? 1 : -1)
adjustmentHeight = (adjustmentHeight < 0.0) ? 0.0 : adjustmentHeight
self.editPaymentTableView.contentInset.bottom = adjustmentHeight
self.editPaymentTableView.scrollIndicatorInsets.bottom = adjustmentHeight
}
keyboardAdjusted = show
}

I've found this on SO some time ago, but can't find the link to it anymore, here is the code that very neatly handles the keyboard and the insets of the tableview. Nice thing is it's for showing and hiding. No need to implement other keyboard notifications:
public func keyboardWillChangeFrame(notification: NSNotification) {
guard
let userInfo = notification.userInfo,
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let duration: TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue,
let animationCurveRawNSNumber = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
else {
return
}
let animationCurveRaw = animationCurveRawNSNumber.uintValue
let animationCurve: UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
let offsetY: CGFloat
if endFrame.origin.y >= UIScreen.main.bounds.size.height {
offsetY = 0.0
} else {
offsetY = endFrame.size.height
}
let contentInsets = UIEdgeInsetsMake(0, 0, offsetY, 0)
UIView.animate(withDuration: duration, delay: TimeInterval(0), options: animationCurve, animations: {
self.tableView.contentInset = contentInsets
self.tableView.scrollIndicatorInsets = contentInsets
}, completion: nil)
}
In terms of your solution, you can replace these two lines:
var adjustmentHeight = (keyboardFrame.height + 100) * (show ? 1 : -1)
adjustmentHeight = (adjustmentHeight < 0.0) ? 0.0 : adjustmentHeight
with:
var adjustmentHeight = keyboardFrame.height * (show ? 1 : 0)
And to be sure that you're also setting top inset to 0 do:
let contentInsets = UIEdgeInsetsMake(0, 0, adjustmentHeight, 0)
self.editPaymentTableView.contentInset = contentInsets

Related

UIScrollView changes contentOffset when I update contentInset

UIScrollView is pinned to controller's view edges.
On UIKeyboardWillShow I increase contentInsets bottom value by adding keyboard height. When animation is finished content is scrolled a little in upper direction.
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0.3
self.formContainer.contentInset = UIEdgeInsets(top: self.rowPadding, left: 0, bottom: keyboardSize.height + self.navigator.frame.height + 10, right: 0)
self.navigatorBottom.constant = -keyboardSize.height
UIView.animate(withDuration: duration) {
self.view.layoutIfNeeded()
}
}
Is there anyway to disable content scrolling here?
Save contentOffset of UIScrollView before starting animation and set saved value to contentOffset in animation completionHandler
Looks like this:
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0.3
let currentContentOffset = self.formContainer.contentOffset.y //assume your scrollView scrolls vertically
self.formContainer.contentInset = UIEdgeInsets(top: self.rowPadding, left: 0, bottom: keyboardSize.height + self.navigator.frame.height + 10, right: 0)
self.navigatorBottom.constant = -keyboardSize.height
UIView.animate(withDuration: duration, animations: {
self.view.layoutIfNeeded()
}) { _ in
self.formContainer.contentOffset.y = currentContentOffset
}
}

UIKeyboard Notification.userInfo Key bug in Xcode 9

#objc func keyboardWasShown(_ notification:NSNotification) {
var userinfo = notification.userInfo!
let kbSize:NSValue = userinfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue
let kbRectSize = kbSize.cgRectValue
let edgeInsects = UIEdgeInsetsMake(0, 0,kbRectSize.height + 10, 0)
self.scrollView.contentInset = edgeInsects
self.scrollView.scrollIndicatorInsets = edgeInsects
// active text field
var aRect:CGRect = self.view.frame
aRect.size.height -= kbRectSize.height
if(!aRect.contains(activeField.frame.origin)){
scrollView.isScrollEnabled = true
scrollView.scrollRectToVisible(activeField.frame, animated: true)
aRect = CGRect.zero
}
}
The scrollview will scroll for first time as intended and then becomes unresponsive.
The code was working fine until Xcode 8.3 without any issues.
please confirm whether it's a bug or not and how to circumvent it.Thanks in advance.
I too got the same issue and by doing some changes it is working fine. In your scenario try below code and see if it works:
#objc func keyboardWasShown(_ notification:NSNotification) {
var keyboardHeight: CGFloat = 260
var userinfo = notification.userInfo!
let keyboardSize = (userinfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
if keyboardSize!.height > 10.0{
keyboardHeight = keyboardSize!.height
}
let edgeInsects = UIEdgeInsetsMake(0, 0, keyboardHeight + 10, 0)
self.scrollView.contentInset = edgeInsects
self.scrollView.scrollIndicatorInsets = edgeInsects
// active text field
var aRect:CGRect = self.view.frame
aRect.size.height -= kbRectSize.height
if(!aRect.contains(activeField.frame.origin)){
scrollView.isScrollEnabled = true
scrollView.scrollRectToVisible(activeField.frame, animated: true)
aRect = CGRect.zero
}
}
I have used below code in my project for sign up screen and it is working perfectly fine:
func keyboardWasShown(notification: NSNotification){
//Need to calculate keyboard exact size due to Apple suggestions
var keyboardHeight: CGFloat = 260
self.scrollView.isScrollEnabled = true
var info = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
if keyboardSize!.height > 10.0{
keyboardHeight = keyboardSize!.height
}
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardHeight, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardHeight
if let activeField = self.activeField {
if (!aRect.contains(activeField.frame.origin)){
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification){
//Once keyboard disappears, restore original positions
self.scrollView.contentInset = UIEdgeInsets.zero
self.scrollView.scrollIndicatorInsets = UIEdgeInsets.zero
self.scrollView.isScrollEnabled = false
}
Just try and debug with some more changes according to your requirements. Hope it works.
// ==== solution =====//
// ==== USE UIKeyboardFrameEndUserInfoKey ===
// as UIKeyboardFrameBeginUserInfoKey return height as 0 for some
//reason on second call onwards , some weird bug indeed.
var userinfo = notification.userInfo!
// === bug fix =====
let kbSize:NSValue = userinfo[UIKeyboardFrameEndUserInfoKey] as! NSValue
let kbRectSize = kbSize.cgRectValue
let edgeInsects = UIEdgeInsetsMake(0, 0,kbRectSize.height + 10, 0)
self.scrollView.contentInset = edgeInsects
self.scrollView.scrollIndicatorInsets = edgeInsects
// active text field
var aRect:CGRect = self.view.frame
aRect.size.height -= kbRectSize.height
if(!aRect.contains(activeField.frame.origin)){
scrollView.isScrollEnabled = true
scrollView.scrollRectToVisible(activeField.frame, animated: true)
aRect = CGRect.zero
}
Solution Found , in Xcode 9, there seems to be some change in key - value pairs of Keyboard notification userInfo dictionary.
Use UIKeyboardFrameEndUserInfoKey instead of UIKeyboardFrameBeginUserInfoKey,
As Value for UIKeyboardFrameBeginUserInfoKey for some reason does not return the proper size of the keyboard on second call onwards.

Move scrollview to original state

I want to move up the view when the keyboard appears and move the view back when the keyboard disappears. I use an scrollview for this.
I have this almost working. The bug that I have is this:
You trigger the keyboard to show
You trigger the keyboard to hide
Things are going great so far.
You trigger the keyboard to show
You trigger the keyboard to hide <-- This doesn't work anymore, but the first time it works perfectly.
The code when the keyboard shows up (this works overtime):
func keyboardWasShown(notification: NSNotification) {
var userInfo = notification.userInfo!
let keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let buttonOrigin: CGPoint = btn.frame.origin
let buttonHeight: CGFloat = btn.frame.size.height
var visibleRect: CGRect = view.frame
visibleRect.size.height -= keyboardFrame.size.height
if !visibleRect.contains(buttonOrigin) {
let scrollPoint = CGPoint(x: CGFloat(0.0), y: CGFloat(buttonOrigin.y - visibleRect.size.height + (buttonHeight + 8)))
scrollView.setContentOffset(scrollPoint, animated: true)
}
}
The code when the keyboard is going to hide (This code works the first time, it doesn't work the second time):
func keyboardWillBeHidden(notification: NSNotification){
var info = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
self.view.endEditing(true)
self.scrollView.isScrollEnabled = false
}
This is how my view looks like:
Everything needs to move up when the keyboard is showing. Moving up works:
But when the keyboard disappears it doesn't work:
Just change this Line
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0)
with
let contentInsets : UIEdgeInsets = .zero
or try This
//MARK:- Keyboard Methods
func keyboardWillOpen(sender:Notification) {
if let info = sender.userInfo{
if let keyboardSize = info[UIKeyboardFrameBeginUserInfoKey] as? CGRect{
let contentInsets = UIEdgeInsetsMake(0, 0, -keyboardSize.height, 0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
}
}
}
func keyboardWillHide(notification:Notification) {
if let info = notification.userInfo {
if let keyboardSize = info[UIKeyboardFrameBeginUserInfoKey] as? CGRect {
self.scrollView.contentInset = .zero
self.scrollView.scrollIndicatorInsets = .zero
}
}
}
I would not bother to scroll the scrollview, but instead move the scrollview itself.
Get the outlet of the scrollview bottom constraint, and, supposing the scrollview is fullscreen, set the constraint to zero when the keyboard is hidden, and then set it to the keyboard height (correct value is in the notification of keyboardwillshow).
Pseudo code looks like this :
func keyboardWillShow(notification: NSNotification) {
self.scrollviewBottomConstraint.constant = notification.keyboardheight;
self.layoutifneeded(); //I suggest you put this in a 0.3 second animation block
}
func keyboardWillHide(notification: NSNotification) {
self.scrollviewBottomConstraint.constant = 0;
self.layoutIfNeeded(); //I suggest you put this in a 0.3 second animation block
}

Creating floating text box in ios application

I want to add an option when clicking on a cell it will show floating text box above the keyboard and its blurs the background.
Anyone familiar with this and how to implement it?
You can view image at the following link:
Start with adding some properties to your class:
var textField = UITextField()
var composeBarView = UIView()
var blurView = UIView()
let barHeight:CGFloat = 44
Next create the blur view, textField and container view. Do this in the viewWillAppear:
blurView = UIView(frame: self.view.frame)
blurView.alpha = 0.5
blurView.backgroundColor = UIColor.blackColor();
self.view.addSubview(blurView);
blurView.hidden = true;// Note its hidden!
textField = UITextField(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, barHeight))
composeBarView = UIView(frame: CGRectMake(0, UIScreen.mainScreen().bounds.height-64, UIScreen.mainScreen().bounds.width, barHeight));
composeBarView.addSubview(textField);
self.view.addSubView(composeBarView);
Then you should register for the UIKeyboardWillShowNotification and UIKeyboardWillHideNotification notifications:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillToggle:", name: UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillToggle:", name: UIKeyboardWillHideNotification, object: nil);
Implement the keyboardWillToggle method:
func keyboardWillToggle(notfication: NSNotification){
if let userInfo = notfication.userInfo {
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let startFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as NSValue).CGRectValue();
let duration:NSTimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.unsignedLongValue ?? UIViewAnimationOptions.CurveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
var signCorrection:CGFloat = 1;
if (startFrame.origin.y < 0 || startFrame.origin.x < 0 || endFrame.origin.y < 0 || endFrame.origin.x < 0){
signCorrection = -1;
}
let widthChange = (endFrame.origin.x - startFrame.origin.x) * signCorrection;
let heightChange = (endFrame.origin.y - startFrame.origin.y) * signCorrection;
let sizeChange = UIInterfaceOrientationIsLandscape(self.interfaceOrientation) ? widthChange : heightChange;
var frame = composeBarView.frame
frame.origin.y += (sizeChange - barHeight ?? 0.0 )
composeBarView.frame = frame;
UIView.animateWithDuration(duration,
delay: NSTimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)
}
}
Note that we must account for the bar height, when you show the keyboard. You will need to adjust the view back to its original position.
Then when the didSelectRowAtIndexPath is called you call:
textFiled.becomeFirstResponder()
blureView.hidden = false;
Not tested yet but i think that you can do it with a modal segue?
For an exemple, when your textfield become editing you can segue to a new CellViewcontroller which can appear above your actual Tableview and insert this parameters :
func prensentationController(controller: UIPresentationController, viewControllerdaptivePresentationStyle style: UIModalPresentationtyle) -> UIViewController
let navcon = UINavigationController(UIPresentationViewController: controller.presentedViewController)
let visualEffectView = UIVisualEffectView(effect:UIBlurEffect(style: .ExtraLight))
visualEffectView.frame = navcon.view.bounds
navcon.view.insertSubview(visualEffectView, atIndex: 0)
return navcon
}

TextView inside ScrollView stick to keyboard

I have a UIScrollView and content inside it. Inside content there is a UITextView. When user presses the UITextView I want the UITextView stick to the keyboard.
In the image:
Whole thing is UIScrollView
Bottom black area is the visible screen
Red area is content
Blue area is UITextView
Green distance is the dynamic margin between content and the screen bound.
I want to calculate the green distance which the user can see along with the blue are(UITextView) which the user can see. If the user half swiped UITextView, the UITextView should still stick to the keyboard.
let userInfo = notification.userInfo!
let keyboardEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let keyboardBeginFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as NSValue).CGRectValue()
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as UInt
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as Double
let options = UIViewAnimationOptions(curve << 16)
UIView.animateWithDuration(duration, delay: 0, options: options,
animations: {
var visibleGreen = ???
var visibleBlue = ???
var amountToSubtract = visibleGreen + visibleBlue
var newFrame = (self.currentCardInstance?.newCommentCell.frame)!
var kbFrameEnd = self.view.convertRect(keyboardEndFrame, toView: nil)
var kbFrameBegin = self.view.convertRect(keyboardBeginFrame, toView: nil)
newFrame.origin.y -= kbFrameBegin.origin.y - kbFrameEnd.origin.y + amountToSubtract
self.currentCardInstance?.newCommentCell.frame = newFrame;
},
completion: nil
)
Here is how I solved it:
var keyboardModifier: CGFloat = 0
func keyboardWillAppear(notification: NSNotification) {
println("keyboardWillAppear")
keyboardResize(notification: notification)
scrollToBottom()
}
func keyboardWillDisappear(notification: NSNotification) {
println("keyboardWillDisappear")
keyboardResize(notification: notification)
}
func keyboardResize(#notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let keyboardBeginFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as NSValue).CGRectValue()
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as UInt
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as Double
let options = UIViewAnimationOptions(curve << 16)
var newFrame = (self. currentCardInstance?.frame)!
var kbFrameEnd = self.view.convertRect(keyboardEndFrame, toView: nil)
var kbFrameBegin = self.view.convertRect(keyboardBeginFrame, toView: nil)
keyboardModifier = kbFrameBegin.origin.y - kbFrameEnd.origin.y
scrollView.frame.size.height -= keyboardModifier
}
func scrollToBottom() {
var bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
scrollView.setContentOffset(bottomOffset, animated: false)
}

Resources