5

Right now, I have basic code for moving the textfield above the keyboard when you start editing. However, the size of the textfield varies based on device and orientation. So, I wrote a crude way of doing it, which doesn't stay consistently right above the keyboard, but instead will go up further when you rotate it, and so it doesn't look as professional as I would like.

The basic sense of my question is if there is a logic for getting the size of the keyboard based on device and orientation and using that value automatically and hopefully faster than this.

If that is the best way, please let me know. Otherwise, please provide input. Here is the code that I have. (This is just the move-up code, not the move down code, in order to prevent taking up too much space)

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

    //Get Device Type
    NSString *deviceType = [[UIDevice currentDevice] model];

    //Animate Text Field
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDuration:0.4];
    [UIView setAnimationBeginsFromCurrentState:YES];

    if ([deviceType isEqualToString:@"iPhone"]) {

        //Size For iPhone
        googleBar.frame = CGRectMake(googleBar.frame.origin.x - 62.0, (googleBar.frame.origin.y - 210.0), googleBar.frame.size.width + 120.0, googleBar.frame.size.height);

    } else if ([deviceType isEqualToString:@"iPad"]) {

        //Size for iPad
        googleBar.frame = CGRectMake(googleBar.frame.origin.x - 62.0, (googleBar.frame.origin.y - 320.0), googleBar.frame.size.width + 120.0, googleBar.frame.size.height);

    } else if ([deviceType isEqualToString:@"iPod touch"]) {

        //Size For iPod Touch
        googleBar.frame = CGRectMake(googleBar.frame.origin.x - 62.0, (googleBar.frame.origin.y - 210.0), googleBar.frame.size.width + 120.0, googleBar.frame.size.height);

    } 

    [UIView commitAnimations];

} 
Jtaylorapps
  • 5,680
  • 8
  • 40
  • 56

3 Answers3

18

What you really want to do is observe the UIKeyboard(Did|Will)(Show|Hide) notifications. They contain in their userInfo dictionaries the beginning and ending frame, as well as the correct animation curve and durations.

So after observing this notification, when it's posted move your text field based on the size of the frame passed in the notification, according to the animation hints provided.

You can see more information in the UIWindow class reference's "notifications" section: https://developer.apple.com/library/ios/#documentation/uikit/reference/UIWindow_Class/UIWindowClassReference/UIWindowClassReference.html

Below is a sample view controller implementation. The nib for this view controller was just a single text field, with an outlet connected to it, and the text field's delegate set to the view controller.

@interface ViewController ()

- (void)viewControllerInit;

@end

@implementation ViewController

@synthesize textField;

- (id)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        [self viewControllerInit];
    }
    return self;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
    {
        [self viewControllerInit];
    }
    return self;
}


- (void)viewControllerInit
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}


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


#pragma mark - Notification Handlers

