UIScrollView changes contentOffset when I update contentInset - ios

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
}
}

Related

Smooth animation in keyboardWillShow doesn't work with UITextView

Solution which worked for me
After struggling for days I finally manage to find the fix for view animation issue. For me keyboardWillChangeFrameNotification worked. The solution is discussed in the following thread:
Move textfield when keyboard appears swift
I've a bunch of views embedded inside stack view along with text view. I've given text view height of <= 120. In keyboardWillShow view doesn't animate despite adding code as required. I've played with duration value but it's all same result. I was wondering if it's due to text view? How to fix it?
#objc func keyboardWillShow(notification:NSNotification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if #available(iOS 11.0, *) {
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardFrame.height - view.safeAreaInsets.bottom, right: 0)
} else {
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardFrame.height, right: 0)
}
scrollView.scrollIndicatorInsets = scrollView.contentInset
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height + keyboardFrame.height - scrollView.bounds.size.height)
scrollView.setContentOffset(bottomOffset, animated: true)
UIView.animate(withDuration: 0.5, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
===
extension FirstViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
let estimatedSize = textView.sizeThatFits(textView.frame.size)
if estimatedSize.height > textViewMaxHeight {
if estimatedSize.height - textViewMaxHeight < textView.font!.lineHeight && !didExpandTextView {
didExpandTextView = true
var contentInset:UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.savedKbHeight, right: 0.0)
if let v = self.tabBarController?.tabBar {
contentInset.bottom -= v.frame.height
}
scrollView.contentInset = contentInset
scrollView.scrollIndicatorInsets = contentInset
if textView.isFirstResponder {
let fr = textView.frame
scrollView.scrollRectToVisible(fr, animated: false)
}
}
textView.isScrollEnabled = true
textView.showsVerticalScrollIndicator = true
} else {
if let lineHeight = textView.font?.lineHeight, Int(estimatedSize.height / lineHeight) != numberOfLines {
numberOfLines = Int(estimatedSize.height / textView.font!.lineHeight)
var contentInset:UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.savedKbHeight, right: 0.0)
print("contentInset: \(contentInset)")
scrollView.contentInset = contentInset
scrollView.scrollIndicatorInsets = contentInset
if textView.isFirstResponder {
let fr = textView.frame
scrollView.scrollRectToVisible(fr, animated: false)
}
didExpandTextView = false
}
textView.isScrollEnabled = false
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
Try this approach:
func textViewDidChange(_ textView: UITextView) {
let estimatedSize = textView.sizeThatFits(textView.frame.size)
textView.isScrollEnabled = estimatedSize.height > textViewMaxHeight
if !textView.isScrollEnabled {
let maxBottom = self.view.frame.height - self.savedKbHeight
// Use following line if you have Extended Edges set
// let maxBottom = self.view.frame.height - self.savedKbHeight - topLayoutGuide.length - bottomLayoutGuide.length
var r = self.textView.frame
r.size.height = estimatedSize.height
let tvBottom = self.scrollView.convert(r, to: self.view).maxX
if tvBottom > maxBottom {
self.scrollView.scrollRectToVisible(r, animated: true)
}
}
}
Edit Note: This is not intended to be Production Ready code. There are many things that affect layout / positioning, and there is no guarantee that just "dropping this in" will work in all situations.

Move CollectionView content above keyboard

I would like to move CollectionView Cells above the keyboard when keyboard its appear. I just got keyboard size then i changed UIEdgeInsets the bottom of Collection view but nothing happens
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(collectionV)
collectionV.dataSource = self
collectionV.delegate = self
collectionV.alwaysBounceVertical = true
collectionV.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 45, right: 0)
collectionV.backgroundColor = UIColor.white
collectionV.keyboardDismissMode = .interactive
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillShow), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillHide), name: .UIKeyboardWillHide, object: nil)
}
#objc func handleKeyboardWillShow(notification: Notification) {
print("will show?")
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let userInfo = notification.userInfo!
let animationDuration: TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
ContainerViewBottomAnchor?.constant = -keyboardSize.height
collectionV.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: keyboardSize.height+8, right: 0)
UIView.animate(withDuration: animationDuration) {
self.view.layoutIfNeeded()
}
}
}
#objc func handleKeyboardWillHide(notification: Notification) {
print("will hide?")
let userInfo = notification.userInfo!
let animationDuration: TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
ContainerViewBottomAnchor?.constant = 0
self.collectionV.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 45, right: 0)
UIView.animate(withDuration: animationDuration) {
self.view.layoutIfNeeded()
}
}
On Simulator when i press (⌘k) its working(the Cells move above), but when i click on TextField to show keyboard isn't working (Cells not moving) and i still get (print("will show")), Also on real device not working.
That makes me confusing, Any help to figure out what's wrong here?
Edit Fix: let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
Thanks to #MichaelVorontsov

UITableView Scrolling/Inset Issue

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

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
}

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