Scroll view content inset issue on keyboardWillShow - uitableview

I've following code to adjust scroll view inset. The content inside scroll view goes below the keyboard limit as shown in screen recording - https://imgur.com/a/XWfiVYX
How do I fix this so that bottom of bottomView stick to the top of keyboard and doesn't scroll up or down?
#objc func keyboardWillShow(notification:NSNotification) {
let userInfo = notification.userInfo!
let keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let kbSize = keyboardFrame.size
let contentInset:UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: kbSize.height, right: 0.0)
scrollView.contentInset = contentInset
scrollView.scrollIndicatorInsets = contentInset
}

Using scrollView.contentInset will not fix you view above kb, it will scroll up and down as you are seeing it in your case. This solution is from Apple's code which is good to show the textView up above kb only and doesn't stick the bottom of text view above kb.
You need to move up the entire self.view by kb height.

Related

UITextView offset adjustment with Keyboard

I am using the following code to adjust UITextView when the keyboard is shown or hidden.
var backupTextRange:NSRange?
#objc func keyboardWillShow(notification: NSNotification) {
if let rectValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardSize = rectValue.cgRectValue.size
let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
textView.contentInset = contentInsets
textView.scrollIndicatorInsets = contentInsets
if let backupTextRange = backupTextRange {
textView.scrollRangeToVisible(backupTextRange)
NSLog("Scrolling to range \(backupTextRange)")
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
textView.contentInset = contentInsets
textView.scrollIndicatorInsets = contentInsets
backupTextRange = self.textView.selectedRange
}
So basically when I hide the keyboard to edit the font or color of selected text, I save the text range so as to scroll back when user is done choosing a font or color in another viewController (UIFontPickerController for instance). But this results in a sloppy behavior as:
Setting the contentInset of textView automatically starts a scrolling animation to the bottom of textView (not sure if this is the expected behavior),
Calling textView.scrollRangeToVisible causes the text in backupTextRange to move to top of the screen (as opposed to where it was before when it was selected). Moreover, setting this while animation is already in progress makes animation look weird.

UITableView adds height of keyboard to contentSize when the keyboard appears on iOS11

