0

I'm trying to get my app to move the view when the keyboard appears, and so far the results have been... mixed to say the least. I can get it to move, thing is it's either hard coded or only works partially.

I have multiple Textfields in my view, when I tap on them, sometimes depending on where my scroll is it get's hidden by the keyboard.

Now what I need my app to do is to move the view to see the textfield only if the active textfield is hidden by the keyboard.

My Hierarchy for the view goes like this :

Scene Hierarchy

So I have a Scroll view, and in the scroll View I have a UIView named ContentView, in the ContentView I have all my textfields and labels.

thing is, I can't hard code it since My app is universal, I need to have the keyboard move the view only if it hides the textfield. Because in a situation where the user is on an iPad, the View will likely never have to move

I used the following Stack overflow answers with no results :

Swift: Scroll View only when a TextField or Button is hidden by the Keyboard

Move view with keyboard using Swift

here's my code that actually comes from one of those answers :

override func viewDidLoad() {
    super.viewDidLoad()

    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(NewPaxController.keyboardWillShow), name: UIKeyboardWillShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(NewPaxController.keyboardWillHide), name: UIKeyboardWillHideNotification, object: nil)

}



func keyboardWillShow(notification:NSNotification) {
    if keyboardIsPresent == false {
        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            self.ContentView.frame.origin.y -= keyboardSize.height
            keyboardIsPresent = true
        }
    }
}

func keyboardWillHide(notification:NSNotification) {
    if keyboardIsPresent == true {
        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            self.ContentView.frame.origin.y += keyboardSize.height
            keyboardIsPresent = false
        }
    }
}

I'm almost 100% sure all my error come from the fact the I have a ContentView... but I need it in my case. Thanks in advance for your help

Community
  • 1
  • 1
Jp4Real
  • 1,982
  • 4
  • 18
  • 33
  • 1
    Will it help if I give you the code in Objective c for this ? I don't know swift – Abdul91 Apr 04 '16 at 14:46
  • it sure can ! I don't know objective C but I can probably get the right Idea from it. Plus I've converted Obj-C code to fit my needs before ! I'll let you know if i'm lost – Jp4Real Apr 04 '16 at 14:48
  • 1
    The problem is that it always moves the view no matter if the text field is hidden or not? – Lorenzo Piccoli Módolo Apr 04 '16 at 14:56
  • Yes ! also when i dismiss the keyboard is dismissed the view scrolls Down wayyy too much leaving an empty space the height of the keyboard above it – Jp4Real Apr 04 '16 at 15:00

3 Answers3

1

Here is the code for it, It is pretty straight forward, but if you still need help with it, I will explain it further.

#pragma mark - Keyboard Observer events

-(void)keyboardWillShow:(NSNotification*)notification {

    NSDictionary *info = [notification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    keyboardHeight = kbSize.height;
    [self updateScrollViewPosition];
}

-(void)keyboardDidChange:(NSNotification *)notification {

    NSDictionary *info = [notification userInfo];
    CGSize kbSizeBegin = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGSize kbSizeEnd = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
    if (kbSizeBegin.height != kbSizeEnd.height) {
        keyboardHeight = kbSizeEnd.height;
        if (activeTextField && [activeTextField isFirstResponder]) {
            [self updateScrollViewPosition];
        }
    }
}

-(void)keyboardWillHide:(NSNotification*)notification {

    keyboardHeight = 0;
    activeTextField = nil;
    [self resignAllTextFields];
}

#pragma mark - UITextFieldDelegate Methods

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
    activeTextField = textField;
    return YES;
}

