I am developing an iOS app and
I have a messaging view where I want to handle this situation:
-> I have a input view at the bottom of the view, that needs to be visible all the time except for some criteria where user is blocked/restricted to send message.
-> when the input view is focused, keyboard appears, I want to move the view along with keyboard frame.
-> I want keyboard to dismiss interactively with table view scrolling. With this being said, the view should respond to keyboard pan gesture and move along with as well
-> I tried using input accessory view but problem with that was when keyboard gets dismissed with table view scrolling, input view gets dismissed as well.
-> I also tried using willShow/willHide/willChangeFrame observers but with this, response is not to the point and it doesn't respond to keyboard interactive dismissal.
Anybody got solution to this...
Thanks for your time.
Swift 3+:
I have take a view into textview background and set the constraint of view (leading, trailing, bottom, fixed height). Create a #IBOutlet for bottom constraint and manage that below code:
class ViewController: UIViewController {
#IBOutlet var bottomConstraint: NSLayoutConstraint!
#IBOutlet var view_TextViewBg: UIView!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardDidShow(_:)),
name: NSNotification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardDidHide(_:)),
name: NSNotification.Name.UIKeyboardWillHide,
object: nil)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
// tap.delegate = self
view.addGestureRecognizer(tap)
}
func handleTap(sender: UITapGestureRecognizer? = nil) {
//dissmiss your keyboard here
}
//MARK: Keyboard show
func keyboardDidShow(_ notification: Notification) {
let params = notification.userInfo
let rect: CGRect? = (params?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue
bottomConstraint.constant = (rect?.size.height)!
}
//MARK: Keyboard hide
func keyboardDidHide(_ notification: Notification) {
bottomConstraint.constant = 0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Related
I try to resize the height of a UITextView-field when the keyboard appears (iOS 14.2, Xcode 12.3). The distance from the bottom of the UITextView to the save area is 90 and hence the lower part of it is hidden by the keyboard and can't be seen while editing.
I tried it with the solution shown here: Resize the UITextView when keyboard appears
Accordingly, my code is as follows:
class EditierenVC: UIViewController, UITextFieldDelegate, UITextViewDelegate {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(false)
NotificationCenter.default.addObserver(
self,
selector: #selector(EditierenVC.handleKeyboardDidShow(notification:)),
name: UIResponder.keyboardDidShowNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(EditierenVC.handleKeyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
#objc func handleKeyboardDidShow(notification: NSNotification) {
guard let endframeKeyboard = notification
.userInfo![UIResponder.keyboardFrameEndUserInfoKey]
as? CGRect else { return }
textfeld.contentInset = UIEdgeInsets(
top: 0.0,
left: 0.0,
bottom: endframeKeyboard.size.height-85,
right: 0.0
)
view.layoutIfNeeded()
}
#objc func handleKeyboardWillHide() {
textfeld.contentInset = .zero
view.layoutIfNeeded()
}
//**************************
// MARK: - Views
//**************************
#IBOutlet weak var textfeld: UITextView!
Unfortunately the inset-size is not changed, when the keyboard appears and the text is partly hidden.
Has anyone an idea, why it is not working?
Thanks for your support
I could not come up with a solution by using content inset but I can suggest another way.
If you add bottom constraint to the textView and create outlet for that, you can change its constant value in notifications;
#objc func handleKeyboardDidShow(notification: NSNotification) {
guard let endframeKeyboard = notification
.userInfo![UIResponder.keyboardFrameEndUserInfoKey]
as? CGRect else { return }
textViewBottomConstraint.constant = endframeKeyboard.size.height-85
}
#objc func handleKeyboardWillHide() {
textViewBottomConstraint.constant = // set the previous value here
}
What is the best way to fix keyboard overlapping without frameworks like IQKeyboardManagerSwift or IHKeyboardAvoiding?
Set your scrollview bottom layout constraint,
This makes all textfields available by scrolling and you don't need to think about other devices and keyboard height differences.
#IBOutlet weak var mainScrollViewBottomConstraint: NSLayoutConstraint!
open override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWillShow(_ notification: Notification) {
let userInfo = (notification as NSNotification).userInfo!
let keyboardSize = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
mainScrollViewBottomConstraint.constant = keyboardSize.height
}
func keyboardWillHide(_ notification: Notification) {
mainScrollViewBottomConstraint.constant = 0
}
EDIT: I made a video about my solution.
https://youtu.be/gS4AKcJAg3Y
The best way is, set a top constraint for your view and animate the view using constraint when click in your UITextfield.
#IBOutlet var constraintViewTop: NSLayoutConstraint!
Animate the view up in "textFieldDidBeginEditing' delegate.
func textFieldDidBeginEditing(_ textField: UITextField) {
switch textField {
case yourTextfield:
//This is the code for view up
UIView.animate(withDuration: 0.45, animations: {
self. constraintViewTop.constant = -172
})
default:
//This the code for view down
UIView.animate(withDuration: 0.45, animations: {
self. constraintViewTop.constant = 0
})
}
}
I have found the best way to handle keyboard overlapping issues, just embed(implement) all the view and textfields in the static table view controller and the system automatically will move up the keyboard by itself. If you don't like this way, go for IQKeyboardManager pod.
In my main app view which contains both a UITextField and a UITableView, I have the "usual" code using a UITapGestureRecognizer to dismiss the virtual keyboard if a tap is detected outside of the keyboard while I'm editing the contents of the UITextField.
An added feature is that this is only enabled if the virtual keyboard is actually shown - I don't want "background taps" to cause editing to end if the virtual keyboard isn't visible, but nor do I want background taps to trigger their normal behaviour - they should be consumed if the virtual keyboard is currently showing.
override func viewDidLoad() {
...
tapper = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShown), name:
NSNotification.Name(rawValue: "UIKeyboardDidShowNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHidden), name:
NSNotification.Name(rawValue: "UIKeyboardDidHideNotification"), object: nil)
}
#IBAction func keyboardShown(_ sender: AnyObject) {
view.addGestureRecognizer(tapper!)
}
#IBAction func keyboardHidden(_ sender: AnyObject) {
view.removeGestureRecognizer(tapper!)
}
#IBAction func viewTapped(_ sender: AnyObject) {
view.endEditing(false)
}
This mostly works, except that the UITableView has interactive header cells which each also have a UITapGestureRecognizer attached.
The net result is that if I click on a header cell the gesture recognizer on that cell gets fired, and not the one on the parent view, and the keyboard doesn't get dismissed. If I click on the data cells instead, everything works fine.
If it matters, my UITableView has its own UIViewController subclass and is contained in a nested UIView - the table is too complicated to have that code in my main view controller.
How can I prevent the sub-view's gesture recognizers from handling these taps when the parent view's recognizer is attached so that the parent view can handle them instead?
I've implemented what I consider a "temporary" solution by also observing the virtual keyboard notifications in the UITableView's controller, tracking the keyboard visibility state, and then implementing this UIGestureRecognizerDelegate method on the header cells' gesture recognizer:
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return !keyboardShowing
}
This duplicates a certain amount of functionality from the main view in the sub-view which really shouldn't need to know about the state of the keyboard. I'm still looking for a method that can be handled entirely from within the parent view.
EDIT - with thanks to #Tommy for the hint, I now have a better solution that removes any need to track the keyboard state in the sub-view.
My parent view no longer uses a UIGestureRecognizer, but instead uses a custom subclass of UIView to track touch events, and conditionally ignore them:
class KeyboardDismissingView: UIView {
private var keyboardVisible = false
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let r = super.hitTest(point, with: event) else { return nil }
var v : UIView! = r
while v != nil {
if v is UITextField {
return r
}
v = v.superview
}
if keyboardVisible {
self.endEditing(false)
return nil
}
return r
}
func setup() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShown), name:
NSNotification.Name(rawValue: "UIKeyboardDidShowNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHidden), name:
NSNotification.Name(rawValue: "UIKeyboardDidHideNotification"), object: nil)
}
#IBAction func keyboardShown(_ sender: AnyObject) {
keyboardVisible = true
}
#IBAction func keyboardHidden(_ sender: AnyObject) {
keyboardVisible = false
}
override init (frame: CGRect) {
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
}
I am creating a iOS application for a student registration. My problem is I cannot get the auto layout to work for all the devices. I want to layout my interface with even spacing between each label and textview. Any help will be much appreciated. storyboard capture here
#IBOutlet weak var bottomconst: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: nil);
//
// NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyBoardDidShow:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardWillShowNotification, object: nil)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
func keyboardWasShown(notification: NSNotification) {
let info = notification.userInfo!
let keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
//bottomspace = self.bottomconst.constant;
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.layoutIfNeeded()
self.bottomconst.constant = keyboardFrame.size.height + 20
})
}
func keyboardWillHide(sender: NSNotification) {
if let keyboardSize = (sender.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.bottomconst?.constant -= keyboardSize.height
}
}
I am attaching a screenshot with constraints so that you can basic idea how to put proper constraints, but in your case it would be better to go with table view if your number of fields increases then all your fields would come automatically inside scrollView and it would be easliy visible in smaller devices, But you can have look into this screenshot if you don't want to use tableview and have less number of fields.
A UITableViewController would be the best approach.
Just keep in mind that UIStackView provides a streamlined way to automatically lay out a collection of views. It handles the alignment and spacing for you:
To replicate:
Create a blank single-view project
Drag a TextField on to the canvas
Set the TextField keyboardAppearance to Dark
Run the app on iPad (device or simulator)
Touch the TextField to bring up the keyboard (it is dark)
Press Home, then come back into the app
Notice the keyboard changes colour (to white).
Presumably the keyboard colour changes to match the background. However in this case some of the keys remain dark, so this seems like a bug in iOS (see attached screenshot).
Anyone care to shed any light on this? We're using a workaround which involves hiding the keyboard and re-showing it, but this isn't ideal.
This code can close the keyboard when Home button is pressed and bring it back when the app re-starts. You need to set UITextFields delegate to the view controller:
class ViewController: UIViewController, UITextFieldDelegate {
private var _textField: UITextField!
private var _isFirstResponder: Bool!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "didBecomeActiveNotification:", name: UIApplicationDidBecomeActiveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "willResignActiveNotification:", name: UIApplicationWillResignActiveNotification, object: nil)
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func didBecomeActiveNotification(nofication: NSNotification) {
if _isFirstResponder? == true {
_textField?.becomeFirstResponder()
}
}
func willResignActiveNotification(nofication: NSNotification) {
if _textField?.isFirstResponder() == true {
_isFirstResponder = true
_textField?.resignFirstResponder()
} else {
_isFirstResponder = false
}
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
_textField = textField
return true
}
}