I'm working on a chat that should work on iOS 11 and 12. On iOS 12 everything works as expected. On iOS 11, however, I have the problem that the table view content size increases (no cells) as soon as the keyboard appears. The amount of extra height matches with the keyboard height.
Demo
Here is a demo with iOS 11 on the left and iOS 12 on the right. On iOS 12 everything works just fine. Put attention to the bottom of the table view on iOS 11 when the keyboard appeared.
View/View controller hierarchy setup
- = View controller
+ = View
- UINavigationViewController
- UIViewController // Controlling contentInsets, contentOffset of the tableView
+ UIView
- UITableViewController
+ UITableView
- UIViewController // Controlling the text input bar at the bottom
+ ... // Other views
+ UITextView
Layout Constraints
The table view's anchors are equal to its superview's anchors. So fullscreen, ignoring safe area. So when the keyboard appears the frame doesn't change, but the bottom content insets.
More Details
I've set tableView.contentInsetAdjustmentBehavior = .never
This is how I calculate the insets and offset of the table view when the keyboard appears. It's complex because there are several scenarios where there should be different behavior. There's a similar complex calculation when the keyboard disappears, and when the height of the text input changes. I always want to scroll the table view up or down according to view frame changes.
#objc func handleKeyboardWillShowNotification(_ notification: NSNotification) {
let frameEnd: CGRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue ?? .zero
let keyboardHeight = frameEnd.height
let contentHeight = tableView.contentSize.height
let visibleTableViewHeight = tableView.frame.height - (tableView.contentInset.top + tableView.contentInset.bottom)
let distanceToScroll = (keyboardHeight - view.safeAreaInsets.bottom)
var y: CGFloat = 0
if contentHeight > visibleTableViewHeight {
y = tableView.contentOffset.y + distanceToScroll
} else {
let diff = visibleTableViewHeight - contentHeight
let positionAtKeyboard = distanceToScroll - tableView.contentInset.top - diff
y = positionAtKeyboard < tableView.contentInset.top ? -tableView.contentInset.top : positionAtKeyboard
}
let contentOffset = CGPoint(x: 0, y: y)
tableView.contentInset.bottom = keyboardHeight + inputBar.frame.height
tableView.scrollIndicatorInsets = tableView.contentInset
tableView.setContentOffset(contentOffset, animated: false)
}
I also have tried this on different screen sizes and it always adds an amount to the contentSize that matches exactly the height of the keyboard.
You can use following code for keyboard hide and show.
//Show keyboard.
#objc func keyboardWillAppear(_ notification: NSNotification) {
if let newFrame = (notification.userInfo?[ UIResponder.keyboardFrameEndUserInfoKey ] as? NSValue)?.cgRectValue {
if self.tableView.contentInset.bottom == 0 {
let insets: UIEdgeInsets = UIEdgeInsets( top: 0, left: 0, bottom: newFrame.height, right: 0 )
self.tableView.contentInset = insets
self.tableView.scrollIndicatorInsets = insets
UIView.animate(withDuration: 0.1) {
self.view.layoutIfNeeded()
}
}
}
}
//Hide keyboard.
#objc func keyboardWillDisappear(_ notification: NSNotification) {
if self.tableView.contentInset.bottom != 0 {
self.tableView.contentInset = UIEdgeInsets( top: 0, left: 0, bottom: 0, right: 0 )
self.tableView.scrollIndicatorInsets = UIEdgeInsets( top: 0, left: 0, bottom: 0, right: 0 )
UIView.animate(withDuration: 0.1) {
self.view.layoutIfNeeded()
}
}
}
This is work for me.
First of all you don't have to do unnecessary calculation
Simply calculate the keyboard height, and move the keyboard upside.
Swift Version:
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height + 10, right: 0)
UIView.animate(withDuration: 0.25) {
self.tableView.layoutIfNeeded()
self.view.layoutIfNeeded()
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
UIView.animate(withDuration: 0.5) {
self.tableView.layoutIfNeeded()
self.view.layoutIfNeeded()
}
}
Objecttive-C Version:
- (void)keyboardWillShow:(NSNotification *)notification
{
NSDictionary *keyInfo = [notification userInfo];
CGRect keyboardFrame = [[keyInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardFrame.size.height + 10, 0);
[UIView animateWithDuration:0.2 animations:^{
[self.tableView layoutIfNeeded];
[self.view layoutIfNeeded];
} completion:nil];
}
- (void) keyboardWillHide: (NSNotification *) notification
{
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
[UIView animateWithDuration:0.2 animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
Let me know If you find any difficulties.
This works for me very well
Workaround
This is not specifically answering the original question, but might be a solution for those who don't have a translucent keyboard and input view.
I could get around this problem by changing the constraints and not setting the bottom insets. Initially the bottom constraint of the table view was set to the bottom of the super view (bottom of the screen, basically). So when the keyboard appeared, I didn't change the table view's frame, but the bottom inset. This apparently didn't work properly.
Now I've set the bottom constraint of the table view to the top of the input view (black bar) and the bottom inset to zero. Since the input view moves up when the keyboard appears, it changes the frame of the table view and the bottom inset remains zero. I still set the content offset, because I need specific behavior in different situations, but that's it.
This only works in my situation, because I neither have a translucent input bar nor keyboard and don't need to show blurry content behind it.

Why does iPhone X ignore UIEdgeInsets for UICollectionView?

I have code for my collectionView that adjusts the content so that it sits beneath the navigation bar.
collectionView.contentInsetAdjustmentBehavior = .never
let tabBarHeight = self.tabBarController?.tabBar.bounds.height
let navBarHeight = self.navigationController?.navigationBar.bounds.height
self.edgesForExtendedLayout = UIRectEdge.all
self.collectionView.contentInset = UIEdgeInsets(top: navBarHeight!, left: 0.0, bottom: tabBarHeight!, right: 0.0)
This works well on every other device on iOS 11 except for iPhone X, on iPhone X, the content sits behind the nav bar and toolbar on app start.
Is there something I am missing for iPhone X specifically?
Thanks
I think you forgot to calculate the height of status bar.
Before iPhone X, the height of status bar is 20pt and in iPhoneX it is 44pt. That's the reason you can not see the complete cell.
To do that, add your constraints from superview and write the following code:
cv.contentInsetAdjustmentBehavior = .never
let tabBarHeight = self.tabBarController?.tabBar.bounds.height ?? 0
let statuBarHeight = UIApplication.shared.statusBarFrame.height
let navBarHeight = self.navigationController?.navigationBar.bounds.height ?? 0
self.edgesForExtendedLayout = UIRectEdge.all
cv.contentInset = UIEdgeInsets(top: navBarHeight+statuBarHeight, left: 0.0, bottom: tabBarHeight, right: 0.0)
Hope this helps :)

Why is my view not growing accordingly to the textView inside it (swift)?

I have a swift app and there I have a view with embedded textView.
It looks like this:
My view is below the map, hence it has constraints:
- top space 0 to MapView
- trailing and leading 0
- bottom: 49 to superview (it's embedded in tab controller so with value 49 it's exactly above the tab controls)
The textView has standard constraints:
- leading, bottom, top 4 to view border
- trailing 8 to button
- height 30
when I run the app everything is fine, I see the map and I see the textView below it. When I start typing something in the textView it goes up with the keyboard (thanks to this:
func keyboardWillChangeFrame(notification: NSNotification) {
let endFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
if(isKeyboardShown == true)
{
bottomConstraint.constant = CGRectGetHeight(view.bounds) - endFrame.origin.y+49
self.view.layoutIfNeeded()
isKeyboardShown = false
} else {
//scrollToBottom()
bottomConstraint.constant = CGRectGetHeight(view.bounds) - endFrame.origin.y
self.view.layoutIfNeeded()
isKeyboardShown = true
}
}
, where bottomConstraint is an IBOutlet to the bottom constraint (=0) of the textView. Anyway, I added this method:
func textViewDidChange(textView: UITextView) {
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
var newFrame = textView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
textView.frame = newFrame
}
and I expected the textView to change its size. It's happening, but the textView expands to the bottom - I mean when user starts writing more than one line the textView goes partially under keyboard, instead of covering more and more part of the map. What is more, while going down, the textView goes over the gray view - so the view itself does not expand.
How can I fix it so that the textView and the view grow up covering more and more map with every new line? I don't want it to hide partially under keyboard.

Updating UICollectionViewFlowLayout when the height of my CollectionView changes during runtime

I have a MyCollectionView that has a constraint that is:
BottomLayoutGuide.Top = MyCollectionView.Bottom + 100
In my IB, but this constraint's constant changes according to the height of the keyboard
func keyboardWillShow(notification: NSNotification) {
let keyboardHeight = getKeyboardHeight(notification)
keyboardHeightConstraint.constant = keyboardHeight + 20
self.cardCollectionView.configureCollectionView()
self.cardCollectionView.reloadInputViews()
}
Where configureCollectionView is:
func configureCollectionView() {
self.backgroundColor = UIColor.clearColor()
// Create the layout
let space = 10.0 as CGFloat
let flowLayout = UICollectionViewFlowLayout()
let width = (self.frame.size.width) - 4 * space
let height = (self.frame.size.height)
let edgeInsets = UIEdgeInsetsMake(0, 2 * space, 0, 2 * space)
// Set top and bottom margins if scrolling horizontally, left and right margins if scrolling vertically
flowLayout.minimumLineSpacing = space
flowLayout.minimumInteritemSpacing = 0
// Set horizontal scrolling
flowLayout.scrollDirection = .Horizontal
// Set edge insets
flowLayout.sectionInset = edgeInsets
flowLayout.itemSize = CGSizeMake(width, height)
print(self.frame)
print(flowLayout.itemSize)
self.setCollectionViewLayout(flowLayout, animated: true)
// Always allow it to scroll
self.alwaysBounceHorizontal = true
}
Before the keyboard is shown, everything is fine and these are the values of the frame of MyCollectionView and it's CollectionViewCell
self.frame: (0.0, 75.0, 375.0, 492.0)
flowLayout.itemSize: (335.0, 492.0)
But after the keyboard is shown and configureCollectionView() is called which basically just resets the flowLayout for MyCollectionView everything breaks. And oddly, even though the height of MyCollectionView has decreased the console outputs says that it has actually increased.
self.frame: (0.0, 55.0, 375.0, 512.0)
flowLayout.itemSize: (335.0, 512.0)
Here is a screenshot after the keyboard has appeared:
As you can see, it seems like the CollectionViewCells are being clipped and it has lost it's initial alpha value. Moreover, after randomly scrolling the console outputs:
2016-04-29 09:43:24.012 DeckWheel[15363:2028073] the behavior of the UICollectionViewFlowLayout is not defined because:
2016-04-29 09:43:24.012 DeckWheel[15363:2028073] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
I know that this is due to the height of MyCollectionView changing but, I don't know why it is going wrong as everything works perfectly before the keyboard shows. For example, I can scroll randomly between the cells and if I get to the last cell it will automatically create a new one without any crashes.
I've tried MyCollectionView.invalidateLayout() and then callingconfigureCollectionView() to reset the flowLayout but nothing seems to work. What is the correct way to update MyCollectionView so that this does not happen?

Resources