I have a scenario were i move the view up when the keyboard appears, this scenario works fine however as soon as i start typing the view goes back to its original position. Something to note is that the textfield is in a stackview.
My question is that is there a way i can stop the view from going back to its original position when textEditing begins.
This is my code :
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)
}
var isScroll = false
#objc func keyboardWillShow(sender: NSNotification) {
mainView.frame.origin.y = -100
}
#objc func keyboardWillHide(sender: NSNotification) {
mainView.frame.origin.y = 0
}
add a scrollview as your base view and inside this add your contentview then
func keyboardShown(_ notification: Notification){
var userInfo = notification.userInfo!
let keyboardSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
let contentInsets = UIEdgeInsetsMake(0.0, 0.0, (keyboardSize!.height + 40), 0.0)
self.mainScrollView.contentInset = contentInsets
self.mainScrollView.scrollIndicatorInsets = contentInsets
// **-- Scroll when keyboard shows up
let aRect = self.view.frame
self.mainScrollView.contentSize = aRect.size
/* if((self.activeTextField) != nil)
{
self.scrollView.scrollRectToVisible(self.activeTextField!.frame, animated: true)
}*/
}
func keyboardHidden(_ notification: Notification) {
let contentInsets = UIEdgeInsets.zero
self.mainScrollView.contentInset = contentInsets
self.mainScrollView.scrollIndicatorInsets = contentInsets
// **-- Scroll when keyboard shows up
self.mainScrollView.contentSize = self.containerView.frame.size
}
My personal opinion to use this library else you can manage the whole UI into the UITableview so no need to manage keyboardWillShow and keyboardWillHide method in that controller.
Related
I have a Uiviewcontroller, in this viewcontroller I put a uiview called categUIV. The categUIV contains uitexfields and a button. The idea is to move the hole categUIV up when the keyboard is up.
I use the code below
override func viewDidLoad() {
super.viewDidLoad()
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() {
self.categUIV.frame.origin.y -= keyboardSize.height
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.categUIV.frame.origin.y += keyboardSize.height
}
}
Now when I click to a uitextfied the keyboard pops up and categUIV pops up correctly, the problem is when I start typing the categUIV go back to his initial position by itself.
I don’t know why but i wonder is that maybe because of the constraints of categUIV?
The problem is with frame-layout things don't work as expected , Try autolayout , hook the bottom constraint of the categUIV as IBOutlet and do this
#objc func handleKeyboardDidShow (notification: NSNotification)
{
let keyboardRectAsObject = notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue
var keyboardRect = CGRect.zero
keyboardRectAsObject.getValue(&keyboardRect)
self.categUIVBotcon.constant = -1 * keyboardRect.height
UIView.animate(withDuration: 0.5,animations: {
self.view.layoutIfNeeded()
})
}
#objc func handleKeyboardWillHide(notification: NSNotification)
{
self.categUIVBotcon.constant = 0
UIView.animate(withDuration: 0.5,animations: {
self.view.layoutIfNeeded()
})
}
To make this work, you need to put your view into a scroll view so that your hierarchy looks like this:
- View Controller
- View
- Scroll View
- Content View
- categUIV
You must also connect the scroll view and the categUIV to your code:
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var categUIV: UIView!
You can then register your keyboard observers:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
With the following functions:
func keyboardWasShown(notification: NSNotification) {
self.keyboardSize = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.size
self.scrollView.isScrollEnabled = true
let contentInsets = UIEdgeInsetsMake(0.0, 0.0, self.keyboardSize.height, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var rect = self.view.frame
rect.size.height -= self.keyboardSize.height
if !rect.contains(self.categUIV.frame.origin) {
self.scrollView.scrollRectToVisible(self.categUIV.frame, animated: true)
}
}
func keyboardWillBeHidden(notification: NSNotification) {
let keyboardSize = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.size
let contentInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize.height, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
self.scrollView.isScrollEnabled = false
}
I have a view inside that I have 6 text fields but I want to move up the view when click on last text field that I can Enter value in last text field.
Here is my code but it is working for all text fields :-
func animateViewMoving (up:Bool, moveValue :CGFloat){
let movementDuration:TimeInterval = 0.3
let movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations("animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}
So for particular textField how can I move the view up (Keyboard size)
Thanks
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var contentView: UIView!
var textFieldActive:UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//Keyboard notification
NotificationCenter.default.addObserver(self,selector: #selector(keyboardWasShown),name:NSNotification.Name.UIKeyboardDidShow,
object: nil)
NotificationCenter.default.addObserver(self,selector: #selector(keyboardWillHide),name:NSNotification.Name.UIKeyboardWillHide,
object: nil)
// Do any additional setup after loading the view.
}
func textFieldDidBeginEditing(_ textField: UITextField){
textFieldActive = textField
}
// MARK: - Keyboard notification Method
func keyboardWasShown(notification: NSNotification)
{
let userInfo = notification.userInfo
let keyboardSize = (userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
let contentInset = UIEdgeInsetsMake(0.0, 0.0, (keyboardSize?.height)!+100, 0.0)
self.scrollViewGroupMakeTakers.contentInset = contentInset
self.scrollViewGroupMakeTakers.scrollIndicatorInsets = contentInset
// Step 3: Scroll the target text field into view.
var aRect: CGRect = self.contentViewMakeTakers.frame;
aRect.size.height -= (keyboardSize?.height)!+100
if (!aRect.contains(textFieldActive!.frame.origin))
{
let scrollPoint: CGPoint = CGPoint(x: 0.0, y: textFieldActive!.frame.origin.y - (keyboardSize!.height-150));
self.scrollViewGroupMakeTakers.setContentOffset(scrollPoint, animated: true)
}
}
func keyboardWillHide(notification: NSNotification)
{
let contentInsets : UIEdgeInsets = UIEdgeInsets.zero
scrollViewGroupMakeTakers.contentInset = contentInsets;
scrollViewGroupMakeTakers.scrollIndicatorInsets = contentInsets;
}
Try this-
func textFieldShouldReturn(textField: UITextField) -> Bool {
if textField == lastTextField{
view.frame.origin.y -= 50 //Or 100, 200 as you required
performAction()
}
}
func performAction() {
lastTextField.becomeFirstResponder()
otherTextField.resignFirstResponder()
}
Assuming you are using autoLayout
I am imagining all your UITextField to be present inside a separate UIScrollView. Further this UISCrollView will have a bottom constraint to its superView. Create an IBOutlet for this bottom spacing vertical constraint to its super view.
Note: You can do this even if its not inside a UIScrollView by simply taking the bottom constraint of your lowest UITextField but that will force your other UITextFields off screen so best put it inside a UIScrollView.
In your viewDidLoad setup observers as follows -
func viewDidLoad(){
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
//and then:
//MARK: Animate Keyboard
func keyboardWillShow(notification : NSNotification){
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
animateKeyboard(withConstraintValue: keyboardSize.size.height)
}
}
func keyboardWillHide(notification : NSNotification){
animateKeyboard(withConstraintValue: 0)
}
func animateKeyboard(withConstraintValue: CGFloat){
scrollViewBottomSpaceConstraint.constant = withConstraintValue
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
}
Description
I have a few UITextField which I want to scroll up, when one of them is covered by keyboard during edition. There are a tons of answers here on SO with many flavors: moving a view (by changing its frame), modifying a constraint, using UIScrollView and UITableView, or using UIScrollView and modifying contentInset.
I decided to use the last one. This one is also described by Apple, and has a Swift version on SO as well as being described on this blog including a sample project on the GitHub.
Partial code
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = nil
}
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func keyboardWillShow(notification: NSNotification) {
if let activeField = self.activeField, keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect = self.view.frame
aRect.size.height -= keyboardSize.size.height
if (!CGRectContainsPoint(aRect, activeField.frame.origin)) {
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification) {
let contentInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
}
Issue
I want just one simple modification - a little more space between the keyboard and an edited field. Because out of the box it looks like this:
I modified CGRect in scrollRectToVisible call, but it changed nothing. What's more, even commenting out the line with scrollRectToVisible had no effect at all - everything worked exactly as before (including scrolling up the content). Checked on iOS 9.2
Replicating, if needed, is trivial - just download the working code using the GitHub link above and comment out the scrollRectToVisible line.
Tested workarounds
Workarounds I tried, but didn't like the final effect:
Increasing contentInset - user could scroll up more then the contentSize
Replacing UIKeyboardDidShowNotification with UIKeyboardWillShowNotification, add another UIKeyboardDidShowNotification observer with just scrollRectToVisible inside - it works, but then there are two scroll up animations, which doesn't look good.
Questions
Why changing contentInset (without scrollRectToVisible call) scrolls content in the scrollView? I've not see in any docs information about such behavior
And more important - what to do, to scroll it up a little more, to have some space between an edited text field and the keyboard?
What did I miss? Is there some easy way to fix it? Or maybe it's better to play with scrollView.contentSize, instead of contentInset?
I haven't found why scrollRectToVisible doesn't work in the scenario described above, however I found other solution which works. In the mentioned Apple article Managing the Keyboard at the very bottom there is a hint
There are other ways you can scroll the edited area in a scroll view
above an obscuring keyboard. Instead of altering the bottom content
inset of the scroll view, you can extend the height of the content
view by the height of the keyboard and then scroll the edited text
object into view.
Solution below is based exactly on extending the height of the content view (scrollView.contentSize). It takes into account orientation change and scrolling back when keyboard is being hidden. Works as required - has some space between the active field and the keyboard, see the image below
Working code
I've placed the full working code on the GitHub: ScrollViewOnKeyboardShow
var animateContenetView = true
var originalContentOffset: CGPoint?
var isKeyboardVisible = false
let offset : CGFloat = 18
override func viewDidLoad() {
super.viewDidLoad()
for case let textField as UITextField in contentView.subviews {
textField.delegate = self
}
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: #selector(ViewController.keyboardWillBeShown(_:)), name: UIKeyboardWillShowNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(ViewController.keyboardWillBeHidden(_:)), name: UIKeyboardWillHideNotification, object: nil)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = nil
}
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func keyboardWillBeShown(notification: NSNotification) {
originalContentOffset = scrollView.contentOffset
if let activeField = self.activeField, keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
var visibleRect = self.scrollView.bounds
visibleRect.size.height -= keyboardSize.size.height
//that's to avoid enlarging contentSize multiple times in case of many UITextFields,
//when user changes an edited text field
if isKeyboardVisible == false {
scrollView.contentSize.height += keyboardSize.height
}
//scroll only if the keyboard would cover a bottom edge of an
//active field (including the given offset)
let activeFieldBottomY = activeField.frame.origin.y + activeField.frame.size.height + offset
let activeFieldBottomPoint = CGPoint(x: activeField.frame.origin.x, y: activeFieldBottomY)
if (!CGRectContainsPoint(visibleRect, activeFieldBottomPoint)) {
var scrollToPointY = activeFieldBottomY - (self.scrollView.bounds.height - keyboardSize.size.height)
scrollToPointY = min(scrollToPointY, scrollView.contentSize.height - scrollView.frame.size.height)
scrollView.setContentOffset(CGPoint(x: 0, y: scrollToPointY), animated: animateContenetView)
}
}
isKeyboardVisible = true
}
func keyboardWillBeHidden(notification: NSNotification) {
scrollView.contentSize.height = contentView.frame.size.height
if var contentOffset = originalContentOffset {
contentOffset.y = min(contentOffset.y, scrollView.contentSize.height - scrollView.frame.size.height)
scrollView.setContentOffset(contentOffset, animated: animateContenetView)
}
isKeyboardVisible = false
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil) { (_) -> Void in
self.scrollView.contentSize.height = self.contentView.frame.height
}
}
deinit {
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
notificationCenter.removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
EDIT: Adding in viewDidLoad code
So i had done some research to learn how to move one of my text views so that when the keyboard popped up, it scrolled the view up so that the keyboard was not covering the textview. I have seemed to be able to get that work, but now my issue is that after dismissing the keyboard, the view stays where it was. It does not 'snap' back to its original it was in position prior to the keyboard popping up and scrolling the textview out of the way. Am i missing something in my code? Or do i need to add something?
override func viewDidLoad() {
super.viewDidLoad()
self.questionInput.delegate = self
self.answerInput.delegate = self
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target:self, action: "DismissKeyboard")
view.addGestureRecognizer(tap)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
}
var activeView: UITextView?
func textViewDidEndEditing(textView: UITextView) {
self.activeView = nil
}
func textViewDidBeginEditing(textView: UITextView) {
self.activeView = textView
}
func keyboardDidShow(notification: NSNotification) {
if let activeView = self.answerInput, keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect = self.view.frame
aRect.size.height -= keyboardSize.size.height
if (!CGRectContainsPoint(aRect, activeView.frame.origin)) {
self.scrollView.scrollRectToVisible(activeView.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification) {
let contentInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
}
My second question was i had read about needing to unregister for keyboard notifications this was the exact quote..
"Do not forget to unregister from these events when you are transitioning away from your view controller."
What exactly does this mean? Does code needed to be added when segueing?
Any help is appreciated as i am quite stuck with both these questions. Thank you.
If you're in the inital stage of the development you can use this library which will take care of keyboard handling throughout your project
https://github.com/hackiftekhar/IQKeyboardManager
regarding your second question here is the sample code that you'll need to implement
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: nil);
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self);
}
If you use autolayout you need to implement this instead
func keyboardWasShown(notification: NSNotification) {
var info = notification.userInfo!
var keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.bottomConstraint.constant = keyboardFrame.size.height + 20
})
}
Try this
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
let insets: UIEdgeInsets = UIEdgeInsetsMake(self.scrollView.contentInset.top, 0, keyboardSize.height, 0)
self.scrollView.contentInset = insets
self.scrollView.scrollIndicatorInsets = insets
}
}
I'm using some functions to move the UITextFieldsimmediately after InputView, see my code below:
func DismissKeyboard(){
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.registerForKeyboardNotifications()
}
override func viewDidDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func registerForKeyboardNotifications() {
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self,
selector: "keyboardWillBeShown:",
name: UIKeyboardWillShowNotification,
object: nil)
notificationCenter.addObserver(self,
selector: "keyboardWillBeHidden:",
name: UIKeyboardWillHideNotification,
object: nil)
}
func keyboardWillBeShown(sender: NSNotification) {
let info: NSDictionary = sender.userInfo!
let value: NSValue = info.valueForKey(UIKeyboardFrameBeginUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.CGRectValue().size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height + 8, 0.0)
backgroundScrollView.contentInset = contentInsets
backgroundScrollView.scrollIndicatorInsets = contentInsets
}
// Called when the UIKeyboardWillHideNotification is sent
func keyboardWillBeHidden(sender: NSNotification) {
let insets: UIEdgeInsets = UIEdgeInsetsMake(self.backgroundScrollView.contentInset.top, 0, 0, 0)
//let insets: UIEdgeInsets = UIEdgeInsetsZero
backgroundScrollView.contentInset = insets
backgroundScrollView.scrollIndicatorInsets = insets
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
What is happening is, after tap on the first UITextField, backgroundScrollView stop scroll, and my app there are lot of fields that need to scroll screen to see.
What may I doing wrong?
In keyboardWillBeShown() method setcontentSize: -
backgroundScrollView.contentSize = CGSizeMake(0, 600)
0 "width" means -> Dont scroll in x direction
600 "height" means -> Scroll content can be scroll upto 600 height