Segev
Segev

Reputation: 19303

Trying to move the view up when the keyboard is showing

I trying to push my view up when the keyboard is showing (it overlays data I want the user to see while he's typing. I'm using this code:

KBKeyboardHandler.h:

@protocol KBKeyboardHandlerDelegate;

@interface KBKeyboardHandler : NSObject

- (id)init;

// Put 'weak' instead of 'assign' if you use ARC
@property(nonatomic, assign) id<KBKeyboardHandlerDelegate> delegate; 
@property(nonatomic) CGRect frame;

@end

KBKeyboardHandler.m:

#import "KBKeyboardHandler.h"
#import "KBKeyboardHandlerDelegate.h"

@implementation KBKeyboardHandler

- (id)init
{
    self = [super init];
    if (self)
    {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWillShow:)
                                                     name:UIKeyboardWillShowNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWillHide:)
                                                     name:UIKeyboardWillHideNotification
                                                   object:nil];
    }

    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}

@synthesize delegate;
@synthesize frame;

- (void)keyboardWillShow:(NSNotification *)notification
{
    CGRect oldFrame = self.frame;    
    [self retrieveFrameFromNotification:notification];

    if (oldFrame.size.height != self.frame.size.height)
    {
        CGSize delta = CGSizeMake(self.frame.size.width - oldFrame.size.width,
                                  self.frame.size.height - oldFrame.size.height);
        if (self.delegate)
            [self notifySizeChanged:delta notification:notification];
    }
}

- (void)keyboardWillHide:(NSNotification *)notification
{
    if (self.frame.size.height > 0.0)
    {
        [self retrieveFrameFromNotification:notification];
        CGSize delta = CGSizeMake(-self.frame.size.width, -self.frame.size.height);

        if (self.delegate)
            [self notifySizeChanged:delta notification:notification];
    }

    self.frame = CGRectZero;
}

- (void)retrieveFrameFromNotification:(NSNotification *)notification
{
    CGRect keyboardRect;
    [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardRect];
    self.frame = [[UIApplication sharedApplication].keyWindow.rootViewController.view convertRect:keyboardRect fromView:nil];
}

- (void)notifySizeChanged:(CGSize)delta notification:(NSNotification *)notification
{
    NSDictionary *info = [notification userInfo];

    UIViewAnimationCurve curve;
    [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];

    NSTimeInterval duration;
    [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];

    void (^action)(void) = ^{
        [self.delegate keyboardSizeChanged:delta];
    };

    [UIView animateWithDuration:duration
                          delay:0.0
                        options:curve
                     animations:action
                     completion:nil];    
}

@end

KBKeyboardHandlerDelegate.h:

@protocol KBKeyboardHandlerDelegate

- (void)keyboardSizeChanged:(CGSize)delta;

@end

Sample MyViewController.h:

@interface MyViewController : UIViewController<KBKeyboardHandlerDelegate>
...
@end

Sample MyViewController.m:

@implementation MyViewController
{
    KBKeyboardHandler *keyboard;
}

- (void)dealloc
{
    keyboard.delegate = nil;
    [keyboard release];
    [super dealloc];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    keyboard = [[KBKeyboardHandler alloc] init];
    keyboard.delegate = self;
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    keyboard.delegate = nil;
    [keyboard release];
    keyboard = nil;
}

- (void)keyboardSizeChanged:(CGSize)delta
{
    // Resize / reposition your views here. All actions performed here 
    // will appear animated.
    // delta is the difference between the previous size of the keyboard 
    // and the new one.
    // For instance when the keyboard is shown, 
    // delta may has width=768, height=264,
    // when the keyboard is hidden: width=-768, height=-264.
    // Use keyboard.frame.size to get the real keyboard size.

    // Sample:
    CGRect frame = self.view.frame;
    frame.size.height -= delta.height;
    self.view.frame = frame;
}