- (void)textFieldDidBeginEditing:(UITextField *)textField {

    activeTextField = textField;
    [self updateScrollViewPosition];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {

    keyboardHeight = 0;
    activeTextField = nil;
    [textField resignFirstResponder];
    return YES;
}

#pragma mark - Update Method

-(void)updateScrollViewPosition {

    if (keyboardHeight > 0 && activeTextField) {
        CGRect frame = activeTextField.frame;
        CGFloat yPoint = scrollView.frame.origin.y+frame.origin.y+frame.size.height+8.0;
        CGFloat height = self.view.frame.size.height-keyboardHeight;
        CGFloat diff = yPoint-height;
        if (diff > 0.0) {
            [scrollView setContentOffset:CGPointMake(0, diff) animated:YES];
        }
        else {
            CGFloat diff = scrollView.contentSize.height-scrollView.contentOffset.y;
            if (diff<scrollView.frame.size.height) {
                diff = scrollView.contentSize.height-scrollView.frame.size.height;
                if (diff < 0) {
                    diff = 0.0;
                }
                [scrollView setContentOffset:CGPointMake(0, diff) animated:YES];
            }
        }
    }
    else {
        CGFloat diff = scrollView.contentSize.height-scrollView.contentOffset.y;
        if (diff<scrollView.frame.size.height) {
            diff = scrollView.contentSize.height-scrollView.frame.size.height;
            if (diff < 0) {
                diff = 0.0;
            }
            [scrollView setContentOffset:CGPointMake(0, diff) animated:YES];
        }
    }
}

#pragma mark

Edit: A simple resignAllTextFields method as requested. containerView is the view which contains all the UITextField.

-(void)resignAllTextFields {

    for (UIView *view in containerView.subviews) {
        if ([view isKindOfClass:[UITextField class]]) {
            UITextField *textField = (UITextField*)view;
            [textField resignFirstResponder];
        }
    }
    [self updateScrollViewPosition];
}
Abdul91
  • 708
  • 5
  • 16
  • Thanks a lot Abdul, I'll keep on trying to convert it but I thought it was a mere addition to my chunk of code, that 's a lot of Obj-c !!... I unfortunately don't have enough knowledge to use it ! – Jp4Real Apr 04 '16 at 15:41
  • can you please provide the code for resignAllTextFields, thanks – Ing. Ron Dec 27 '16 at 19:59
  • it usually depends on hierarchy of the your view, it only contains the code of textfield/textview objects calling resignFirstResponder. If your view is complex, i can write a recursive code for it. If it is a simpler one like your fields are present in one parent view, you can just put a for loop on its subviews and call this function if the subview is textfield/textview. – Abdul91 Dec 28 '16 at 09:49
1

You should not modify frames when keyboard shows up.

Instead you need to set the bottom inset of the scrollview scrollView.contentInset.bottom from zero to keyboard height. When the keyboard disappears, you set the inset back to zero.

This whole problem is solvable by mere 10 lines of code.

literally get the keyboard height from notification, store it ion a local variable in the class while KB is showing, and use the value to set insets in delegate callback/event handler methods.

The trick here is that setting nonzero insets will effectively scroll the scrollview together with the content for you up by and that will push the current textfield up as well.

Earl Grey
  • 7,426
  • 6
  • 39
  • 59
  • Aside from setting the scrollView inset, isn't getting my keyboard height exactly what I am doing in my KeyboardWillShow method ?? – Jp4Real Apr 04 '16 at 15:13
  • I just did this in my code, trying to simply modify `self.ContentView.frame.origin.y -= keyboardSize.height` to `self.ScrollView.contentInset.bottom -= keyboardSize.height` and well the view does not move at all... – Jp4Real Apr 04 '16 at 15:31
  • 1
    Well you did set a negative inset, you should set the inset to kb height instead...not substract the height from original inset – Earl Grey Apr 04 '16 at 16:04
  • that was it ! simply changing `self.ContentView.frame.origin.y -= keyboardSize.height` to `self.ScrollView.contentInset.bottom = keyboardSize.height` !! thanks so much ! – Jp4Real Apr 04 '16 at 17:37
1

You should first try to locate the UITextField instance by using the following code.

extension UIView {

    func firstResponder() -> UIView? {
        if self.isFirstResponder() {
            return self
        }

        for subview in self.subviews {
            if subview.isFirstResponder() {
                return subview
            }
        }

        return nil
    }

}

In the keyboardWillShow: function decide if the textfield is visible or not if the keyboard comes up.

func keyboardWillShow(notification:NSNotification) {
    if keyboardIsPresent == false {
        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue(),
            let inputView = self.ContentView.firstResponder()
            where inputView.frame.maxY > self.ContentView.frame.size.height - keyboardSize.height {

            self.ContentView.frame.origin.y -= keyboardSize.height
            keyboardIsPresent = true

        }
    }
}

Than only move the view back in the hide function if it was moved away.

Kádi
  • 2,796
  • 1
  • 16
  • 22
  • great Idea, this kinda works but : If the predictive text is enabled it covers part of the textfield (the one in the middle), also when my view comes back down I have a huge empty space at the top ! – Jp4Real Apr 04 '16 at 15:24