Is it possible to get the frame, actually its height, of the keyboard dynamically? As I have a UITextView and I would like to adjust its height according to the keyboard frame height, when the input method of the keyboard is changed. As you know, different input methods may have different keyboard frame height.
try this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification
object:nil];
- (void)keyboardWasShown:(NSNotification *)notification
{
// Get the size of the keyboard.
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
//Given size may not account for screen rotation
int height = MIN(keyboardSize.height,keyboardSize.width);
int width = MAX(keyboardSize.height,keyboardSize.width);
//your other code here..........
}
Tutorial for more information
Just follow this tutorial from Apple and you will get what you want. Apple Documentation. In order to determine the area covered by keyboard please refer to this tutorial.
For the Swift 3 users, the #Hector code (with some additions) would be:
In your viewDidLoad add the observer :
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: .UIKeyboardDidShow , object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(_:)), name: .UIKeyboardDidHide , object: nil)
Then implement those methods:
func keyboardDidShow(_ notification: NSNotification) {
print("Keyboard will show!")
// print(notification.userInfo)
let keyboardSize:CGSize = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue.size
print("Keyboard size: \(keyboardSize)")
let height = min(keyboardSize.height, keyboardSize.width)
let width = max(keyboardSize.height, keyboardSize.width)
}
func keyboardDidHide(_ notification: NSNotification) {
print("Keyboard will hide!")
}
You can add this code to the view which contains the text field in Swift 3. This will make the text field animate up and down with the keyboard.
private var keyboardIsVisible = false
private var keyboardHeight: CGFloat = 0.0
// MARK: Notifications
private func registerForKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
private func deregisterFromKeyboardNotifications() {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
// MARK: Triggered Functions
#objc private func keyboardWillShow(notification: NSNotification) {
keyboardIsVisible = true
guard let userInfo = notification.userInfo else {
return
}
if let keyboardHeight = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.height {
self.keyboardHeight = keyboardHeight
}
if !textField.isHidden {
if let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber,
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber {
animateHUDWith(duration: duration.doubleValue,
curve: UIViewAnimationCurve(rawValue: curve.intValue) ?? UIViewAnimationCurve.easeInOut,
toLocation: calculateTextFieldCenter())
}
}
}
#objc private func keyboardWillBeHidden(notification: NSNotification) {
keyboardIsVisible = false
if !self.isHidden {
guard let userInfo = notification.userInfo else {
return
}
if let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber,
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber {
animateHUDWith(duration: duration.doubleValue,
curve: UIViewAnimationCurve(rawValue: curve.intValue) ?? UIViewAnimationCurve.easeInOut,
toLocation: calculateTextFieldCenter())
}
}
}
// MARK: - Helpers
private func animateHUDWith(duration: Double, curve: UIViewAnimationCurve, toLocation location: CGPoint) {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(TimeInterval(duration))
UIView.setAnimationCurve(curve)
textField.center = location
UIView.commitAnimations()
}
private func calculateTextFieldCenter() -> CGPoint {
if !keyboardIsVisible {
return self.center
} else {
let yLocation = (self.view.frame.height - keyboardHeight) / 2
return CGPoint(x: self.center.x, y: yLocation)
}
}
Related
Keyboard responder file looks like:
class KeyboardResponder: ObservableObject {
#Published var currentHeight: CGFloat = 0
var _center: NotificationCenter
init(center: NotificationCenter = .default) {
_center = center
_center.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
_center.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyBoardWillShow(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
withAnimation {
currentHeight = keyboardSize.height
}
}
print("the KEYBOARD HEIGHT IS \(self.currentHeight)")
}
#objc func keyBoardWillHide(notification: Notification) {
withAnimation {
currentHeight = 0
}
print("the KEYBOARD HEIGHT IS \(self.currentHeight)")
}
}
I try to use it in a view where the body is:
VStack {
VStack {
\\view content here
}.offset(y: -self.keyboardResponder.currentHeight) \\ keyboardResponder is an instance of KeyboardResponder
}.edgesIgnoringSafeArea(.all)
When I remove edgesIgnoringSafeArea(.all) it works fine but if I put it in, it breaks the offset so it no longer moves the content at all...
They deprecated .edgesIgnoreSafeArea in iOS 14. The new method has multiple options for the “types” of safe area to ignore: .container (the usual “safe area”), .keyboard (new!), and .all (ignores both container and keyboard — I suspect that’s the behavior you’re getting).
Try .ignoresSafeArea(.container) instead.
https://developer.apple.com/documentation/swiftui/offsetshape/ignoressafearea(_:edges:)
In SwiftUI we can let the keyboard be interactive in a List by swiping down. We only need to provide this code to the init() of the view.
init() {
UITableView.appearance().keyboardDismissMode = .interactive
}
This works fine but the view that is attached to the keyboard isn't moving with the keyboard itself.
I found this post which should solve the issue in UIKit. How to I solve it in SwiftUI?
This is how I handle Keyboard popping up:
final class KeyboardResponder: ObservableObject {
private var _center: NotificationCenter
#Published private(set) var currentHeight: CGFloat = 0
#Published private(set) var duration: Double = 0.0
init(center: NotificationCenter = .default) {
_center = center
_center.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
_center.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
deinit {
_center.removeObserver(self)
}
#objc func keyBoardWillShow(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double {
self.duration = duration
currentHeight = keyboardSize.height
}
}
#objc func keyBoardWillHide(notification: Notification) {
currentHeight = 0
}
}
struct KeyboardHandler: ViewModifier {
let height: CGFloat
let duration: Double
func body(content: Content) -> some View {
content
.padding(.bottom, height)
.animation(.spring(blendDuration: duration))
}
}
I need to find out when the textfield becomes the first responder to notify me whether the keyboard that's going to show will obstruct the UITextField. If it does, I wanna adjust the scrollview properties.
So far I have this setup. I'm listening for UIKeyboardWillShow notifications that calls the following selector:
func keyboardWillAppear(notification:NSNotification)
{
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
{
if keyboardSize.intersects(textField.frame)
{
print("It intersects")
}
else
{
print("Houston, we have a problem")
}
}
Note: I tried with UIKeyboardDidShow but still no success. UITextField is a subview of the scrollView.
listen to size changes of the keyboard
CONVERT the coordinates
working sample:
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
//keyboard observers
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
func keyboardWillChange(notification:NSNotification)
{
print("Keyboard size changed")
if let keyboardSize = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect {
//convert gotten rect
let r = self.view.convert(keyboardSize, from: nil)
//test it
if r.intersects(textView.frame) {
print("intersects!!!")
}
}
}
How about comparing the start position of the keyboard with the end position of the text?
working sample:
func keyboardWillAppear(notification:NSNotification)
{
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
{
if keyboardSize.origin.y < textField.frame.origin.y + textField.frame.size.height {
print("It intersects")
} else {
print("Houston, we have a problem")
}
}
}
I made an NSObject class that is receiving a couple of variables from a UIView class. I am successfully receiving the variables in the NSObject class and am processing them in my method receiveDims, where I append an array of Doubles. When I want to access this array in a second method (serialize), they show up as nil. Some of the research I have done on this suggest that this is a timing issue.
Does anybody have an idea as to why this is showing up as nil and not updating in serialize()? Is there a better way to have these arrays update in serialize()? Pertinent code is below.
class PostPath: NSObject {
var points:Array<PostPoint>
var color:UIColor
var width = [Double]()
var height = [Double]()
init(point:CGPoint, color:UIColor) {
self.color = color
self.points = Array<PostPoint>()
let newPoint = PostPoint(point: point)
points.append(newPoint)
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PostPath.receiveDims(_:)), name: "screenWidth", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PostPath.receiveDims(_:)), name: "screenHeight", object: nil)
}
func addPoint(point:CGPoint){
let newPoint = PostPoint(point: point)
points.append(newPoint)
}
func receiveDims(sender: NSNotification){
if sender.userInfo!["width"] != nil{
self.width.append(sender.userInfo!["width"] as! Double)
print("post path width \(self.width[0])")
}
if sender.userInfo!["height"] != nil{
self.height.append(sender.userInfo!["height"] as! Double)
print("post path height \(self.height[0])")
}
}
func serialize() -> NSDictionary{
print(self.width[0])
let dictionary = NSMutableDictionary()
let cgColor = color.CGColor
dictionary["color"] = CIColor(CGColor: cgColor).stringRepresentation
let pointsOfPath = NSMutableArray()
for point in points{
let pointDictionary = NSMutableDictionary()
pointDictionary["x"] = point.x!
pointDictionary["y"] = point.y!
pointsOfPath.addObject(pointDictionary)
}
dictionary["points"] = pointsOfPath
return dictionary
}
}
The class DrawingView is where the width and height variables originate. They are stored to NSNotificationCenter and PostPath.serialize() are called in the following manner (I have trimmed the code to only show these parts):
class DrawingView: UIView {
func getDims(){
self.width = Double(self.frame.size.width)
self.height = Double(self.frame.size.height)
NSNotificationCenter.defaultCenter().postNotificationName("screenWidth", object: nil, userInfo: ["width": width])
NSNotificationCenter.defaultCenter().postNotificationName("screenHeight", object: nil, userInfo: ["height": height])
}
func resetPatch(sendToFirebase:Bool){
currentPointPath?.serialize()
}
func addPathToSend(path: PostPath)->String{
firebaseKey.setValue(path.serialize()) { (error:NSError!, ref:Firebase!) -> Void in
if let error = error{
print("Error saving path to firebase\(error)")
} else{
pathsInLine.removeObject(firebaseKey)
}
}
return firebaseKey.key
}
}
I have input form in popover.
When keyboard appears it shrinks in half..I have added in scroll view in base too but didn’t work..
Initializing:
var popoverViewController = self.storyboard?.instantiateViewControllerWithIdentifier("UnitEditController") as! UnitEditController
popoverViewController.modalPresentationStyle = .Popover
popoverViewController.preferredContentSize = CGSizeMake(820, 820)
popoverViewController.unit = unit
popoverViewController.property = property
popoverViewController.unitDetailProtocolVar = self
let popoverPresentationViewController = popoverViewController.popoverPresentationController
var rect = CGRectMake(cell!.bounds.origin.x+500, cell!.bounds.origin.y+20, 50, 30);
popoverPresentationViewController?.delegate = self
popoverPresentationViewController?.sourceView = cell!.contentView
popoverPresentationViewController?.sourceRect = cell!.frame
popoverPresentationViewController?.permittedArrowDirections = UIPopoverArrowDirection.allZeros
popoverPresentationViewController?.sourceRect = rect
presentViewController(popoverViewController, animated: true, completion: nil)
Prepare code for textfield and textview editing:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: nil);
let scrollSize = CGSizeMake(900, 820)
self.contentScrollView.contentSize = scrollSize
func keyboardWillShow(sender: NSNotification) {
self.contentScrollView.frame=CGRectMake(0, -300, 320, 700);
}
func keyboardWillHide(sender: NSNotification) {
self.view.frame=CGRectMake(0, +300, 320, 700);
}
it should auto scroll accordingly when i start editing textview and textfield.
Can someone help me on this
this is what I did:
I created a keyboardHandler class
class KeyboardUtils{
static var instance = KeyboardUtils();
static var lastKeyboardSize : CGSize = CGSize();
var keyBoarEventsCallBacks : [(onShow: ()->Void,onHide: ()->Void)] = [];
init(){
self._registerKeyboardHandler()
KeyboardUtils.instance = self;
}
private func _registerKeyboardHandler(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: nil);
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0);
KeyboardUtils.lastKeyboardSize = keyboardSize.size;
for i in self.keyBoarEventsCallBacks{
i.onShow();
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
for i in self.keyBoarEventsCallBacks{
i.onHide();
}
}
}
}
this class will handle event when keyboard is opened.
now from my scrollView class i did this:
class myScrollView : UIScrollView,UIScrollViewDelegate{
override init(frame: CGRect)
{
super.init(frame:frame)
KeyboardUtils.instance.keyBoarEventsCallBacks.append((onShow:self.onKeyboardAppear,onHide: self.onKeyboardHide));
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func onKeyboardAppear(){
//I just resized, you can also do super.contentOffset =...
var size = super.contentSize;
size.height+=KeyboardUtils.lastKeyboardSize.height;
super.contentSize = size;
}
func onKeyboardHide(){
var size = super.contentSize;
size.height-=KeyboardUtils.height;
super.contentSize = size;
}
}
and now every time the keyboard will open, you'll have callback function to your UIViewScroll class. +it'll know the keyboard size that opened, because keyboard sizes might be different from phone to phone
edit:
you don't have to use custom view class, you can register any class
class myClass : anyClass{
override init(...)
{
super.init(...)
//on ViewController class do it on ViewDidLoad
KeyboardUtils.instance.keyBoarEventsCallBacks.append((onShow:self.onKeyboardAppear,onHide: self.onKeyboardHide));
}
func onKeyboardAppear(){
let keyboardSize =KeyboardUtils.lastKeyboardSize.height;
//do your stuff
}
func onKeyboardHide(){
let keyboardSize =KeyboardUtils.lastKeyboardSize.height;
//do your stuff
}
}