- (void)keyboardWillShow:(NSNotification *)notification
{
    // I'll try to make my text field 20 pixels above the top of the keyboard
    // To do this first we need to find out where the keyboard will be.

    NSValue *keyboardEndFrameValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey];
    CGRect keyboardEndFrame = [keyboardEndFrameValue CGRectValue];

    // When we move the textField up, we want to match the animation duration and curve that
    // the keyboard displays. So we get those values out now

    NSNumber *animationDurationNumber = [[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey];
    NSTimeInterval animationDuration = [animationDurationNumber doubleValue];

    NSNumber *animationCurveNumber = [[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey];
    UIViewAnimationCurve animationCurve = [animationCurveNumber intValue];

    // UIView's block-based animation methods anticipate not a UIVieAnimationCurve but a UIViewAnimationOptions.
    // We shift it according to the docs to get this curve.

    UIViewAnimationOptions animationOptions = animationCurve << 16;


    // Now we set up our animation block.
    [UIView animateWithDuration:animationDuration 
                          delay:0.0 
                        options:animationOptions 
                     animations:^{
                         // Now we just animate the text field up an amount according to the keyboard's height,
                         // as we mentioned above.
                        CGRect textFieldFrame = self.textField.frame;
                        textFieldFrame.origin.y = keyboardEndFrame.origin.y - textFieldFrame.size.height - 40; //I don't think the keyboard takes into account the status bar
                        self.textField.frame = textFieldFrame;
                     } 
                     completion:^(BOOL finished) {}];

}


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

    NSNumber *animationDurationNumber = [[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey];
    NSTimeInterval animationDuration = [animationDurationNumber doubleValue];

    NSNumber *animationCurveNumber = [[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey];
    UIViewAnimationCurve animationCurve = [animationCurveNumber intValue];
    UIViewAnimationOptions animationOptions = animationCurve << 16;

    [UIView animateWithDuration:animationDuration 
                          delay:0.0 
                        options:animationOptions 
                     animations:^{
                         self.textField.frame = CGRectMake(20, 409, 280, 31); //just some hard coded value
                     } 
                     completion:^(BOOL finished) {}];

}
#pragma mark - View lifecycle

- (void)viewDidUnload
{
    [self setTextField:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

#pragma mark - UITextFieldDelegate

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [self.textField resignFirstResponder];
    return YES;
}

@end
Carl Veazey
  • 18,392
  • 8
  • 66
  • 81
  • I could do it this way, and I'm looking at the alert section right now. However, how would you go about getting the actual coordinates from those messages in order to implement it into the method I posted. I'm toying with it right now, but assistance will be nice. – Jtaylorapps Feb 04 '12 at 02:04
  • Update: I've been working on it for the last 35 minutes. So far, I have nothing to show for it. Could you possibly provide me with some assistance for the check mark? Much appreciated! – Jtaylorapps Feb 04 '12 at 02:16
  • What do you mean by the check mark? I'll post up some basic code in a bit though. – Carl Veazey Feb 04 '12 at 02:35
  • @JTApps did this code help you out? If not, did I miss something about your problem? – Carl Veazey Feb 04 '12 at 04:28
  • It's a great answer. I gave you the check, and I'm going to go ahead and try it out right now. Thanks again! – Jtaylorapps Feb 04 '12 at 23:09
  • Question for you, please. How would you go about adding 120 pixels to the width when the keyboard opens, and removing it again when you're finished? – Jtaylorapps Feb 04 '12 at 23:45
2

Using notification keyboard register , you can place your textfield inside a scroll and manage the content offset of scroll to turn the actual first responder above keyboard if it's neccesary.

so after register controller to keybard appear , you must to obtain the gap between keyboard origin and scroll origin relative to parent.

you must know if an specific first responder can change content offset of scroll, therefore is neccesary to know the possible bounds between keyboard origin and first responder.

by the way you need to know the gap betwen the scroll content offset and the first responder for place your first responder in specific position.

@interface MainViewController : UIViewController

  @property (strong, nonatomic) IBOutlet UIScrollView *scroll;

@end    

@interface MainViewController ()
{
   CGPoint scrollOffset;
}
@end 

@implementation MainViewController

@synthesize scroll

-(void)viewWillAppear:(BOOL)animated
 {
    [[ NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillAppear:) name:UIKeyboardDidShowNotification object:nil];

    [[ NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(keyboardWillDisAppear:) name:UIKeyboardDidHideNotification object:nil];

 }


-(void)viewWillDisappear:(BOOL)animated
 {
    [[ NSNotificationCenter defaultCenter ] removeObserver:self ];
 }

-(void)keyboardWillAppear:(NSNotification*) note
 {
   const CGFloat default_gap = 25.0f;

   NSValue *keyBoardEndFrameValue = [[ note userInfo ] objectForKey:UIKeyboardFrameEndUserInfoKey ];

   CGRect keyBoardFrame = [ keyBoardEndFrameValue CGRectValue ];


   offset = scroll.contentOffset;

   UIWindow *window = [[ UIApplication sharedApplication ] keyWindow];
   UITextField *textField = (UITextField*)[ window performSelector:@selector(firstResponder) ];

   //Gap between keyboard origin and the scroll origin, relative to parent.
   CGFloat distanceRelativeToParent = keyBoardFrame.origin.y - scroll.frame.origin.y; 


   //Distance between superview to textfield inside scroll. to determine if it's necesary to scroll.
   CGFloat bound = (textField.frame.origin.y + textField.frame.size.height)+scroll.frame.origin.y; 

   CGFloat gapScroll = textField.frame.size.height+default_gap;

  if( bound >= keyBoardFrame.origin.y )
   {
     [ UIView animateWithDuration:.3 delay:0.0 options:UIViewAnimationCurveEaseOut 
   animations:^{

        [ scroll setContentOffset:CGPointMake(0, textField.frame.origin.y - distanceRelativeToParent +  gapScroll  ) animated:YES ];

   }
  completion:^(BOOL finished){

   }];
}

}

-(void) keyboardWillDisAppear:(NSNotification*) note
{   
   [ scroll setContentOffset:offset animated:YES ];
}
@end
Jans
  • 11,064
  • 3
  • 37
  • 45
0

UIViewControllers have a property called interfaceOrientation and the function UIInterfaceOrientationIsPortrait/Landscape so essentially you can do:

if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation){
//portrait logic
}
else{
//landcapeLogic
}

inside for each the iPhone and iPad in your view controller. From there you can do your pixel measurement way as you had done before, because as far as I know that's the simplest way to do it.

P.S. There is a function to check for landscape checker too, but if the first if statement is false meaning the device is not in portrait, then it must be in landscape hence the plain else.

brandonbocklund
  • 595
  • 4
  • 11
  • What a about landscape left, as well as upside down? Or, does portait and landscape properties include upside down as well as both landscape options? – Jtaylorapps Feb 03 '12 at 21:07
  • Portrait does anything right side up or upside down, and landscape covers both left and right – brandonbocklund Feb 03 '12 at 21:09
  • Good, I'll try it, and if it works, I'll give you the check mark. Give me a minute please =) – Jtaylorapps Feb 04 '12 at 02:04
  • Tried it. The problem is if you rotate the device while it's being edited, the coordinates don't change and it fails miserably. Sorry, I'll have to use another method. Thanks though – Jtaylorapps Feb 04 '12 at 02:14
  • Sorry, I should have mentioned that you ought to add code in the shouldRotateToInterfaceOrientation method in your view controller to animate the screen to the new spot depending on which orientation you're switching from. Maybe too complex for your situation and if you don't want to attempt it I don't blame you. – brandonbocklund Feb 05 '12 at 03:43
  • Yeah, that's what I was thinking. It might not be worth it to just get 20 pixels more accurate. Thanks for your help! – Jtaylorapps Feb 05 '12 at 05:30