I am creating an app with swift and I subclassed a view controller in which I add a tap gesture recognizer and an NSNotification that listens for the keyboard appearing. I put the selector for the keyboardWillShow in a function in my base view controller. When I subclassed my view controller, however, and I had the keyboard show, my app terminated with an NSException that said that it could not find the selector. Can anyone explain why this happened and how I can fix it?
Here is the functionality in my base view controller:
override func viewDidLoad() {
super.viewDidLoad()
setNotificationListers()
setTapGestureRecognizer()
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func setNotificationListers() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
func setTapGestureRecognizer() {
let tapped = UITapGestureRecognizer(target: self, action: "closeKeyboard")
tapped.numberOfTapsRequired = 1
self.view.addGestureRecognizer(tapped)
}
func closeKeyboard() {
self.view.endEditing(true)
}
func keyboardWillShow() {
self.view.frame.origin.y += CGFloat(keyboardHeight)
}
func keyboardWillHide() {
self.view.frame.origin.y -= CGFloat(keyboardHeight)
}
I did not override anything in my subclass. What will be inherited and what will not?
Thanks in advance for any help!
Your selector declaration require a parameter, but your function does not require a parameter.
Either remove : from your selector declaration
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow"), name: UIKeyboardWillShowNotification, object: nil)
or change your function
func keyboardWillShow(notification: NSNotification) {
self.view.frame.origin.y += CGFloat(keyboardHeight)
}
and do the same for keyboardWillHide.
Note that the Selector method have a :, that means your method need have a parameter.
So, you should change two your methods to this:
func keyboardWillShow(notification: NSNotification) {
self.view.frame.origin.y += CGFloat(keyboardHeight)
}
func keyboardWillHide(notification: NSNotification) {
self.view.frame.origin.y -= CGFloat(keyboardHeight)
}
Anyway, xCode 7.3 has change the way to implement selector. And you can use this great lib https://github.com/hackiftekhar/IQKeyboardManager to handle the keyboard pushing up. Very simple to use.
Related
This is my view.
When I click inside the text view the keyboard was coming on top. so I added made a class and in that class, I added these functions.
var objectObserver:UIViewController?
func setKeyboardResponsiviness(observer:UIViewController){
objectObserver = observer
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if objectObserver!.view.frame.origin.y == 0 {
objectObserver!.view.frame.origin.y -= keyboardSize.height
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if objectObserver!.view.frame.origin.y != 0 {
objectObserver!.view.frame.origin.y = 0
}
}
After adding the code the whole screen slides up which was the intended goal but as a side effect, half of the text view is out of the screen. Any idea how I can fix this?
The simple solution is to use IQKeyboardManagerSwift.
pod 'IQKeyboardManagerSwift' // add this in your pod file.
Add the following code in didFinishLaunchingWithOptions.
IQKeyboardManager.shared.enable = true
I hope this helps.
I looked at and tried multiple solutions for Swift 3, Xcode 8 but couldn't get any to work. I've tried:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
and also setting a text field input as first responder:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
pressureInput.resignFirstResponder()
}
I don't know if something from Xcode 8 to Xcode 9 that cause these methods to not work, or if I messed elsewhere. I have 9 text fields and they've all set delegate to self. Their tags are incremented to move on to the next text field on pressing return. Don't think that would affect it. Sorry, new at this! The code runs fine with either of those attempted functions, but they keyboard stays. I would just like to dismiss keyboard when touched outside of any text field.
first of all write this extension in any swift file
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
}
func dismissKeyboard() {
view.endEditing(true)
}
}
Than in viewDidLoad of that View only call in any view controller there are textFields.
self.hideKeyboardWhenTappedAround()
Swift 4, 5. I always use hide keyboard when tapped around and return button.
override func viewDidLoad() {
super.viewDidLoad()
hideKeyboardWhenTappedAround()
emailTextField.delegate = self // your UITextfield
passwordTextField.delegate = self // your UITextfield
}
// Hide Keyboard
extension EmailAutorization: UITextFieldDelegate {
// Return button tapped
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
// Around tapped
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(EmailAutorization.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
Here is Solution of Dismiss Keyboard and Move View Up on Keyboard Open : Swift 5
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(taped))
view.addGestureRecognizer(tap)
NotificationCenter.default.addObserver(self, selector: #selector(KeyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(KeyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
//This Method Will Hide The Keyboard
#objc func taped(){
self.view.endEditing(true)
}
#objc func KeyboardWillShow(sender: NSNotification){
let keyboardSize : CGSize = ((sender.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size)!
if self.view.frame.origin.y == 0{
self.view.frame.origin.y -= keyboardSize.height
}
}
#objc func KeyboardWillHide(sender : NSNotification){
let keyboardSize : CGSize = ((sender.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size)!
if self.view.frame.origin.y != 0{
self.view.frame.origin.y += keyboardSize.height
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
Did you tried to debug the program to see if the code stops in the function at all(with break point)? Usually this code should work...Check if those textFields are in the super view or in a child view and if they are maybe you should call self.childView.endEditing(true).
If you really work with multiple textFields maybe you should try IQKeyboardManager library. I use it in all my projects. You can find it here: https://github.com/hackiftekhar/IQKeyboardManager. Simple to use and with good support. Just install it trough cocoa pods, put IQKeyboardManager.sharedManager().enable = true in the AppDelegate and you're ready to go. :)
Are you sure that touchesBegan is being called? If you're sure, try adding self.resignFirstResponder() to your touchesBegan function. This tells your view controller that it's no longer the first responder and should dismiss the keyboard.
If not, what you'll want to do is create a UITapGestureRecognizer, add it to your view, and wire it to a function that calls self.resignFirstResponder().
I'm using IQKeyboardManager to keep the text fields to go up after typing with the keyboard.
I don't want to scroll to a specific view even when clicked on the text field. Below is the screenshot of the design. I want the 'header' to remain on top.
From their documentation, there is a way to keep the navigation bar remain on top.
Disable the IQKeyboardManager for your ViewController.
for that,
IQKeyboardManager.sharedManager().disableInViewControllerClass(ViewController.self)
And In that viewController write the following code. It will move your view up as per keyboard height
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardSize.height
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y += keyboardSize.height
}
}
}
Now you want your "HEADER" view remain on TOP then,
Do like this :
**
YourViewController.view -> [headerView][contentView]
**
Put textfield in [contentView] And change [contentView].y instead of Self.view in above code.
Disable the IQKeyboardManager for your viewController:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
IQKeyboardManager.sharedManager().enable = false
NotificationCenter.default.addObserver(self, selector: #selector(Login.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(Login.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Handle keyboard:
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0{
self.table_view.frame.origin.y -= keyboardSize.height
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0{
self.table_view.frame.origin.y += keyboardSize.height
}
}
}
Remove observer:
override func viewWillDisappear(animated: Bool) {
IQKeyboardManager.sharedManager().enable = true
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Answers from #Wolverine and #Bhavin Ramani were great: the best way to keep your custom header staying at top is to manually handle your keyboard (according to IQKeyboardSwift author's comment). If you use iOS default navigation bar, it seems it's handled for you by the library.
Here I just want to share some updates on this topic, for my future reference, as the answers are a bit old and some Swift syntax has changed. Code below is written in Xcode 13.2, targeting iOS 13+.
Firstly, you want to disable KQKeyboardManager by doing
IQKeyboardManager.shared.enable = false
Please note this lines only disables moving text field up feature, other IQKeyboard features such as resign on touch outside, auto tool bar, and etc., are not disabled by this line, this is usually what you want.
Then, you register keyboard events observer in view controller's viewDidLoad, remove observers in deinit.
override func viewDidLoad() {
super.viewDidLoad()
IQKeyboardManager.shared.enable = false
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
deinit {
IQKeyboardManager.shared.enable = true
NotificationCenter.default.removeObserver(self)
}
Next, add view moving up/down methods for keyboard show/hide.
#objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
print("keyboardSize.height", keyboardSize.height)
// using the right key here is important, because
// keyboardFrameEndUserInfoKey is an user info key
// to retrieve the keyboard’s frame at the END of its animation.
// here you move up the views you need to move up
// if you use auto layout, update the corresponding constraints
// or you update the views' frame.origin.y
// you may want to do the updates within a 0.25s animation
}
}
#objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? CGRect {
// reset views to their original position on keyboard dismiss
}
}
You may also want to enable/disable auto tool bar, as it could make your keyboard height unstable.
// in viewDidLoad set to false, in deinit set back to true (if you need it)
IQKeyboardManager.shared.enableAutoToolbar = false
I've been trying to set up my keyboard since a few days ago, but I can't manage to do it. I just want the imageView to move up when I am writing in the bottom text field. In fact, it's working, but it also moves up when I try to write at the topTextField. I don't want that, because when that happens, I can't see the textField at the top, and I can't see what I'm writing.
I'll include my screenshots and my code.
In this image, I pressed the topTextField to write something, but as you can see, the topTextField is lost. I mean the view moves up when I press the topTextField and I don't want that. What I want is so that when I press the topTextField the keyboard should appear but the view should be at the same place.
And in the last one I pressed the textFieldBottom, and as you can see, it works. The view moves up so I can see what I'm writing inside the textFieldBottom.
Here is my code:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.subscribeToKeyboardNotifications()
self.subscribeToKeyboardNotificationsDown()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.unsubscribeToKeyBoardNotifications()
self.unsubscribeToKeyBoardNotificationsDown()
}
func subscribeToKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardWillShow(_:)) , name: UIKeyboardWillShowNotification, object: nil)
}
func unsubscribeToKeyBoardNotifications() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
view.frame.origin.y -= getKeyboardHeight(notification)
}
func subscribeToKeyboardNotificationsDown() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)
}
func unsubscribeToKeyBoardNotificationsDown() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillHide(notification: NSNotification) {
view.frame.origin.y += getKeyboardHeight(notification)
}
func getKeyboardHeight(notification:NSNotification) -> CGFloat {
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue
return keyboardSize.CGRectValue().height
}
// do one thing maintain one globle varible to save current textfield which is on editing mode based on that varible u put condition to move imageview
following example code will help you.
var currentTextField: UITextField
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
currentTextField = textField
return true
}
func keyboardWillShow(notification: NSNotification) {
if currentTextField == textFieldBottom {
view.frame.origin.y -= getKeyboardHeight(notification)
}
}
When topTextField becomes first responder, you're changing view.frame.origin, this is moving your topTextField up. Instead of changing view frame, you can set constraints for bottomTextField and you can change constraints for bottomTextField programmatically in keyboardWillShow method. This way your view frame will not change and topTextField will not move up.
Hi I am using this code to move my view when a textView is selected, this is to make sure my texView is visible for when the keyboard pops up.
override func viewDidLoad() {
super.viewDidLoad() NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: self.view.window)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: self.view.window)
}
func keyboardWillHide(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
self.view.frame.origin.y += keyboardSize.height
}
func keyboardWillShow(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
let offset: CGSize = userInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue.size
if keyboardSize.height == offset.height {
if self.view.frame.origin.y == 0 {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y -= keyboardSize.height
})
}
} else {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y += keyboardSize.height - offset.height
})
}
print(self.view.frame.origin.y)
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: self.view.window)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: self.view.window)
}
How can i only move the view if the bottom textView is selected? Because currently if you select the uppermost textView it moves half of it off screen.
I appreciate any help, thanks in advance.
In the notification callbacks check for bottomTextView.isFirstResponder() and move the view only if its true. Otherwise don't move the view.
Make your class a UITextViewDelegate like so:
class ViewController: UIViewController, UITextViewDelegate {
Then in viewDidLoad set only the bottom textView to be controlled by the delegate:
bottomTextView.delegate = self
Then you can use these functions, changing the values to suit your needs:
func textViewDidBeginEditing(textView: UITextView) {
self.view.frame.origin.y -= 150
}
func textViewDidEndEditing(textView: UITextView) {
self.view.frame.origin.y += 150
}
Rather than only moving the keyboard for a single text field, I suggest a more flexible approach.
What I do is to keep track of which text field is being selected and do some calculations to move the view controller's content view up just enough to expose the text field that is becoming active.
To do that you need to set up your view controller to be the text fields' delegates.
I have a development blog post that explains this in detail:
Shifting views to make room for the keyboard
The code in that post is in Objective-C, but the concepts are identical. I'm not sure I have the same code in Swift that I can share (The only code I've found is in a project I did for a client.)
That blog post references a project called RandomBlobs on Github.
I would suggest using a delegate. For example, you can set you bottom textView's delegate to self (add UITextViewDelegate first of course) and implement the
textViewDidBeginEditing(_:) method. When that's triggered, you will know that the user started editing this particular view.