I have got a textView and a scrollView. I want to achieve a state where I click in the textView and it will make way for the keyboard. However it seems to work only when I click on the textView for the second time.
And the code:
#objc open override func keyboardWillShow(notification: NSNotification) {
keyboardTapGesture.isEnabled = true
scrollView.isScrollEnabled = true
let info = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize?.height ?? 0.0, right: 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
scrollView.scrollRectToVisible(commentInput.frame, animated: true)
}
#objc open override func keyboardWillHide(notification: NSNotification) {
keyboardTapGesture.isEnabled = false
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
view.endEditing(true)
scrollView.isScrollEnabled = false
}
Thanks for your help.
Related
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.
I'm using this code. if keyboard appears then it increases the size of the view so user can easily scroll to the bottom. everything works fine but I want to make an extension for this code because I don't want to use such a long code in my controller
import UIKit
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
func setnotification()
{
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWasShown(notification: NSNotification)
{
var info = notification.userInfo!
let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize!.height+10, right: 0.0)
self.scrolView.contentInset = contentInsets
self.scrolView.scrollIndicatorInsets = contentInsets
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if let activeField = activeTextField
{
if (!aRect.contains(activeField.frame.origin))
{
self.scrolView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
// when keyboard hide reduce height of scroll view
#objc func keyboardWillBeHidden(notification: NSNotification){
let contentInsets : UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0,bottom: 0.0, right: 0.0)
myScrolView!.contentInset = contentInsets
myScrolView!.scrollIndicatorInsets = contentInsets
self.view.endEditing(true)
}
}
Extensions add new functionality to an existing class, structure, enumeration, or protocol type.
Syntax for extension:
extension ViewControllerName {
// Put code which you want to
}
/**
If you implement UITableViewDataSource and UITableViewDelegate methods
or you can implement for UIPickerViewDataSource methods and protocol also
*/
extension ViewController: UItableViewDataSource, UITableViewDelegate {
//implement tableview datasource and delegate method
}
/**
Keyboard show/hide
*/
extension ViewController {
/**
Add scrollview functionality to scroll top and you can call
this function from anywhere in the controller.
*/
func scrollToTop() {
}
#objc func keyboardWasShown(notification: NSNotification)
{
var info = notification.userInfo!
let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize!.height+10, right: 0.0)
self.scrolView.contentInset = contentInsets
self.scrolView.scrollIndicatorInsets = contentInsets
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if let activeField = self.activeTextField
{
if (!aRect.contains(activeField.frame.origin))
{
self.scrolView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
// when keyboard hide reduce height of scroll view
#objc func keyboardWillBeHidden(notification: NSNotification){
let contentInsets : UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0,bottom: 0.0, right: 0.0)
self.scrolView.contentInset = contentInsets
self.scrolView.scrollIndicatorInsets = contentInsets
self.view.endEditing(true)
}
}
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
}
func keyboardWillShow(notification: NSNotification) {
//self.scrollView.frame.origin.y = -150
debugPrint("keyboard show")
self.scrollView.scrollEnabled = true
let info : NSDictionary = 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
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if activeField != nil
{
if (!CGRectContainsPoint(aRect, activeField!.frame.origin))
{
self.scrollView.scrollRectToVisible(activeField!.frame, animated: true)
}
}
}
func keyboardWillHide(notification: NSNotification) {
// self.scrollView.frame.origin.y = 0
debugPrint("key board hide")
let info : NSDictionary = 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.keyboardDismissMode = .Interactive
self.viewDidLayoutSubviews()
}
I am calculating uiscrollview content size like this, container is the view inside uiscrollview
override func viewDidLayoutSubviews() {
var i = 0
var contentRect : CGRect = CGRectZero;
for view in self.container.subviews {
debugPrint("\(i)")
contentRect = CGRectUnion(contentRect, view.frame)
i += 1
}
self.scrollView.contentSize = contentRect.size;
}
After doing all this when the keyboard disappears scrollview stops scrolling. I am new to ios development and this uiscrollview is killing me.
I have a method on a view controller which scrolls text fields up when focused if the keyboard is on screen.
I have tried to do the same thing on a different controller but with a textview but it's not working.
Attempt:
func keyboardOnScreen(notification: NSNotification){
// Retrieve the size and top margin (inset is the fancy word used by Apple)
// of the keyboard displayed.
let info: NSDictionary = notification.userInfo!
let kbSize = info.valueForKey(UIKeyboardFrameEndUserInfoKey)?.CGRectValue.size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize!.height, 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
var aRect: CGRect = self.view.frame
aRect.size.height -= kbSize!.height
//you may not need to scroll, see if the active field is already visible
print("are we in here?")
if activeField != nil {
print("active field was not nil")
if (CGRectContainsPoint(aRect, activeField!.frame.origin) == false) {
let scrollPoint:CGPoint = CGPointMake(0.0, activeField!.frame.origin.y - kbSize!.height)
scrollView.setContentOffset(scrollPoint, animated: true)
}
}
}
func keyboardOffScreen(notification: NSNotification){
print("keyboard off screen")
let contentInsets:UIEdgeInsets = UIEdgeInsetsZero
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
self.scrollView.setContentOffset(CGPointMake(0, -self.view.frame.origin.y/2), animated: true)
}
My controller extends the text view delegate. Active field is declared as an optional. And it is set in the following:
func textViewDidBeginEditing(textView: UITextView) {
activeField = textView
//other
}
func textViewDidEndEditing(commentTextView: UITextView) {
activeField = nil
//other
}