I have a background image in my main window so that when I flip views, it's not a blank white screen behind, but an image. My problem is that this image doesn't rotate when the device rotates.
Edit: As far as I can tell, Brett was correct when he pointed out that I'd have to rotate the background image manually in this instance. In case it helps anyone else out in the future, here's how I rotated it.
Inside myAppDelegate:
- (void) application:(UIApplication *)application
willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation
duration:(NSTimeInterval)duration
{
if (newStatusBarOrientation == UIInterfaceOrientationPortrait)
self.bgImage.transform = CGAffineTransformIdentity;
else if (newStatusBarOrientation == UIInterfaceOrientationPortraitUpsideDown)
self.bgImage.transform = CGAffineTransformMakeRotation(-M_PI);
else if (UIInterfaceOrientationIsLandscape(newStatusBarOrientation))
{
float rotate = ((newStatusBarOrientation == UIInterfaceOrientationLandscapeLeft) ? -1:1) * (M_PI / 2.0);
self.bgImage.transform = CGAffineTransformMakeRotation(rotate);
self.bgImage.transform = CGAffineTransformTranslate(self.bgImage.transform, 0, -self.bgImage.frame.origin.y);
}
}
I think that UIWindow passes on rotation events to child controllers. The window should contain the root view controller view then pass on the rotation event message to the view controller to manage the rotation.
You should be able to listen for these events in your app delegate and manage the image rotation manually. Otherwise, just add the image as a subview of the root view controller.
On your rootViewController make sure you implement this method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
You rootViewController will probably just be a simple UIVIew with 2 UIViews, one for the background image view and the other for your main interface view.
Related
My App's Root view controller is UISplitViewController.
Master and Detail view controllers are two navigation controllers.
What I want is, when a particular view controller gets visible in master view, I need master view to be hidden and show-able by swipe gesture.
When I implement delegate methods and set presentWithGesture to yes before setting the root view controller, it works as its normal behavior for all the view controllers coming on navigation stack. But I need it only for for one view controller. Please share any idea you have.
I previously accomplished this by triggering a rotation event and implementing the "shouldHideViewController" delegate method. The biggest issue with doing it that way is that it rarely looks good. Sometimes it appears to animate, while others it just hurries off the screen. I wrote the below functions and placed them in my appdelegate to handle hiding/showing the master. It hides with a nice animation and has worked nicely for me so far (note: this is an edit of my preliminary response to this question).
This function Will hide/show masterViewController by adjusting it's frame with an animation.
you can also pass a completion block to be called when the animation is finished incase calling controller needs to do animations in-order for it's view to layout nicely. Pass nil if not needed. Note that this also works best when using autolayout.
#pragma mark - UISplitView Hide Master
/** Will hide/show masterViewController by adjusting it's frame with an animation.
* Can pass a completion block to be called when the animation is finished incase calling
* controller needs to do animations in-order for view. Pass nil if not needed.
#param completionBlock - accepts an optional completion block or nil arguement. The completion block is called when the hide/unhide animation is complete.
#return void
*/
-(void)toggleHideMaster:(void(^)(void))completionBlock
{
__weak MyAppDelegate *delegate = self;
__weak UISplitViewController *splitView = (UISplitViewController*)self.window.rootViewController;
// Adjust the detailView frame to hide/show the masterview
[UIView animateWithDuration:0.20f
delay:0.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^(void)
{
CGRect selfFrame = splitView.view.frame;
// Get the width of the master view controller - so we know how far to animate.
CGFloat deltaWidth = delegate.masterNavigationController.topViewController.view.frame.size.width;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
if (!delegate.masterIsHidden)
{
selfFrame.size.width += deltaWidth;
selfFrame.origin.x -= deltaWidth;
}
else
{
selfFrame.size.width -= deltaWidth;
selfFrame.origin.x += deltaWidth;
}
}
else
{
if(!delegate.masterIsHidden)
{
selfFrame.size.height += deltaWidth;
if (splitView.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
selfFrame.origin.y -= deltaWidth;
}
}
else
{
selfFrame.size.height -= deltaWidth;
if (splitView.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
selfFrame.origin.y += deltaWidth;
}
}
}
[splitView.view setFrame:selfFrame];
}completion:^(BOOL finished){
if (finished)
{
delegate.masterIsHidden = !delegate.masterIsHidden;
if (completionBlock)
{
completionBlock();
}
}
}];
}
/** Method is called by the Navigation controller when the ipad is rotated. Also called when we come from the background incase we were in a full screen state. This is to get the master controller
back into the correct state.
#return void
*/
- (void)updateHideMasterOnRotation {
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
if (self.masterIsHidden) {
self.masterIsHidden = NO;
[self toggleHideMaster:nil];
}
}
}
// I observe the rotation in my detailview's navigationcontroller like this.
// We track this on the navigation controller to globally reset the master hidden state to where it needs to be. This won't take into account previously passed completion blocks. If needed that can be handled in the view controller implementing the hideable splitview.
// Again - additional things will need to be considered if supporting portrait - like resetting if we rotate to a portrait orientation.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
MYAppDelegate *appDelegate = (MYAppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate updateHideMasterOnRotation];
}
Now you have a pattern that will hide the master view with animation and can be called by any viewcontroller. This example is for an app that is locked in landscape, but supports both landscape rotations (FYI), you could update the pattern to work in portrait with a few conditionals. This also allows a minimalistic approach to this functionality. I have looked at MGSplitviewcontroller before, and while it's wonderful, I don't need all the functionality it provides. I also don't like having dependencies that are dependent on others to update when I don't need most of what they do.
Edit 12/10 - Added iOS8 support to this answer. In iOS7, the xy coords for the splitview's frame were reversed. In iOS8 they are as you would expect. So we now need to take this into account. The frame is also unchanged on rotation in iOS8, so we only update on rotation in iOS < 8.0.
Hope this helps someone out.
Happy Programming.
I have one app always presenting in Portrait mode (in the summary of the Xcode project, only the portrait orientation is supported).
Now what I want to do is when I'm using the app, from any view controllers of the app, if I rotate the device in landscape right, the app presents a view controller (ARViewController.m) in landscape right, in fact the rotation to landscape right is the trigger to present ARViewController.m. But what I've experienced is, since the the first view controller only supports portrait and even if I orient the device in landscape right, the view controller (ARViewController.m) I want to present from the first one is in portrait too, not in landscape right.
Even if I write this in the second view controller (ARViewController.m), it doesn't autorotate (this view controller can be presented in every orientations):
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
I have to rotate the iphone once after the second view controller (ARViewController.m) is presented to have all in order.
And here is how I call this second view controller (ARViewController.m) from the first view controller:
ARViewController *arVC = [[ARViewController alloc] initWithNibName:#"ARViewController" bundle:nil];
[self presentModalViewController:arVC animated:YES];
I'm calling it from "ViewController.m", this one is defined as the rootViewController in the AppDelegate.m.
This is the first time I'm doing such things, I've looked for solutions but still the same problem. Any tips on this?
I finally solved this problem, I suppose there are alternatives but this one works fine:
In fact I kept only Portrait in the orientation restrictions. Then when I turn the phone in landscape right or left, I call my ARViewController modally, but before loading it I force this view controller to landscape (in viewWillAppear) by making an appropriate rotation like here:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self transformView2ToLandscape];}
-(void) transformView2ToLandscape {
NSInteger rotationDirection;
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
if(currentOrientation == UIDeviceOrientationLandscapeLeft){
rotationDirection = 1;
}else {
rotationDirection = -1;
}
CGAffineTransform transform = [arController.viewController.view transform];
transform = CGAffineTransformRotate(transform, degreesToRadians(rotationDirection * 90));
[arController.viewController.view setTransform: transform];}
Edit: In Swift 4
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
transformViewToLansdcape()
}
func transformViewToLansdcape(){
var rotationDir : Int
if(UIDeviceOrientationIsLandscape(UIDevice.current.orientation)){
rotationDir = 1
}else{
rotationDir = -1
}
var transform = self.view.transform
//90 for landscapeLeft and 270 for landscapeRight
transform = transform.rotated(by: (rotationDir*270).degreesToRadians)
self.view.transform = transform
}
extension BinaryInteger {
var degreesToRadians: CGFloat {
return CGFloat(Int(self)) * .pi / 180
}
}
What i've found is that first of all, what's stopping the entire application from rotating is the project summary sheet. What you should do is deselect the restrictions in the project summary sheet & just put a method in each UIViewController like you'd like it to be. In the UIViewController that you have, make the method available for the landscape orientations & implement the method
[UIViewControllerSubclass willRotateToInterfaceOrientation:<interfaceOrientation> duration:<NSTimeInterval>]
what Apple says:
willRotateToInterfaceOrientation:duration:
Sent to the view controller just before the user interface begins rotating.
(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
Parameters
toInterfaceOrientation
The new orientation for the user interface. The possible values are described in
UIInterfaceOrientation.
duration
The duration of the pending rotation, measured in seconds.
Discussion
Subclasses may override this method to perform additional actions immediately prior to the rotation. For example, you might use this method to disable view interactions, stop media playback, or temporarily turn off expensive drawing or live updates. You might also use it to swap the current view for one that reflects the new interface orientation. When this method is called, the interfaceOrientation property still contains the view’s original orientation.
This method is called regardless of whether your code performs one-step or two-step rotations.
so, this seems like the method you are looking for. Just implement this, and put your view calling code inside & it should work. (also, put one of these in the presented view to return when rotated back)
also, you should consider not presenting the view controller modally as it is rotating anyways and is obvious that the display is changing.
When in landscape, transitioning from one view (that's part of a Navigation Controller stack) to another as a modal view, with UIModalTransitionStyleFlipHorizontal set as the modalTransitionStyle, the view flips vertically in landscape mode.
Everything else about the look of the views is fine after the animation, though I did notice that the frame size of the views isn't changing which is causing issues in other places of my code as well. I figured if I fix whatever is making this particular flip vertical instead of horizontal, it will fix the other issue.
I assume it has something to do with the window itself not changing orientation, but I'm not sure that's it.
Anyone have any ideas?
Talked to an Apple Engineer at WWDC and figured out the UIModalTransitionStyleFlipHorizontal does not work in landscape, it will flip what looks like in vertical.
The other issue I mentioned was because I wasn't adapting a frame to the view correctly.
There is a solution for this if you use iOS7 custom view controller transition. The ViewController which initiates the transition should confirm to protocols an implement the following methods.
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController: (UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return (id<UIViewControllerAnimatedTransitioning>)self;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return (id<UIViewControllerAnimatedTransitioning>)self;
}
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 0.7f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIView *containerView = [transitionContext containerView];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[containerView addSubview:fromVC.view];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[containerView addSubview:toVC.view];
UIViewAnimationOptions animationOption;
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
animationOption = ([toVC.presentedViewController isEqual:fromVC])?UIViewAnimationOptionTransitionFlipFromTop:UIViewAnimationOptionTransitionFlipFromBottom;
}
else {
animationOption = ([toVC.presentedViewController isEqual:fromVC])?UIViewAnimationOptionTransitionFlipFromLeft:UIViewAnimationOptionTransitionFlipFromRight;
}
[UIView transitionFromView:fromVC.view
toView:toVC.view
duration:[self transitionDuration:transitionContext]
options:animationOption
completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
The transitioning delegate of the modal ViewController to be displayed should be set like this:
[modalViewController setTransitioningDelegate:self];
For example this linke can be put in the prepareForSegue: method.
That's it.
I use a tabBar Controller as root controller. It has 4 tabs and each of its ViewControllers has
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
as well as the tabBarController itself.
But when I rotate the device (real or simulator), the screen turns at random! If it doesn't turn when I open the application it would have the same behavior until I quit the app.
I tried to add the 4 viewControllers one by one in IB to see if one was problematic, but I obtained the same issue. It only always turns when there is no tabs at all!
Please tell me if you have any ideas. Thanks!
You set every view controller to say that it responds to any possible orientation. Therefore, every view will attempt to rotate to every orientation.
Views don't really automatically rotate. You usually have to manage the placement of subview programmatically in all but the simplest views.
If you have no custom orientation code, you're probably seeing the views try to draw the portrait view in the landscape frame or vice versa. If you have autoresize subviews set your subviews will appear to scatter across the screen in a seemingly random pattern. The more you change orientation, the more random the placement becomes.
For complex views, I like to create separate viewController/view pairs for each orientation. Then I put the views in a nav controller. As the orientation changes, each view controller will push or pop the appropriate view controller for the coming orientation onto/off the stack. To the user, this looks like a single view is gracefully redrawing itself. (This is especially useful if you have non-standard UI elements that have to be manually rotated with transforms)
You have to subclass UITabBarController and implement shouldAutorotateToInterfaceOrientation:
Actually, I just want my first tab view controller to rotate. So I put this code in my custom tabBarController :
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
if (self.selectedIndex == 0) {
return toInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
}else {
return toInterfaceOrientation == UIInterfaceOrientationPortrait;
}
}
but I had the same problem. I use a custom orientation code for my first tab view controller when turning to landscape. Called with the following function in my custom tabBarcontroller:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (toInterfaceOrientation == UIInterfaceOrientationPortrait) {
//rotation to Portrait
lastOrientation = toInterfaceOrientation;
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];
[self.selectedViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
else if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
if (!UIInterfaceOrientationIsLandscape(lastOrientation)) {
//rotation to Landscape
[self.selectedViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
lastOrientation = toInterfaceOrientation;
}
}
I found that if you set the selected tab programmatically the tabViewController rotates erratically.
I'm trying to create an iPhone application that is always in landscape mode, using the Utility application template. Here's what I did:
Create a new iPhone application project, using the Utility Application template
In Interface Builder, rotate all the views 90 degrees.
In Interface Builder, add a label to the middle of the MainView. Stretch it all the way across the view, set the alignment to centered, and set the autosizing springs so that it can stretch horizontally.
In Info.plist, add the key "UIInterfaceOrientation" with value "UIInterfaceOrientationLandscapeRight"
In the controller classes, change the shouldAutorotateToInterfaceOrientation methods to "return (interfaceOrientation == UIInterfaceOrientationLandscapeRight) || (interfaceOrientation == UIInterfaceOrientationLandscapeLeft);"
Run the app.
When I launch my app, it comes up in landscape orientation, but the main view only covers the top half of the display, and it is stretched horizontally. I get the same results in both the simulator and on an actual device. I've seen it with versions 2.2 and 2.2.1 of the SDK.
I have been able to work around the problem by adding the following step to the above:
Add "self.view.autoresizesSubviews = NO;" to RootViewController's viewDidLoad method after "[super viewDidLoad];".
If I do this, then it works as expected. But this feels like a hack. Why should this be necessary?
I don't think it is a transformation issue. All elements are drawn in the proper orientation and with the proper scaling. The problem seems to be that the bounds rectangles of the main view gets funky. It looks like the height of the main view is being cut by a little more than half, and the width is being increased by about 50%.
If I do the exact same set of steps using the View-based Application template instead of Utility, then everything works as expected. So I'm pretty sure the problem is specific to how a Utility application manages its views.
Anybody understand what's going on here?
I was going to say that setting this key does not rotate your interface; you still need to lay out your content in landscape mode and do the appropriate rotation using CFAffineTransform - see "Launching in Landscape Mode" in iPhone OS Programming Guide. Going to find the reference for you, I found this comment: "To launch a view controller–based application in landscape mode in versions of iPhone OS prior to v2.1, you need to apply a 90 degree rotation to the transform of the application’s root view in addition to all the preceding steps. Prior to iPhone OS 2.1, view controllers did not automatically rotate their views based on the value of the UIInterfaceOrientation key. This step is not necessary in iPhone OS 2.1 and later, however."
So if you're running pre-2.1, you need to add this code to your viewDidLoad method in your view controller. (Otherwise, can you post some code?)
-(void)viewDidLoad
// After loading the view, transform the view so that the co-ordinates are right for landscape
// As described in iPhone Application Programming Guide
// Weird, I'm sure this used to be needed, but it doesn't now. The one in CardScrollViewController is needed though.
{
[super viewDidLoad];
CGAffineTransform transform = self.view.transform;
CGPoint center = CGPointMake(kScreenHeight / 2.0, kScreenWidth / 2.0);
// 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;
}
Jane describes the setting of UIInterfaceOrientation to UIInterfaceOrientationLandscapeRight (or UIInterfaceOrientationLandscapeLeft), and the rotation settings recommended in the documentation, but I used a slightly different block of code (to the same end) in my root view controller:
- (void)loadView
{
UIView *primaryView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
primaryView.backgroundColor = [UIColor clearColor];
// Start in landscape orientation, and stay that way
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationLandscapeRight)
{
CGAffineTransform transform = primaryView.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(60.0, bounds.size.height / 2.0);
// Set the center point of the view to the center point of the window's content area.
primaryView.center = center;
// Rotate the view 90 degrees around its new center point.
transform = CGAffineTransformRotate(transform, (M_PI / 2.0));
primaryView.transform = transform;
}
self.view = primaryView;
[primaryView release];
}
In addition to that, I implemented the following delegate method in my root view controller:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return ( (interfaceOrientation == UIInterfaceOrientationLandscapeLeft) || (interfaceOrientation == UIInterfaceOrientationLandscapeRight));
}
Finally, I was encountering weird glitches with the Simulator not auto-rotating properly, so I needed to implement the following delegate method in my UIApplicationDelegate:
- (void)application:(UIApplication *)application willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation duration:(NSTimeInterval)duration;
{
// This prevents the view from autorotating to portrait in the simulator
if ((newStatusBarOrientation == UIInterfaceOrientationPortrait) || (newStatusBarOrientation == UIInterfaceOrientationPortraitUpsideDown))
[application setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:NO];
}
After all that, my application was able to start in landscape (right) and stay in that orientation under the 2.0 firmware and in the Simulator.
Try setting the orientation property of the view to Landscape in the nib. This property can be found in 4th tab[Attributes Inspector] of Info View of the UIView under Simulated Metrices.