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];
}