views:

207

answers:

1

In my iPad app, I have an implementation of a login window as a UIAlertView subclass that adds two UITextField, using an idiom that's found on the net.

The problem is that in horizontal orientation, the alert is partially hidden by the keyboard. Namely, the buttons are hidden. Of course, it's possible to hide the keyboard to reveal the button, but this is ugly.

The way the idiom is supposed to fix that is to add a translation transform to the view:

CGAffineTransform translate = CGAffineTransformMakeTranslation(0.0, 100.0); 
[self setTransform:translate];

However that doesn't really work:

  • in initial vertical orientation, that works (but stops working after any device rotation)
  • in initial horizontal orientation, one can see it work, but the alert is right away animated back to the center of the screen where it's partially hidden again.
  • in any orientation after rotating the iPad, it doesn't work at all: nothing happens, as if the transform was not even there.

(additionally, this transform idea may stop {working|being necessary} for iOS 4.x. But that's another issue).

Any idea welcome.

For the sake of completeness, here is the full code:

- (id)initWithLogin:(NSString *)defaultLogin delegate:(id)delegate
{
    if (self = [super initWithTitle:@"Username and password"
                            message:@"\n\n\n"
                           delegate:delegate
                  cancelButtonTitle:@"Cancel"
                  otherButtonTitles:@"Enter", nil])
    {
        UITextField *theTextField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 45.0, 260.0, 25.0)]; 
        [theTextField setBackgroundColor:[UIColor whiteColor]];
        theTextField.text = defaultLogin;
        theTextField.placeholder = @"username";
        [self addSubview:theTextField];
        self.loginField = theTextField;
        [theTextField release];

        theTextField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 80.0, 260.0, 25.0)]; 
        [theTextField setBackgroundColor:[UIColor whiteColor]]; 
        theTextField.placeholder = @"password";
        theTextField.secureTextEntry = YES;
        [self addSubview:theTextField];
        self.passwordField = theTextField;
        [theTextField release];

    // the two next lines may not be useful for iOS > 4.0
        CGAffineTransform translate = CGAffineTransformMakeTranslation(0.0, 100.0); 
        [self setTransform:translate];
    }
    return self;
}

Thanks for Bittu for a working solution. Here is my implementation of his idea. I put the code to move the alert up in a new method of my subclass:

- (void)slideUp
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    [UIView beginAnimations:nil context:context];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDuration:0.25f];           

    CGPoint center = self.center;
    center.y -= 100;
    self.center = center;

    [UIView commitAnimations];
}

The key point is when to call that code. There are two cases: the simplest case is when the user rotates the device. Unfortunately, the Alert class is not informed of that event, only the client UIViewController, so that's were we need to call it. This is ugly by breaking encapsulation, but so be it:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation 
{
    if(loginWindow && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
    {
        [loginWindow slideUp];
    }   
}

The second case is when the orientation is already horizontal when opening up the alert. The alert delegate is informed of that in its didPresentAlertView: delegate method.

- (void)didPresentAlertView:(UIAlertView *)alertView
{
    if ( UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ) {
        [self slideUp];
    }
}

Unfortunately, that implementation doesn't work, because calling slideUp at that time will conflict with the system already animating the alert to the center of the screen. The solution is to delay the call slightly, for example using NSTimer:

- (void)didPresentAlertView:(UIAlertView *)alertView
{
    if ( UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ) {
        [NSTimer scheduledTimerWithTimeInterval:0.25f
                                      target:self
                                    selector:@selector(slideUp)
                                    userInfo:nil
                                     repeats:NO];
    }
}

By the way, slideUp doesn't have the documented signature for a NSTimer selector, but it still seems to work! If that bothers you, simply add an in between method with the correct signature:

- (void)slideUpByTimer:(NSTimer*)theTimer
{
    [self slideUp]; 
}
+1  A: 

I had the exact same issue with the alertView with UITextField as a subview. Instead of taking the transform path, I took a little different approach. Here's how I did it.

First create your alertview with textfield in it:

UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 40.0, 260.0, 25.0)];
[textField setBackgroundColor:[UIColor whiteColor]];
textField.placeholder = @"Search";

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Enter Search Text" message:@"        " delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Enter",nil];

[alertView addSubview:textField];
[alertView show];

Then in the UIViewController that's going to show the alert view, implement this method:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation 
{

    if(self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft || self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
    {
         CGContextRef context = UIGraphicsGetCurrentContext();
         [UIView beginAnimations:nil context:context];
         [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
         [UIView setAnimationDuration:0.25f];           

         CGPoint center = self.alertView.center;
         center.y -= 100;
         self.alertView.center = center;

         [UIView commitAnimations];
    }

}

this method happens after the "rotation finished" notification has been posted. therefore, you'll see the alertview partially under the keyboard before the animation to slide it up starts happening. Hope this helps you with your login alertview.

Bittu
one more addition:implement the following uialertview delegate method- (void)didPresentAlertView:(UIAlertView *)alertView;and in this method also, do the same check that are done in didRotateFromInterfaceOrientation. this will allow to fix the alert issue if you are showing the alert when the uiviewcontroller's current interfaceOrietion was already landscape.
Bittu
Yeas I saw that right away. Unfortunately, it doesn't work, because in that case, I already see the alert being animated to the center of the screen by the system, and somehow your code never runs, as if it was preempted by the system animation. The fix is to delay you code: I run it within an NSTimer scheduled for 0.25s later.
Jean-Denis Muys
Also I hate one thing about this: you need to break encapsulation by adding implementation code to the client controller. I hate that, but I can't see a way around it.
Jean-Denis Muys