Collapsed keyboard, toolbar - ios

How can I check when keyboard is shown fully and when collapsed like this? Maybe can I check it height?
I checked userInfo from notification for now and I found nothing useful for resolve my problem.

Just subscribe to notifications:
NotificationCenter.default.addObserver(self, selector:
#selector(self.adjustForKeyboard), name:
NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector:
#selector(self.adjustForKeyboard), name:
NSNotification.Name.UIKeyboardDidHide, object: nil)
and paste next method nearly
func adjustForKeyboard(notification: NSNotification) {
let userInfo = notification.userInfo!
if let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardViewEndFrame = delegate.getView().convert(keyboardScreenEndFrame, from: delegate.getView().window)
var customInset = UIEdgeInsets.zero
if notification.name == NSNotification.Name.UIKeyboardDidHide {
customInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
} else {
customInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
scrollView.contentInset = customInset
scrollView.scrollIndicatorInsets = customInset
}
}
don't forget to remove observer when you left this VC
NotificationCenter.default.removeObserver(self)

Related

swift UITableView scrolls up when keyboard appears but I can't see the top cells

I have a ViewController with a TableView and a textView. I'm using the following code so that when the keyboard shows up, it pushes the textView and Tableview up.
bindToKeyboard
extension UIView {
func bindToKeyboard(){
NotificationCenter.default.addObserver(self, selector:
#selector(UIView.keyboardWillChange(_:)), name:
UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
#objc func keyboardWillChange(_ notification: NSNotification) {
let duration = notification.userInfo!
[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey]
as! UInt
let curFrame = (notification.userInfo![UIResponder.keyboardFrameBeginUserInfoKey]
as! NSValue).cgRectValue
let targetFrame = (notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey]
as! NSValue).cgRectValue
let deltaY = targetFrame.origin.y - curFrame.origin.y
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options:
UIView.KeyframeAnimationOptions(rawValue: curve), animations: {
self.frame.origin.y += deltaY
},completion: {(true) in
self.layoutIfNeeded()
})
}
}
The problem I'm having is with the top cells in the tableView, i can't scroll down to display them unless i close the keyboard. I've been searching for possible code samples to fix this but I can't seem to find the right solution. Any input is greatly appreciated.
SOLUTION
Added observer to viewDidLoad
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
this was added to the class
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
tableView.contentInset = .zero
} else {
//Modify the top insets to 350 and the height of the keyboard > 10 ? 10 : 10
//to eliminate the gap between the bottom cell and the textView
tableView.contentInset = UIEdgeInsets(top: 350, left: 0, bottom: keyboardViewEndFrame
.height > 10 ? 10 : 10, right: 0)
}
tableView.scrollIndicatorInsets = tableView.contentInset
}
hope this helps anyone.
You are changing the frame of the table view, but you should modify the contentOffset or contentInsets property.
If you move the frame, the whole table will shift offscreen, hence the top cells are also offscreen.
There a many (and with many I mean many) tutorials who might help you, see e.g. https://www.hackingwithswift.com/example-code/uikit/how-to-adjust-a-uiscrollview-to-fit-the-keyboard
Also multiple solutions are to be found on e.g. github.
you can also use IQKeyboardManager cocoapod into your project

How to correctly determine keyboard and text field position on iOS?

I want to lift the view when the keyboard shows and the text field becomes invisible. Here is my code:
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let tfPos = lastTextField!.convert(lastTextField!.frame, to: self.view)
let tfBottom = tfPos.origin.y
let kbTop = keyboardSize.origin.y
if kbTop < tfBottom {
self.verticalCenterConstraint.constant = -(tfBottom - kbTop)
}
}
}
I determine the top of the keyboard, then the bottom of the text field. However, the text field position is not correct as even if I can clearly see on the screen that it is above the keyboard, my code says it's below with 35pt. How do I fix this?
Step :- 1 Add Observer for the hide and show notifications
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard),
name: Notification.Name.UIKeyboardWillHide, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard),
name: Notification.Name.UIKeyboardWillChangeFrame, object: nil)
Step :- 2 Implement target method
#objc func adjustForKeyboard(notification: Notification) {
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == Notification.Name.UIKeyboardWillHide {
yourTextView.contentInset = UIEdgeInsets.zero
} else {
yourTextView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
yourTextView.scrollIndicatorInsets = yourTextView.contentInset
let selectedRange = yourTextView.selectedRange
yourTextView.scrollRangeToVisible(selectedRange)
}
If any changes are done to the constraints self.view.layoutIfNeeded() needs to be called.
You can call it in animation block
UIView.animate(withDuration: duration,
delay: TimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)

Handling keyboard with bottom constraint