That I've found here. I'm trying to figure out why my view isn't pushing up. The keyboard is showing and keyboardSizeChanged is fired up but the view doesn't move. I opened a new project and copied the KBKeyboardHandler and delegate files to it, implemented the code and in the new project it works fine so I know for a fact that it's something in my original project that is messed up. Any idea what it can be?

Upvotes: 3

Views: 749

Answers (4)

Cristina Sita
Cristina Sita

Reputation: 376

You can also try another library, it's called TPKeyboardAvoiding. It is working great, and is very easy to setup:

  1. Add a UIScrollView into your view controller's xib
  2. Set the scroll view's class to TPKeyboardAvoidingScrollView (still in the xib, via the identity inspector)
  3. Place all your controls within that scrollview

It also automatically hooks up "Next" buttons on the keyboard to switch through the text fields.

Upvotes: 0

zurbergram
zurbergram

Reputation: 429

If you are not using a UITableView use UIScrollView as the parent view of your elements and add the code below Adjust as needed,

You will need a NSArray of NSObjects, I called mine scrollToObjects and in viewDidLoad add the elements to the array that would need scrolling so that when keyboardWasShown is called you can check to see if the currently selected element should be scrolled to.

I used self.activeField to store my currently selected element which is set when a element triggers an event(the event needs to be one of the first called and that is always called)

Then I check to see if the scrollToObjects contains activeField and if it does scroll the UIScrollView up and then back down when the activeField resignsFirstResponder

// Call this method somewhere in your view controller setup code.
- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil];
}

// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
    self.scrollView.contentInset = contentInsets;
    self.scrollView.scrollIndicatorInsets = contentInsets;

    // If active text field is hidden by keyboard, scroll it so it's visible
    // Your application might not need or want this behavior.
    CGRect aRect = self.view.frame;
    aRect.size.height -= kbSize.height;
    // Wouldn't go true so used an array that contains text fields that need to be scrolled   to
    //    if (!CGRectContainsPoint(aRect, self.activeField.frame.origin) ) {
    //        CGPoint scrollPoint = CGPointMake(0.0, self.activeField.frame.origin.y-    kbSize.height);
    //        [self.scrollView setContentOffset:scrollPoint animated:YES];
    //    }
    if ([self.scrollToObjects containsObject:self.activeField]) {
        CGPoint scrollPoint = CGPointMake(0.0, self.activeField.frame.origin.y+    (self.activeField.frame.size.height*2)-kbSize.height);
        [self.scrollView setContentOffset:scrollPoint animated:YES];
    }
}

// Called when the UIKeyboardWillHideNotification is sent 
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{ 
    // self.scrollView.contentInset = UIEdgeInsetsZero;
    [self.scrollView setContentOffset:CGPointMake(0.0, 0.0) animated:YES];
    self.scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
}

A simpler solution would be to handle the UITextFieldDelegate methods below with following code or something similar to activation and deactivation of the object

-(void)textFieldDidBeginEditing:(UITextField *)textField
{
    CGPoint scrollPoint = CGPointMake(0.0, textField.frame.origin.y);
    [self.theScrollView setContentOffset:scrollPoint animated:YES];
}

-(void)textFieldDidEndEditing:(UITextField *)textField
{
    [self.theScrollView setContentOffset:CGPointMake(0.0, 0.0) animated:YES];
    self.theScrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
}

Upvotes: 1

janineanne
janineanne

Reputation: 613

The problem is in keyboardSizeChanged: - what you want to adjust is frame.origin.y, not frame.size.height. At least that is what I wanted it to do.

Upvotes: 0

Alexandru
Alexandru

Reputation: 12902

I'd check your calculation for delta.

CGSize delta = CGSizeMake(self.frame.size.width - oldFrame.size.width,
                              self.frame.size.height - oldFrame.size.height);

My guess is oldFrame and self.frame might have the same dimensions.

I'm trying to think if there's anything else that could have been missed, like properly linking views to these objects or something in the interface editor?

Upvotes: 0

Related Questions