I've got a view controller listening for both UIKeyboardWillShowNotification and UIKeyboardWillHideNotification. The handlers for these notifications adjust various parts of the view, which is standard procedure.
The following code is used to convert the keyboard rect from screen coordinates:
CGRect keyboardBounds = [self.view convertRect:[keyboardBoundsValue CGRectValue] fromView:nil];
Again, standard procedure. Unfortunately, there is a critical situation where this conversion fails. Look at what happens when an iPhone is rotated from portrait to landscape while the keyboard is deployed:
1) iOS automatically fires UIKeyboardWillHideNotification; self.interfaceOrientation is reported as portrait; keyboardBounds.height is 216.0. This makes sense. Why? Because the notification handler is given the chance to "clean up" before the view switches to landscape mode.
2) iOS automatically fires UIKeyboardWillShowNotification; self.interfaceOrientation is reported as portrait; keyboardBounds.height is 480.0. This does NOT make sense. Why not? Because the notification handler is going to do its work thinking that the height of the keyboard is 480.0!
Did Apple drop the ball on this one, or am I doing something wrong?
Please note that listening instead for UIKeyboardDidShowNotification is not a valid solution, because it significantly degrades the user experience. Why? Because animating my changes to the view after the keyboard deployment animation occurs is... well, pretty terrible-looking.
Has anyone managed to get autorotation working perfectly while the keyboard is deployed? It seems like an explosion of chaos that Apple has completely overlooked. >:|
Maybe a bit late, but I've just run into the same issue and have a nice solution for it that avoids any kind of work arounds (unless of course apple change things)
Basically, when the notification center calls your method for UIKeyboardWillShowNotification (or any of the other notifications), the frame that it gives you for UIKeyboardFrameBeginUserInfoKey is in context of the window, NOT your view. The problem with this, is that the windows coordinate system is always in portrait, regardless of the devices orientation, hence you're finding the width and height the wrong way round.
If you want to avoid your work around, simply convert the rectangle into the coordinate system of your view (which does change according to the orientation). To do this, do something like the following :
- (void) keyboardWillShow:(NSNotification *)aNotification
{
CGRect keyboardFrame = [[[aNotification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect convertedFrame = [self.view convertRect:keyboardFrame fromView:self.view.window];
......
/* Do whatever you want now with the new frame.
* The width and height will actually be correct now
*/
......
}
Hopefully this should be what you're after :)
Recently I've wrote a blog post about this exact problem you've described and how to solve it with a short and elegant way. Here is the link to the post: Synchronizing rotation animation between the keyboard and the attached view
If you don't want to dive into the long explanation described in the blog post here is a short description with a code example:
The basic principle is to use the same method that everyone uses - observing keyboard notifications to animate the attached view up and down. But in addition to that, you have to cancel these animations when the keyboard notifications are fired as a consequence of interface orientation change.
Rotation example without animation cancellation custom on interface orientation change:
Rotation example with animation cancellation on interface orientation change:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(adjustViewForKeyboardNotification:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(adjustViewForKeyboardNotification:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter]
removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]
removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
self.animatingRotation = YES;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
self.animatingRotation = NO;
}
- (void)adjustViewForKeyboardNotification:(NSNotification *)notification {
NSDictionary *notificationInfo = [notification userInfo];
// Get the end frame of the keyboard in screen coordinates.
CGRect finalKeyboardFrame = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
// Convert the finalKeyboardFrame to view coordinates to take into account any rotation
// factors applied to the window’s contents as a result of interface orientation changes.
finalKeyboardFrame = [self.view convertRect:finalKeyboardFrame fromView:self.view.window];
// Calculate new position of the commentBar
CGRect commentBarFrame = self.commentBar.frame;
commentBarFrame.origin.y = finalKeyboardFrame.origin.y - commentBarFrame.size.height;
// Update tableView height.
CGRect tableViewFrame = self.tableView.frame;
tableViewFrame.size.height = commentBarFrame.origin.y;
if (!self.animatingRotation) {
// Get the animation curve and duration
UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
// Animate view size synchronously with the appearance of the keyboard.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:animationCurve];
[UIView setAnimationBeginsFromCurrentState:YES];
self.commentBar.frame = commentBarFrame;
self.tableView.frame = tableViewFrame;
[UIView commitAnimations];
} else {
self.commentBar.frame = commentBarFrame;
self.tableView.frame = tableViewFrame;
}
}
This answer was also posted in similar question: UIView atop the Keyboard similar to iMessage App
I met the same problem. iOS gaves me incorrect width/height of the keyboard. I used the following snipped in a keyboardDidShow handler:
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGSize keyboardSize2 = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
LogDbg(#"keyboard size: frameBegin=%#; frameEnd=%#", NSStringFromCGSize(keyboardSize), NSStringFromCGSize(keyboardSize2));
and for portrait and landscape modes of iPad I got respectively:
2012-06-14 04:09:49.734 -[LoginViewController keyboardDidShow:] 132 [DBG]:keyboard size: frameBegin={768, 264}; frameEnd={768, 264}
2012-06-14 04:10:07.971 -[LoginViewController keyboardDidShow:] 132 [DBG]:keyboard size: frameBegin={352, 1024}; frameEnd={352, 1024}
Guessing that the width of the keyboard should be greater then the height (yep, i'm so naive) I made a workaround like following:
if (keyboardSize.width < keyboardSize.height)
{
// NOTE: fixing iOS bug: http://stackoverflow.com/questions/9746417/keyboard-willshow-and-willhide-vs-rotation
CGFloat height = keyboardSize.height;
keyboardSize.height = keyboardSize.width;
keyboardSize.width = height;
}
Well, try looking at keyboard width. If it is the value that you are expecting, then I assume that the values are simply switched ;). 480 makes sense as a keyboard width for going into landscape, which is what gives me this hunch.
If that fails, just store the portrait and landscape rectangles separately. They are well documented ;)
I know this a very very late reply. Now only I came on this situation and find the unanswered question. So I thought I'll share my solution. There will be some other better way, but the following way also we can solve this.
The KBKeyboardHandler that I used is from: UITextField: move view when keyboard appears
I just changed my delegate as following:
- (void)keyboardSizeChanged:(CGSize)delta
{
CGRect frame = self.view.frame;
UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
switch (interfaceOrientation) {
case UIInterfaceOrientationPortrait:
frame.origin.y-=delta.height;
break;
case UIInterfaceOrientationPortraitUpsideDown:
frame.origin.y+=delta.height;
break;
case UIInterfaceOrientationLandscapeLeft:
frame.origin.x-=delta.height;
break;
case UIInterfaceOrientationLandscapeRight:
frame.origin.x+=delta.height;
break;
default:
break;
}
self.view.frame = frame;
}
And it was working fine.
Here is my workaround:
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
float keyboardHeight = self.interfaceOrientation == UIInterfaceOrientationPortrait ? keyboardSize.height : keyboardSize.width;
Hope this helps :)
I use the following code to get the size of the keyboard which works fine for all rotations
NSDictionary *info = [aNotification userInfo];
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
kbHeight = [[NSNumber numberWithFloat:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.width] floatValue];
else
kbHeight = [[NSNumber numberWithFloat:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height] floatValue];
NSLog(#"keyboard height = %F",kbHeight);
I then test for the orientation using the status bar orientation (which works in the first launch case for the iPad) and shift the view in the relative direction needed to make space for the keyboard. This works perfectly, if the keyboard is visible then it relocates to the correct position on rotations.
UIDeviceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (orientation == UIDeviceOrientationPortrait)
{
NSLog(#"Orientation: portrait");
self.originalCenter = self.view.center;
self.view.center = CGPointMake(self.originalCenter.x, self.originalCenter.y-kbHeight);
}
if (orientation == UIDeviceOrientationPortraitUpsideDown)
{
NSLog(#"Orientation: portrait upside down");
self.originalCenter = self.view.center;
self.view.center = CGPointMake(self.originalCenter.x, self.originalCenter.y+kbHeight);
}
if (orientation == UIDeviceOrientationLandscapeLeft)
{
NSLog(#"Orientation: landscape left");
self.originalCenter = self.view.center;
self.view.center = CGPointMake(self.originalCenter.x+kbHeight,self.originalCenter.y);
}
if (orientation == UIDeviceOrientationLandscapeRight)
{
NSLog(#"Orientation: landscape right");
self.originalCenter = self.view.center;
self.view.center = CGPointMake(self.originalCenter.x-kbHeight,self.originalCenter.y);
}
You can return the view to its original position when the keyboard disappears or via a textFileDidEndEditing function.
Related
I've been wondering if it is possible to replicate the behavior of Apple's iOS5 keyboard in the messages app, without using any private API calls. When you scroll down past the keyboard in the messages app, the keyboard will collapse leaving more room to see messages - try it to see.
I couldn't find anything that points towards making this without having to start jumping through some serious hoops to get an instance of the Keyboard's View. And I'm pretty sure Apple wouldn't be happy with that.
In addition to the answer given below you can see a fully baked xcode project of my implementation here:
https://github.com/orta/iMessage-Style-Receding-Keyboard
In iOS 7 there is a keyboardDismissMode property on UIScrollView.
So just set it to "UIScrollViewKeyboardDismissModeInteractive" and you'll get this behavior. Works in UIScrollView subclasses such as UITableView.
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
Swift 3:
tableView.keyboardDismissMode = .interactive
Or change it in storyboard (if using it) in attributes inspector for your UIScrollView subclass.
This is an incomplete solution, however it should give you a good starting point.
Add the following ivars to your UIViewController:
CGRect keyboardSuperFrame; // frame of keyboard when initially displayed
UIView * keyboardSuperView; // reference to keyboard view
Add an inputAccessoryView to your text controller. I created an small view to insert as the accessoryView:
accView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
accView.backgroundColor = [UIColor clearColor];
textField.inputAccessoryView = accView;
I added the above code to -(void)loadView
Register to receive UIKeyboardDidShowNotification and UIKeyboardDidHideNotification when view is loaded:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
return;
}
Add methods to specified as the selectors for the notifications:
// method is called whenever the keyboard is about to be displayed
- (void)keyboardWillShow:(NSNotification *)notification
{
// makes keyboard view visible incase it was hidden
keyboardSuperView.hidden = NO;
return;
}
// method is called whenever the keyboard is displayed
- (void) keyboardDidShow:(NSNotification *)note
{
// save reference to keyboard so we can easily determine
// if it is currently displayed
keyboardSuperView = textField.inputAccessoryView.superview;
// save current frame of keyboard so we can reference the original position later
keyboardSuperFrame = textField.inputAccessoryView.superview.frame;
return;
}
Add methods to track touched and update keyboard view:
// stops tracking touches to divider
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGRect newFrame;
CGRect bounds = [[UIScreen mainScreen] bounds];
newFrame = keyboardSuperFrame;
newFrame.origin.y = bounds.size.height;
if ((keyboardSuperView.superview))
if (keyboardSuperFrame.origin.y != keyboardSuperView.frame.origin.y)
[UIView animateWithDuration:0.2
animations:^{keyboardSuperView.frame = newFrame;}
completion:^(BOOL finished){
keyboardSuperView.hidden = YES;
keyboardSuperView.frame = keyboardSuperFrame;
[textField resignFirstResponder]; }];
return;
}
// updates divider view position based upon movement of touches
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch;
CGPoint point;
CGFloat updateY;
if ((touch = [touches anyObject]))
{
point = [touch locationInView:self.view];
if ((keyboardSuperView.superview))
{
updateY = keyboardSuperView.frame.origin.y;
if (point.y < keyboardSuperFrame.origin.y)
return;
if ((point.y > updateY) || (point.y < updateY))
updateY = point.y;
if (keyboardSuperView.frame.origin.y != updateY)
keyboardSuperView.frame = CGRectMake(keyboardSuperFrame.origin.x,
point.y,
keyboardSuperFrame.size.width,
keyboardSuperFrame.size.height);
};
};
return;
}
Disclaimers:
When resigning as first responded, the keyboard moves back to its original position before sliding off screen. To make dismissing the keyboard more fluid, you first need to create an animation to move the keyboard off of the screen and then hide the view. I'll leave this part as an exercise to the readers.
I've only tested this on the iOS 5 simulator and with an iPhone with iOS 5. I have not tested this with earlier versions of iOS.
The SlidingKeyboard project I created to test this concept is available from GitHub in the examples directory of BindleKit:
https://github.com/bindle/BindleKit
Edit: Updating example to address first disclaimer.
Vladimir's simple solution will hide the keyboard as the user scrolls down. However to finish the question regarding iMessage, in order to keep a TextField always visible and anchored to the top of the keyboard, you need to implement these methods:
- (UIView *) inputAccessoryView {
// Return your textfield, buttons, etc
}
- (BOOL) canBecomeFirstResponder {
return YES;
}
Here's a good tutorial breaking it down more
Heey,
In my iPad application I have a UIPopoverController with a UIViewController containing some textfields.
When the keyboard comes up, the Popover gets animated to fit. Does anybody know how to disable this?
Thanks
I dont think you can except make the popovers smaller in height...Its done like that so when your keyboard pops up none of the popover gets covered by it (thus it shrinks back), however i have found it to be annoying at times since it messed with table views (not making them able to scroll all the way and having to resize them)
It would be great if it did actually animate to better part of the screen, I think you mean it actually shrinks the popUp which is mostly not good.(which i see during rotation in my case). You can not keep the popUp from squishing, UNLESS you move a view. The best way to handle this is to temporarily move your entire main UIView of the whole screen up with the keyBoard by the difference between the size of your pop up, and how much that pop up would shrink if you did not move it up... you can't just move it up by the size of your keyboard, because popUps up high would then also be effected. This code below is for when the keyboard rotated, similar code for when it is first introduced. easy to do, EXCept for when you rotate the screen, then things get tricky...
in otherwords, sometimes your UIView will not move at all, sometimes it will move up by a good 170 points.
//-----------------finding the keyboard top (also used on "didRotate")----very handy,
// took me hours to figure out an absolute always work topOfKeyboard, so here you go.-------
//--------------------------------------------------------------------------------
- (void)keyboardWillShow:(NSNotification*)notification
{
NSLog(#" keyboardWillShow");
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
NSDictionary* info = [notification userInfo];
keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardRect = [(UIView*)keyWindow convertRect:keyboardRect toView:mainViewController.view];
keyboardIsVisible = TRUE;
topOfKeyboard = keyboardRect.origin.y;
the tricky part is finding the PopUp bottom, because there appears to be code in the popup itself or the convertRect:toView: code that makes the origin flaky , (if you try to "view" origin after a "convertRect:toView:" code), it wants to move and be in different spots during rotation,(or one of it's super views) so bottom calc comes out different some times,(not predicable) because of async process of the rotation of different elements possibly because the popUp itself has many superviews down in the pop up. (to see problem in action with pop up and, must have keyboard effecting popup, move the whole view up then log the "origin" and "size" of popUp after the "convertRect:toView:" code)... the "origin" i'm talking about is the origin of the frame after it is translated to the main view (or atleast a couple views up) with the "convertRect:toView:" code....
update:(it appears if you move down about 3 superviews from the popUp, the flakiness goes away... (this code below was in a "didRotate" ipad type of code. That is that the popUp has several superviews before you can get to the one that is projected ontop of the proper frame
UIPopoverController probably should have a property that has the origin that is predicted after a rotation in it, in a type of "will rotate" kind of code, (because of the keyboard problem), instead of just the "popoverContentSize", (also should include the popoverContentSize that would have been without the keyboard as another variable, ".. anyway This is how I had to do it, see code below.
//--------------------------------------------------------------------------------
- (void) checkRotation
{
NSLog(#" ");
NSLog(#"checkRotation");
if (wasOffset)
{
wasOffset = false;
[UIImageView beginAnimations:nil context:NULL];
[UIImageView setAnimationDuration:0.2f];
CGRect frame = carousel.frame;
frame.origin.y += offset;
carousel.frame = frame;
[UIImageView commitAnimations];
[popPickerController presentPopoverFromRect:zoneButton.frame inView:[zoneButton superview] permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
if (popPickerController.popoverVisible)
{
if (keyboardIsVisible)
{
wasOffset = false;
[popPickerController presentPopoverFromRect:zoneButton.frame inView:[zoneButton superview]
permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
upView3 = [[[popPickerController.contentViewController.view superview] superview] superview]; //hey it works... :o)
//NSLog(#" ");
//NSLog(#"upView3.frame.origin.x = %f",upView3.frame.origin.x);
//NSLog(#"upView3.frame.origin.y = %f",upView3.frame.origin.y);
//NSLog(#"upView3.frame.size.height = %f",upView3.frame.size.height);
//NSLog(#"upView3.frame.size.width = %f",upView3.frame.size.width);
//NSLog(#" ");
popUpRect.origin.x = upView3.frame.origin.x;
popUpRect.origin.y = upView3.frame.origin.y;
popUpRect.size.height = popUpSize.height;
popUpRect.size.width = popUpSize.width; //you must save the size because the keyboard destroys it before you can use it. very tricky....
//NSLog(#" ");
//NSLog(#"popUpRect.origin.x = %f",popUpRect.origin.x);
//NSLog(#"popUpRect.origin.y = %f",popUpRect.origin.y);
//NSLog(#"popUpRect.size.height = %f",popUpRect.size.height);
//NSLog(#"popUpRect.size.width = %f",popUpRect.size.width);
//NSLog(#" ");
//NSLog(#" ");
//NSLog(#"keyboardIsVisible = %d", keyboardIsVisible);
//NSLog(#" ");
//NSLog(#"keyboardRect.origin.x = %f",keyboardRect.origin.x);
//NSLog(#"keyboardRect.origin.y = %f",keyboardRect.origin.y);
//NSLog(#"keyboardRect.size.height = %f",keyboardRect.size.height);
//NSLog(#"keyboardRect.size.width = %f",keyboardRect.size.width);
//NSLog(#"topOfKeyboard = %f",topOfKeyboard);
CGFloat bottomOfPicker = popUpRect.origin.y + popUpRect.size.height - amountShadowCanEncroach;
//NSLog(#" ");
//NSLog(#"bottomOfPicker = %f",bottomOfPicker);
//NSLog(#"topOfKeyboard = %f",topOfKeyboard);
//NSLog(#" ");
if (bottomOfPicker > topOfKeyboard)
{
wasOffset = true;
offset = bottomOfPicker - topOfKeyboard;
NSLog(#"offset = %f",offset);
[UIImageView beginAnimations:nil context:NULL];
[UIImageView setAnimationDuration:0.2f];
CGRect frame = carousel.frame;
frame.origin.y -= offset;
carousel.frame = frame;
[UIImageView commitAnimations];
}
}
[popPickerController presentPopoverFromRect:zoneButton.frame inView:[zoneButton superview] permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
and moving the main UIView back
//-----------------------------------------------------------------------------
- (void) searchDidEndEditing
{
NSLog(#"searchDidEndEditing");
keyboardIsVisible = false;
if (wasOffset)
{
wasOffset = false;
[UIImageView beginAnimations:nil context:NULL];
[UIImageView setAnimationDuration:0.2f];
CGRect frame = mainView.frame;
frame.origin.y += offset;
mainView.frame = frame;
[UIImageView commitAnimations];
if (zoneButton.selected)
[popPickerController presentPopoverFromRect:zoneButton.frame inView:[zoneButton superview] permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
I created a bare bones iPhone app with a UIWebView (Scales Page to Fit = YES, shouldAutorotateToInterfaceOrientation = YES) and loaded a webpage, e.g. https://stackoverflow.com/
Rotating the device shows that UIWebView is auto-resized to fit the width. Good.
Incorrect: Zoom into the page and zoom out. Now rotating the device shows UIWebView in a weird width in one of the orientation (if u zoom in landscape, the portrait width is weird, vice versa). This behavior is fixed only when you navigate to another page.
Correct: Load the same URL in Mobile Safari. Rotating works & the width fits regardless of the zooming exercise.
Is this a UIWebView bug (probably not)? Or is there something that needs to be done to make things "just work" like in Mobile Safari?
I found something that worked for me. The problem is that when uiwebview changes its orientation web contents are zoommed to fit with viewport. But zoomscale parameter of scrollview subview is not updated correctly (nor are updated minimumZoomScale nor maximumZoomScale
Then we need to do it manually at willRotateToInterfaceOrientation:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
CGFloat ratioAspect = webview.bounds.size.width/webview.bounds.size.height;
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:
// Going to Portrait mode
for (UIScrollView *scroll in [webview subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale/ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale/ratioAspect;
[scroll setZoomScale:(scroll.zoomScale/ratioAspect) animated:YES];
}
}
break;
default:
// Going to Landscape mode
for (UIScrollView *scroll in [webview subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale *ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale *ratioAspect;
[scroll setZoomScale:(scroll.zoomScale*ratioAspect) animated:YES];
}
}
break;
}
}
Hope this helps!
I've tried the solution from M Penades and this seems to work for me as well.
The only issue that I'm experiencing is that when running this on a 3Gs the rotation is unfortunately not very smooth.
I'm therefore now using a different approach:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
CGFloat scale = browserWebView.contentScaleFactor;
NSString *javaStuff = [NSString stringWithFormat:#"document.body.style.zoom = %f;", scale];
[browserWebView stringByEvaluatingJavaScriptFromString:javaStuff];
}
Best Regards,
Ralph
- (UIScrollView *)findScrollViewInsideView:(UIView *)view
{
for(UIView *subview in view.subviews){
if([subview isKindOfClass:[UIScrollView class]]){
return (UIScrollView *)subview;
}
UIScrollView *foundScrollView = [self findScrollViewInsideView:subview];
if (foundScrollView){
return foundScrollView;
}
}
return nil;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
switch (self.interfaceOrientation){
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
{
UIScrollView *webViewScrollView = ([self.webView respondsToSelector:#selector(scrollView)])
? self.webView.scrollView
: [self findScrollViewInsideView:self.webView];
[webViewScrollView setZoomScale:1.01f animated:YES];
}
break;
default:
break;
}
}
try this code, it insignificantly changes zoom level (1.01) to allow UIWebView increase content size in landscape mode
findScrollViewInsideView: method added to support ios4
I have a solution to this problem, but I gotta say I'm not a huge fan of it. It works great, but the solution actually causes another problem. I have a fix for the secondary issue, but it takes a bit of effort.
Just keep in mind that since OS3.2 or iOS4 (not sure which) UIWebView's direct subview is now UIScrollView instead of UIScroller, so we can do a lot more with it. Also, since accessing subviews of a View is not a private action, neither is using a subview that is casted as a documented view we can do a lot with the UIWebView without breaking the rules.
First we need to get the UIScrollView from the UIWebview:
UIScrollView *sview = [[webView subviews] objectAtIndex:0];
Now we need to change the delegate of this scrollview so we can override scrollview delegate calls (which may actually be the cause of a secondary bug as a result of this solution, which I'll share in a moment):
sview.delegate = self;
Now, if you try it at this point, zooming is broken. We need to implement a UIScrollViewDelegate method to fix it. add:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
UIView *webBrowserView = [[scrollView subviews] objectAtIndex:10];
return webBrowserView;
}
webBrowserView is actually a UIWebBrowserView, but that isn't a documented class, so we are just going to treat it as a UIView.
Now run your app, zoom in and then zoom out the webpage. Rotate, and it should appear correctly.
This does cause a rather large bug, that is perhaps worse than the original.
If you zoom in and then rotate, you will loose scrolling ability, but your view will be zoomed in still. Here is the fix To complete the whole thing.
First, we need to keep track of a few numbers, and have a flag defined:
I have these defined in my h file:
BOOL updateZoomData;
float zoomData; //this holds the scale at which we are zoomed in, scrollView.zoomScale
CGPoint zoomOffset; //this holds the scrollView.contentOffset
CGSize zoomContentSize; //this holds the scrollView.contentSize
You may think you can just grab these numbers from UIScrollView, but when you need them, they will have changed, so we need them stored elsewhere.
We need to use another delegate method:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView{
if(updateZoomData){
zoomData = scrollView.zoomScale;
zoomOffset = scrollView.contentOffset;
zoomContentSize = scrollView.contentSize;
}
}
Now it gets into a mess I feel.
We need to track rotation, so you'll need to add this to your viewDidLoad, loadView, or whatever method you use to register notifications:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(webViewOrientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
and create this method:
- (void)webViewOrientationChanged:(NSNotification *)notification{
updateZoomData = NO;
[self performSelector:#selector(adjustWithZoomData) withObject:nil afterDelay:0.0];
}
So now anytime you rotate webViewOrientationChange will be called. The reason performSelector is delayed for 0.0 seconds is because we want to call adjustWithZoomData on the next runloop. If you call it directly, the adjustWithZoomData will adjust for the previous orientation.
Here is the adjustWithZoomData method:
- (void)adjustWithZoomData{
UIScrollView *sview = [[webView subviews] objectAtIndex:0];
[sview setZoomScale:zoomData animated:YES];
[sview setContentOffset:zoomOffset animated:YES];
[sview setContentSize:zoomContentSize];
updateZoomData = YES;
}
Thats it! Now when you rotate it will maintain zoom, and roughly maintain the correct offset. If anyone wants to do the math on how to get the exact correct offset then go for it!
I was looking into this myself and found out some more information:
Issues when changing zoom:
Safari often doesn't repaint properly (if at all) even though zoom level changed.
Changing the width forces a repaint.
you would think width=device-width in landscape would use 1024 but it seems to use 768 (screen.width happens too).
e.g. if current width is 1024 and you want to zoom from 1 to 1.5 in landscape you could:
change combination of width and zoom e.g. width to 2048 and zoom to 0.75
change width to 1023 (ugly aliasing?)
change width to say 1023, then next line back to 1024 (double repaint, but at least window is repainted).
So apparently I didn't use the solution by M Penades in the end (and forgot to update this post! sorry).
What I did was to resize the entire document (and change my font-size to keep things proportionate). That apparently fixed the issue.
However, my UIWebView is only for loading my own HTML & CSS from the iOS filesystem - if you're building a general purpose web browser, this trick may not work as well.
ViewController.m
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:
if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)) {
[webview stringByEvaluatingJavaScriptFromString:#"document.body.className = 'ppad'"];
} else {
[webview stringByEvaluatingJavaScriptFromString:#"document.body.className = 'pphone'"];
}
break;
default:
if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)) {
[webview stringByEvaluatingJavaScriptFromString:#"document.body.className = 'lpad'"];
} else {
[webview stringByEvaluatingJavaScriptFromString:#"document.body.className = 'lphone'"];
}
break;
}
}
And app.css
html>body.pphone { font-size:12px; width: 980px; }
html>body.lphone { font-size:18px; width: 1470px; }
html>body.ppad { font-size:12px; width: 768px; }
html>body.lpad { font-size:15.99999996px; width: 1024px; }
Gist at https://gist.github.com/d6589584944685909ae5
I am posting this because i have also faced the same problem and here i am following the M Penades Approach.M Penades 's Answer woks good only for case if user does not Skew(pinch Out) the Webview then rotate the device and repeat this process .then Content Size of UiwebView gets reduce gradually. so that was the issue came in M Penades Answer. so I have fixed that issue too and my code is as below.
1) For This I set the Pinch Gesture so that when User Skew The UIwebView could check the Scaled size of UIwebView.
//One This Please import The UIGestureRecognizerDelegate Protocol in '.h file'
//call below method in ViewDidLoad Method for setting the Pinch gesture
- (void)setPinchgesture
{
UIPinchGestureRecognizer * pinchgesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(didPinchWebView:)];
[pinchgesture setDelegate:self];
[htmlWebView addGestureRecognizer:pinchgesture];
[pinchgesture release];
// here htmlWebView is WebView user zoomingIn/Out
}
//Allow The allow simultaneous recognition
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
-(void)didPinchWebView:(UIPinchGestureRecognizer*)gestsure
{
//check if the Scaled Fator is same is normal scaling factor the allow set Flag True.
if(gestsure.scale<=1.0)
{
isPinchOut = TRUE;
}
else// otherwise Set false
{
isPinchOut = FALSE;
}
NSLog(#"Hello Pinch %f",gestsure.scale);
}
If User Hase Pinch In/Out The Web View in that Case Just Set THat Zooming Factor .
SO that WebView Can Adjust Its ContentSize as Oreintaion Changed.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//Allow the Execution of below code when user has Skewed the UIWebView and Adjust the Content Size of UiwebView.
if(isPinchOut){
CGFloat ratioAspect = htmlWebView.bounds.size.width/htmlWebView.bounds.size.height;
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:
// Going to Portrait mode
for (UIScrollView *scroll in [htmlWebView subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale/ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale/ratioAspect;
[scroll setZoomScale:(scroll.zoomScale/ratioAspect) animated:YES];
}
}
break;
default:
// Going to Landscape mode
for (UIScrollView *scroll in [htmlWebView subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale *ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale *ratioAspect;
[scroll setZoomScale:(scroll.zoomScale*ratioAspect) animated:YES];
}
}
break;
}
}
}
This Works perfectly for even user skew the UIWebView.
On rotation, try setting the scrollView zoomScale to 0.
See my full answer here: UIWebView content not adjusted to new frame after rotation
I put this code into all of my viewControllers:
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationLandscapeRight) {
CGAffineTransform transform = self.view.transform;
// Use the status bar frame to determine the center point of the window's content area.
//CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
//CGRect bounds = CGRectMake(0, 0, statusBarFrame.size.height, statusBarFrame.origin.x);
CGPoint center = CGPointMake(320/2, 480/2);
// Set the center point of the view to the center point of the window's content area.
self.view.center = center;
// Rotate the view 90 degrees around its new center point.
transform = CGAffineTransformRotate(transform, (M_PI / 2.0));
self.view.transform = transform;
}
and I also have:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
I can see that the codes work correctly as they should, but once in a while, there is a rare occasion that the main view controller failed to rotate for landscape mode after I put its view back to be the main view. The chance that it occurs is very rare, but this is nagging me. Moreover, I can see that it occurs more often on the iphone. On the simulator, I recalled it never happen. Do you know what are the possible causes for this? Thank you in advance.
I found the cause of my iphone program.
One of the possible causes is memory. This method below will be called when the system is low on memory:
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
An "unseen" UIView is unloaded when this method is called. When you are about to see this view again, the device reloaded it, but maybe because you set the parameters (for the rotation, etc.) in a place where it won't be called when it gets reloaded. You may use:
- (void)viewDidLoad {
UIApplication* application = [UIApplication sharedApplication];
application.statusBarOrientation = UIInterfaceOrientationLandscapeRight;
CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation([MyMath radd:90]);
landscapeTransform = CGAffineTransformTranslate (landscapeTransform, -80, 83);
[self.view setTransform: landscapeTransform];
[super viewDidLoad];
}
It occurs in the device because of we are talking real memory on the real device. You can use "Hardware > Simulate Memory Warning" when you are using the simulator.
I have an app where I would like to support device rotation in certain views but other don't particularly make sense in Landscape mode, so as I swapping the views out I would like to force the rotation to be set to portrait.
There is an undocumented property setter on UIDevice that does the trick but obviously generates a compiler warning and could disappear with a future revision of the SDK.
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
Are there any documented ways to force the orientation?
Update: I thought I would provide an example as I am not looking for shouldAutorotateToInterfaceOrientation as I have already implemented that.
I want my app to support landscape and portrait in View 1 but only portrait in View 2. I have already implemented shouldAutorotateToInterfaceOrientation for all views but if the user is in landscape mode in View 1 and then switches to View 2, I want to force the phone to rotate back to Portrait.
This is long after the fact, but just in case anybody comes along who isn't using a navigation controller and/or doesn't wish to use undocumented methods:
UIViewController *c = [[UIViewController alloc]init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];
It is sufficient to present and dismiss a vanilla view controller.
Obviously you'll still need to confirm or deny the orientation in your override of shouldAutorotateToInterfaceOrientation. But this will cause shouldAutorotate... to be called again by the system.
If you want to force it to rotate from portrait to landscape here is the code. Just note that you need adjust the center of your view. I noticed that mine didn't place the view in the right place. Otherwise, it worked perfectly. Thanks for the tip.
if(UIInterfaceOrientationIsLandscape(self.interfaceOrientation)){
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
self.view.center = CGPointMake(160.0f, 240.0f);
[UIView commitAnimations];
}
From what I can tell, the setOrientation: method doesn't work (or perhaps works no longer). Here's what I'm doing to do this:
first, put this define at the top of your file, right under your #imports:
#define degreesToRadian(x) (M_PI * (x) / 180.0)
then, in the viewWillAppear: method
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
if (self.interfaceOrientation == UIInterfaceOrientationPortrait) {
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0.0, 0.0, 480, 320);
}
if you want that to be animated, then you can wrap the whole thing in an animation block, like so:
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:1.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
if (self.interfaceOrientation == UIInterfaceOrientationPortrait) {
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0.0, 0.0, 480, 320);
}
[UIView commitAnimations];
Then, in your portrait mode controller, you can do the reverse - check to see if its currently in landscape, and if so, rotate it back to Portrait.
I was having an issue where I had a UIViewController on the screen, in a UINavigationController, in landscape orientation. When the next view controller is pushed in the flow, however, I needed the device to return to portrait orientation.
What I noticed, was that the shouldAutorotateToInterfaceOrientation: method isn't called when a new view controller is pushed onto the stack, but it is called when a view controller is popped from the stack.
Taking advantage of this, I am using this snippet of code in one of my apps:
- (void)selectHostingAtIndex:(int)hostingIndex {
self.transitioning = YES;
UIViewController *garbageController = [[[UIViewController alloc] init] autorelease];
[self.navigationController pushViewController:garbageController animated:NO];
[self.navigationController popViewControllerAnimated:NO];
BBHostingController *hostingController = [[BBHostingController alloc] init];
hostingController.hosting = [self.hostings objectAtIndex:hostingIndex];
[self.navigationController pushViewController:hostingController animated:YES];
[hostingController release];
self.transitioning = NO;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
if (self.transitioning)
return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
else
return YES;
}
Basically, by creating an empty view controller, pushing it onto the stack, and immediately popping it off, it's possible to get the interface to revert to the portrait position. Once the controller has been popped, I just push on the controller that I intended to push in the first place. Visually, it looks great - the empty, arbitrary view controller is never seen by the user.
There is a simple way to programmatically force iPhone to the necessary orientation - using two of already provided answers by kdbdallas, Josh :
//will rotate status bar
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
//will re-rotate view according to statusbar
UIViewController *c = [[UIViewController alloc]init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];
works like a charm :)
EDIT:
for iOS 6 I need to add this function:
(works on modal viewcontroller)
- (NSUInteger)supportedInterfaceOrientations
{
return (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight);
}
I've been digging and digging looking for a good solution to this. Found this blog post that does the trick: remove your outermost view from the key UIWindow and add it again, the system will then re-query the shouldAutorotateToInterfaceOrientation: methods from your viewcontrollers, enforcing the correct orientation to be applied.
See it : iphone forcing uiview to reorientate
This is no longer an issue on the later iPhone 3.1.2 SDK. It now appears to honor the requested orientation of the view being pushed back onto the stack. That likely means that you would need to detect older iPhone OS versions and only apply the setOrientation when it is prior to the latest release.
It is not clear if Apple's static analysis will understand that you are working around the older SDK limitations. I personally have been told by Apple to remove the method call on my next update so I am not yet sure if having a hack for older devices will get through the approval process.
Josh's answer works fine for me.
However, I prefer posting an "orientation did change, please update UI" notification. When this notification is received by a view controller, it calls shouldAutorotateToInterfaceOrientation:, allowing you to set any orientation by returning YES for the orientation you want.
[[NSNotificationCenter defaultCenter] postNotificationName:UIDeviceOrientationDidChangeNotification object:nil];
The only problem is that this forces a re-orientation without an animation. You would need to wrap this line between beginAnimations: and commitAnimations to achieve a smooth transition.
Hope that helps.
FWIW, here's my implementation of manually setting orientation (to go in your app's root view controller, natch):
-(void)rotateInterfaceToOrientation:(UIDeviceOrientation)orientation{
CGRect bounds = [[ UIScreen mainScreen ] bounds ];
CGAffineTransform t;
CGFloat r = 0;
switch ( orientation ) {
case UIDeviceOrientationLandscapeRight:
r = -(M_PI / 2);
break;
case UIDeviceOrientationLandscapeLeft:
r = M_PI / 2;
break;
}
if( r != 0 ){
CGSize sz = bounds.size;
bounds.size.width = sz.height;
bounds.size.height = sz.width;
}
t = CGAffineTransformMakeRotation( r );
UIApplication *application = [ UIApplication sharedApplication ];
[ UIView beginAnimations:#"InterfaceOrientation" context: nil ];
[ UIView setAnimationDuration: [ application statusBarOrientationAnimationDuration ] ];
self.view.transform = t;
self.view.bounds = bounds;
[ UIView commitAnimations ];
[ application setStatusBarOrientation: orientation animated: YES ];
}
coupled with the following UINavigationControllerDelegate method (assuming you're using a UINavigationController):
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
// rotate interface, if we need to
UIDeviceOrientation orientation = [[ UIDevice currentDevice ] orientation ];
BOOL bViewControllerDoesSupportCurrentOrientation = [ viewController shouldAutorotateToInterfaceOrientation: orientation ];
if( !bViewControllerDoesSupportCurrentOrientation ){
[ self rotateInterfaceToOrientation: UIDeviceOrientationPortrait ];
}
}
That takes care of rotating the root view according to whether an incoming UIViewController supports the current device orientation. Finally, you'll want to hook up rotateInterfaceToOrientation to actual device orientation changes in order to mimic standard iOS functionality. Add this event handler to the same root view controller:
-(void)onUIDeviceOrientationDidChangeNotification:(NSNotification*)notification{
UIViewController *tvc = self.rootNavigationController.topViewController;
UIDeviceOrientation orientation = [[ UIDevice currentDevice ] orientation ];
// only switch if we need to (seem to get multiple notifications on device)
if( orientation != [[ UIApplication sharedApplication ] statusBarOrientation ] ){
if( [ tvc shouldAutorotateToInterfaceOrientation: orientation ] ){
[ self rotateInterfaceToOrientation: orientation ];
}
}
}
Finally, register for UIDeviceOrientationDidChangeNotification notifications in init or loadview like so:
[[ NSNotificationCenter defaultCenter ] addObserver: self
selector: #selector(onUIDeviceOrientationDidChangeNotification:)
name: UIDeviceOrientationDidChangeNotification
object: nil ];
[[ UIDevice currentDevice ] beginGeneratingDeviceOrientationNotifications ];
This works for me (thank you Henry Cooke):
The aim for me was to deal with landscape orientations changes only.
init method:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
- (void)orientationChanged:(NSNotification *)notification {
//[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
CGRect bounds = [[ UIScreen mainScreen ] bounds ];
CGAffineTransform t;
CGFloat r = 0;
switch ( orientation ) {
case UIDeviceOrientationLandscapeRight:
r = 0;
NSLog(#"Right");
break;
case UIDeviceOrientationLandscapeLeft:
r = M_PI;
NSLog(#"Left");
break;
default:return;
}
t = CGAffineTransformMakeRotation( r );
UIApplication *application = [ UIApplication sharedApplication ];
[ UIView beginAnimations:#"InterfaceOrientation" context: nil ];
[ UIView setAnimationDuration: [ application statusBarOrientationAnimationDuration ] ];
self.view.transform = t;
self.view.bounds = bounds;
[ UIView commitAnimations ];
[ application setStatusBarOrientation: orientation animated: YES ];
}
I have an app where I would like to support device rotation in certain views but other don't particularly make sense in Landscape mode, so as I swapping the views out I would like to force the rotation to be set to portrait.
I realise that the above original post in this thread is very old now, but I had a similar problem to it - ie. all of the screens in my App are portrait only, with the exception of one screen, which can be rotated between landscape and portrait by the user.
This was straightforward enough, but like other posts, I wanted the App to automatically return to portrait regardless of the current device orientation, when returning to the previous screen.
The solution I implemented was to hide the Navigation Bar while in landscape mode, meaning that the user can only return to previous screens whilst in portrait. Therefore, all other screens can only be in portrait.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)pInterfaceOrientation {
BOOL lHideNavBar = self.interfaceOrientation == UIInterfaceOrientationPortrait ? NO : YES;
[self.navigationController setNavigationBarHidden:lHideNavBar animated:YES];
}
This also has the added benefit for my App in that there is more screen space available in landscape mode. This is useful because the screen in question is used to display PDF files.
Hope this helps.
I solved this quite easily in the end. I tried every suggestion above and still came up short, so this was my solution:
In the ViewController that needs to remain Landscape (Left or Right), I listen for orientation changes:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:UIDeviceOrientationDidChangeNotification object:nil];
Then in didRotate:
- (void) didRotate:(NSNotification *)notification
{ if (orientationa == UIDeviceOrientationPortrait)
{
if (hasRotated == NO)
{
NSLog(#"Rotating to portait");
hasRotated = YES;
[UIView beginAnimations: #"" context:nil];
[UIView setAnimationDuration: 0];
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-90));
self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
self.view.frame = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
[UIView commitAnimations];
}
}
else if (UIDeviceOrientationIsLandscape( orientationa))
{
if (hasRotated)
{
NSLog(#"Rotating to lands");
hasRotated = NO;
[UIView beginAnimations: #"" context:nil];
[UIView setAnimationDuration: 0];
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(0));
self.view.bounds = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
self.view.frame = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
[UIView commitAnimations];
}
}
Keep in mind any Super Views/Subviews that use autoresizing, as the view.bounds/frame are being reset explicitly...
The only caveat to this method for keeping the view Landscape, is the inherent animation switching between orientations that has to occur, when it would be better to have it appear to have no change.
iOS 6 solution:
[[[self window] rootViewController] presentViewController:[[UIViewController alloc] init] animated:NO completion:^{
[[[self window] rootViewController] dismissViewControllerAnimated:NO completion:nil];
}];
The exact code depends per app and also where you place it (I used it in my AppDelegate). Replace [[self window] rootViewController] with what you use. I was using a UITabBarController.
I found a solution and wrote something in french (but code are in english). here
The way is to add the controller to the window view (the controller must possess a good implementation of the shouldRotate.... function).
If you are using UIViewControllers, there is this method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
Return NO for the view controllers containing the views you don't want to rotate.
More info here
I don't think this is possible to do at run-time, though you could of course just apply a 90 degree transform to your UI.
This is what I use. (You get some compile warnings but it works in both the Simulator and the iPhone)
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];