I'm building a chat app with a regular design and UI of a chat app.
I have a "toolbar" that my app needs (0,0 375x66) and I want it to stay where it is when the keyboard is shown. It has 4 constraints: top: 0, leading: 0, trailing: 0, aspect-ratio: 125:22
I have a UICollectionView (0,66 375x530) that I want to be scrolled like any other chat app when the keyboard is shown. It has 4 constraints: top (to the toolbar): 0, leading: 0, trailing: 0, bottom (to the newMessageView I'll explain shortly): 0
I have a UIView that has a UITextField inside it. let's call it newMessageView (0,596 375x71) and it has 4 constraints: bottom: 0, leading: 0, trailing: 0, aspect-ratio: 375:71
Now, I'm trying to bring the newMessageView app when the keyboard is shown. I'm trying to to it like this:
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0{
self.newMessageViewBottomConstraint.constant += keyboardSize.height
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0{
self.newMessageViewBottomConstraint.constant -= keyboardSize.height
}
}
}
but it completly doesn't work. The view jumps and hides and I really can't understand why.
Am I doing it right? Can anyone help me and guide me with this?
You have to use self.view.layoutIfNeeded() after Update constraint
i write complete solution for you
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
#objc func keyboardWillShow(notification: Notification) {
self.keyboardControl(notification, isShowing: true)
}
#objc func keyboardWillHide(notification: Notification) {
self.keyboardControl(notification, isShowing: false)
}
private func keyboardControl(_ notification: Notification, isShowing: Bool) {
/* Handle the Keyboard property of Default*/
var userInfo = notification.userInfo!
let keyboardRect = (userInfo[UIKeyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue
let curve = (userInfo[UIKeyboardAnimationCurveUserInfoKey]! as AnyObject).uint32Value
let convertedFrame = self.view.convert(keyboardRect!, from: nil)
let heightOffset = self.view.bounds.size.height - convertedFrame.origin.y
let options = UIViewAnimationOptions(rawValue: UInt(curve!) << 16 | UIViewAnimationOptions.beginFromCurrentState.rawValue)
let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue
var pureheightOffset : CGFloat = -heightOffset
if isShowing { /// Wite space of save area in iphonex ios 11
if #available(iOS 11.0, *) {
pureheightOffset = pureheightOffset + view.safeAreaInsets.bottom
}
}
// Here change you Consrant
// self.actionBarPaddingBottomConstranit?.update(offset:pureheightOffset)
UIView.animate(
withDuration: duration!,
delay: 0,
options: options,
animations: {
self.view.layoutIfNeeded()
},
completion: { bool in
})
}

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

Extend iOS 11 Safe Area to include the keyboard

The new Safe Area layout guide introduced in iOS 11 works great to prevent content from displaying below bars, but it excludes the keyboard. That means that when a keyboard is displayed, content is still hidden behind it and this is the problem I am trying to solve.
My approach is based on listening to keyboard notifications and then adjusting the safe area through additionalSafeAreaInsets.
Here is my code:
override func viewDidLoad() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
//MARK: - Keyboard
extension MyViewController {
#objc func keyboardWillShow(notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded();
}
}
#objc func keyboardWillHide(notification: NSNotification) {
additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded();
}
}
#objc func keyboardWillChange(notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded();
}
}
}
This works well as the MyController is a UIViewController with a UITableView that extends through the whole safe area. Now when the keyboard appears, the bottom is pushed up so that no cells are behind the keyboard.
The problem is with bottom bars. I also have a toolbar at the bottom which is already included in the safe area. Therefore, setting full keyboard height as additional safe area inset pushes the bottom of the table view up too much by exactly the height of the bottom bar. For this method to work well, I must set the additionalSafeAreaInsets.bottom to be equal to the keyboard height minus the height of the bottom bar.
Question 1: What is the best way to get the current safe area gap on the bottom? Manually get frame of toolbar and use its height? Or is it possible to get the gap directly from the safe area layout guide?
Question 2: Presumably it should be possible for the bottom bar to change size without the keyboard changing size so I should also implement some method listening to change in frame of the bar. Is this best done in viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)? Or elsewhere?
Thank you
What seems to be working for me is to calculate the intersection between view.safeAreaLayoutGuide.layoutFrame and the keyboard frame, and then setting the height of that as the additionalSafeAreaInsets.bottom, instead of the whole keyboard frame height. I don't have a toolbar in my view controller, but I do have a tab bar and it is accounted for correctly.
Complete code:
import UIKit
public extension UIViewController
{
func startAvoidingKeyboard()
{
NotificationCenter.default
.addObserver(self,
selector: #selector(onKeyboardFrameWillChangeNotificationReceived(_:)),
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil)
}
func stopAvoidingKeyboard()
{
NotificationCenter.default
.removeObserver(self,
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil)
}
#objc
private func onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification)
{
guard
let userInfo = notification.userInfo,
let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
else {
return
}
let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)
let intersection = safeAreaFrame.intersection(keyboardFrameInView)
let keyboardAnimationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey]
let animationDuration: TimeInterval = (keyboardAnimationDuration as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
let animationCurve = UIView.AnimationOptions(rawValue: animationCurveRaw)
UIView.animate(withDuration: animationDuration,
delay: 0,
options: animationCurve,
animations: {
self.additionalSafeAreaInsets.bottom = intersection.height
self.view.layoutIfNeeded()
}, completion: nil)
}
}
If you need support back to pre IOS11 versions you can
use the function from Fabio and add:
if #available(iOS 11.0, *) { }
final solution:
extension UIViewController {
func startAvoidingKeyboard() {
NotificationCenter.default.addObserver(self,
selector: #selector(_onKeyboardFrameWillChangeNotificationReceived(_:)),
name: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil)
}
func stopAvoidingKeyboard() {
NotificationCenter.default.removeObserver(self,
name: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil)
}
#objc private func _onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification) {
if #available(iOS 11.0, *) {
guard let userInfo = notification.userInfo,
let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
return
}
let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)
let intersection = safeAreaFrame.intersection(keyboardFrameInView)
let animationDuration: TimeInterval = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
let animationCurve = UIViewAnimationOptions(rawValue: animationCurveRaw)
UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: {
self.additionalSafeAreaInsets.bottom = intersection.height
self.view.layoutIfNeeded()
}, completion: nil)
}
}
}
I use a different approach. I have a view (KeyboardProxyView) that I add to my view hierarchy.
I pin this to the bottom of the main view, and adjust its height with the keyboard. This means we can treat the keyboardProxy as if it is the keyboard view - except that it is a normal view, so you can use constraints on it.
This allows me to constrain my other views relative to the keyboardProxy manually.
e.g. - my toolbar isn't constrained at all, but I might have inputField.bottom >= keyboardProxy.top
Code below
(note - I use HSObserver and PureLayout for notifications and autolayout - but you could easily rewrite that code if you prefer to avoid them)
import Foundation
import UIKit
import PureLayout
import HSObserver
/// Keyboard Proxy view will mimic the height of the keyboard
/// You can then constrain other views to move up when the KeyboardProxy expands using AutoLayout
class KeyboardProxyView: UIView, HSHasObservers {
weak var keyboardProxyHeight: NSLayoutConstraint!
override func didMoveToSuperview() {
keyboardProxyHeight = self.autoSetDimension(.height, toSize: 0)
let names = [UIResponder.keyboardWillShowNotification,UIResponder.keyboardWillHideNotification]
HSObserver.init(forNames: names) { [weak self](notif) in
self?.updateKeyboardProxy(notification: notif)
}.add(to: self)
activateObservers()
}
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
func updateKeyboardProxy(notification:Notification){
let userInfo = notification.userInfo!
let animationDuration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let convertedKeyboardEndFrame = self.superview!.convert(keyboardEndFrame, from: self.window)
let rawAnimationCurve = (notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).uint32Value << 16
let animationCurve = UIView.AnimationOptions(rawValue:UInt(rawAnimationCurve))
keyboardProxyHeight.constant = self.superview!.bounds.maxY - convertedKeyboardEndFrame.minY
//keyboardProxyHeight.constant = keyboardEndFrame.height
UIView.animate(withDuration: animationDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {
self.parentViewController?.view.layoutIfNeeded()
}, completion: nil)
}
}
bottom inset:
var safeAreaBottomInset: CGFloat = 0
if #available(iOS 11.0, *) {
safeAreaBottomInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
}
whole keyboard function:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.onKeyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
#objc private func onKeyboardWillChangeFrame(_ notification: NSNotification) {
guard let window = self.view.window,
let info = notification.userInfo,
let keyboardFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
let duration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval,
let animationCurve = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
else {
return
}
var safeAreaInset: CGFloat = 0
if #available(iOS 11.0, *) {
safeAreaInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
}
self.keyboardConstraint.constant = max(0, self.view.frame.height - window.convert(keyboardFrame, to: self.view).minY - safeAreaInset)
UIView.animate(
withDuration: duration,
delay: 0,
options: [UIView.AnimationOptions(rawValue: animationCurve.uintValue), .beginFromCurrentState],
animations: { self.view.layoutIfNeeded() },
completion: nil
)
}
Excluding the bottom safe area worked for me:
NSValue keyboardBounds = (NSValue)notification.UserInfo.ObjectForKey(UIKeyboard.FrameEndUserInfoKey);
_bottomViewBottomConstraint.Constant = keyboardBounds.RectangleFValue.Height - UIApplication.SharedApplication.KeyWindow.SafeAreaInsets.Bottom;
View.LayoutIfNeeded();

Resources