I am now testing an app for Ipad. Basically I am using template for master detail application and have another portraitViewController. Now when application starts in portrait mode I want it to display only portraitViewController and when device is rotated e.g landscape mode I want to display only master-detailViewController. What is the best way to do this.
I was testing sample code for single view application but master-detail view refuses to hide:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)
interfaceOrientation duration:(NSTimeInterval)duration {
if (interfaceOrientation == UIInterfaceOrientationPortrait) {
self.view = self.portrait;
self.view.transform = CGAffineTransformIdentity;
self.view.transform =
CGAffineTransformMakeRotation(degreesToRadians(0));
self.view.bounds = CGRectMake(0.0, 0.0, 320.0, 460.0);
}
else if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform =
CGAffineTransformMakeRotation(degreesToRadians(−90));
self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 300.0);
}
else if (interfaceOrientation ==
UIInterfaceOrientationLandscapeRight) {
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform =
CGAffineTransformMakeRotation(degreesToRadians(90));
self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 300.0);
} }
The view controller should not be changing its view in real time like this. You're not seeing anything happening because the view controller's old view is still in the interface; you're not removing it from the interface. Nor should you do so. The way to make that happen is to swap out the view controller; use a different view controller and a different view.
You started with the master-detail view controller, so the app's root view controller is the UISplitViewController. It's view (the split view), it appears, is the view you want to remove. So you will have to replace the UISplitViewController as the window's rootViewController.
But that's a huge pain in the butt. I think you might be happier just putting a modal (presented) view controller in front of everything when in portrait orientation.
This is a downloadable example project that presents a view controller in response to device rotation:
https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/ch19p609rotationForcingModalView
Related
Im using the following code to adjust the height and frame of my view when flipping landscape/portrait (to compensate for hiding status bar):
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{
UIInterfaceOrientation toInterfaceOrientation = (UIInterfaceOrientation)[[UIDevice currentDevice]orientation];
if((toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)&&
!UIInterfaceOrientationIsLandscape(self.interfaceOrientation)){
NSLog(#"Going LandScape");
//self.view.frame = CGRectOffset(self.view.frame, 0, -20);
self.view.frame = CGRectMake(0, -20, self.view.frame.size.width, self.view.frame.size.height+20);
}
if(toInterfaceOrientation == UIInterfaceOrientationPortrait || toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown){
NSLog(#"Going Portrait");
//self.view.frame = CGRectOffset(self.view.frame, 0, 20);
self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height-20);
}
}
It works fine on my first view controller when loading the app. But whenever I present a new (or the same) view controller, it doesn't move up the frame when going landscape. I get the correct logs as for when going portrait or landscape, and obv I have the same code on all my view controllers. Can someone tell me whats going on? (note presenting the same view controller will still give me issues when going to landscape even though this was working initially)
EDIT:
To make things even worse, I tried adding a button in the if(landscape) statement. The button will be drawed initially, but whenever I re-present the view controller, it doesn't even draw the button. I think I'm losing my mind...
It seems that the first time i rotate to landscape, it works, no matter what VC I'm on. but then reloading any vc, will make it not work.
Solution is pretty simple in my opinion: The function viewWillTransitionToSize is getting called only after the orientations is changed so you should make helper class that will somehow manage rotation or check the orientation on view did load.
You can simply test my statement by adding breakpoint in the function.
This line of code needs to be added at start of viewWillTransitionToSize:
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
(and damn that was painful to realise what an awfully simple mistake I did (but it normally is like that:) ) .... been pulling hairs out for long with this problem)
I have rather complicated user interface for my application that supports both Portrait & Landscape orientations (excluding PortraitUpsideDown). There are two View Controllers and thus two Views. The first one represents Portrait UI and the other Landscape. Both controllers are related with the same class. I have created two Outlets for each kind of orientation and several Outlet Collections for other UI objects. The code that I use for animating the rotation is below.
This is how I declare those properties.
#property (strong, nonatomic) IBOutlet UIView *landscape;
#property (strong, nonatomic) IBOutlet UIView *portrait;
And the process of animation.
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if(toInterfaceOrientation == UIInterfaceOrientationPortrait) {
self.view = self.portrait;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadius(0));
self.view.bounds = CGRectMake(0.0, 0.0, 320.0, 568.0);
} else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadius(-90));
self.view.bounds = CGRectMake(0.0, 0.0, 568.0, 300.0);
} else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadius(90));
self.view.bounds = CGRectMake(0.0, 0.0, 568.0, 300.0);
}
}
The problem is being that the second (landscape) view doesn't exist neither at the moment of rotation nor at the moment of View's loading. Code samples that I use as an example don't work for me due to this issue. As well as I understand the thing, the second View isn't automatically loaded and I should do something about that in my ViewController...
Please help me solve that problem! Thank you in advance!
P.S. Everything works fine with using a simple .XIB file for managing UI. I just simply drag two Views and it's okay because they are both handled by the same controller. So what do I do for my Storyboard?
Summary
I have a content UIViewController that presents a settings UIViewController using a custom transition. The presentation is with presentViewController:animated:completion:.
When I later dismiss the settings with dismissViewControllerAnimated:completion:, the presenting controller is suddenly jumped back to it's initial position prior to the settings controller presentation.
I have a work around for this on the device but not the simulator. However, I'd like to know what I'm doing wrong rather than hack in a bodge that makes it go away. I also plan to make this animation interactive, and I suspect this issues will amplify when I do this.
Custom Transition – Opening the hood
The desired effect is that the presenting controller slides down the screen, and the presented controller is seen to be lying behind it from where it lifts up to fill the screen. The top of the presenting controller remains on-screen during the lifetime of use of the presented controller. It stays at the bottom of the screen, but above the presented controller.
You could imagine lifting the bonnet on a car (the front presenting controller) to see the engine behind (the presented settings), but the bonnet stays visible at the bottom for a bit of context.
I plan to refine this so that the presenting controller really appears to lift up with perspective in a 3d way, but I've not got that far, yet.
When the settings are dismissed, the original presenting controller (bonnet) should slide back up the screen and the presented controller (settings) sink back slightly (closing the bonnet).
Code
Here's the method that toggles the settings on and off the screen (it's just called by a UIButton). You'll notice that the presenting view controller sets itself up as the <UIViewControllerTransitioningDelegate>.
-(void) toggleSettingsViewController
{
const BOOL settingsAreShowing = [self presentedViewController] != nil;
if(!settingsAreShowing)
{
UIViewController *const settingsController = [[self storyboard] instantiateViewControllerWithIdentifier: #"STSettingsViewController"];
[settingsController setTransitioningDelegate: self];
[settingsController setModalPresentationStyle: UIModalPresentationCustom];
[self presentViewController: settingsController animated: YES completion: nil];
}
else
{
[self dismissViewControllerAnimated: YES completion: nil];
}
}
To implement <UIViewControllerAnimatedTransitioning> the presenting view controller also just returns itself as the <UIViewControllerAnimatedTransitioning>
-(id<UIViewControllerAnimatedTransitioning>) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return self;
}
-(id<UIViewControllerAnimatedTransitioning>) animationControllerForDismissedController:(UIViewController *)dismissed
{
// Test Point 1.
return self;
}
So finally, the presenting view controller will receive animateTransition::
-(void) animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *const fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *const toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
const BOOL isUnwinding = [toController presentedViewController] == fromController;
const BOOL isPresenting = !isUnwinding;
UIViewController * presentingController = isPresenting ? fromController : toController;
UIViewController * presentedController = isPresenting ? toController : fromController;
if(isPresenting)
{
// Add the presented controller (settings) to the view hierarchy _behind_ the presenting controller.
[[transitionContext containerView] insertSubview: [presentedController view] belowSubview: [presentingController view]];
// Set up the initial position of the presented settings controller. Scale it down so it seems in the distance. Alpha it down so it is dark and shadowed.
presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
presentedController.view.alpha = 0.7;
[UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{
// Lift up the presented controller.
presentedController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);
// Brighten the presented controller (out of shadow).
presentedController.view.alpha = 1;
// Push the presenting controller down the screen – 3d effect to be added later.
presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0);
} completion: ^(BOOL finished){
[transitionContext completeTransition: ![transitionContext transitionWasCancelled]];
}];
}
else
{
// Test Point 2.
// !!!This line should not be needed!!!
// It resets the presenting controller to where it ought to be anyway.
presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0);
[UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{
// Bring the presenting controller back to its original position.
presentingController.view.layer.transform = CATransform3DIdentity;
// Lower the presented controller again and put it back in to shade.
presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
presentedController.view.alpha = 0.4;
} completion:^(BOOL finished) {
[transitionContext completeTransition: ![transitionContext transitionWasCancelled]];
}];
}
}
-(NSTimeInterval) transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
Problem
In the code above, I've indicated !!!This line should not be needed!!!.
What's happening is that between Test Point 1 and Test Point 2 the screen position of the presenting view controller is reset to be the default full screen bounds. So, instead of being at the bottom of the screen ready to animate back up again smoothly, it suddenly jumps up the screen to position that it is meant to smoothly animate too!
I've tried various approaches to animating the presenting view controller down the screen:
I've changed its view's frame.
I've changed its view's transform.
I've changed its view's layer's 3d transform.
In all cases, at Test Point 1, when the transition delegate is asked for, the presenting controller is set up as I would expect. However, in all cases, at Test Point 2, the presenting view controller has lost the correct position and has been "cleared" to have the normal full screen position that I want to animate it to.
In the work around above I explicitly relocate the presenting view controller back to where it should be at the start of the animation with !!!This line should not be needed!!!. This seems to work on the device with the current version of iOS 7. However, on the simulator, the controller is visible at the cleared position for at least one frame.
I am suspicious that I am doing something else wrong, and that I'm going to get in to trouble with my workaround just masking another problem.
Any ideas what's going on? Thanks!
A few potential gotchas with dismissal of modally presented view controllers using custom transition animations:
Add the presented ("to") view to the container, then bring the presented view front. Don't add the presenting view as you might remove it from its current superview.
On dismiss, UIKit sets the alpha of the presented view to 0 before animateTransition is called. So you'll want to set it to 1.0 or whatever it was at the completion of the present before firing off your dismiss animation(s).
Likewise for the presented view's transform. On dismiss it gets reset to identity before your animateTransition is called.
Given all that, I think this should work:
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = transitionContext.containerView;
const BOOL isUnwinding = [toController presentedViewController] == fromController;
const BOOL isPresenting = !isUnwinding;
UIViewController *presentingController = isPresenting ? fromController : toController;
UIViewController *presentedController = isPresenting ? toController : fromController;
[containerView addSubview:presentingController.view];
[containerView bringSubviewToFront:presentingController.view];
if(isPresenting)
{
// Set up the initial position of the presented settings controller. Scale it down so it seems in the distance. Alpha it down so it is dark and shadowed.
presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
presentedController.view.alpha = 0.7;
[UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{
// Lift up the presented controller.
presentedController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);
// Brighten the presented controller (out of shadow).
presentedController.view.alpha = 1;
// Push the presenting controller down the screen – 3d effect to be added later.
presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0);
} completion: ^(BOOL finished){
[transitionContext completeTransition: ![transitionContext transitionWasCancelled]];
}];
}
else
{
presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
presentedController.view.alpha = 0.7;
[UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{
// Bring the presenting controller back to its original position.
presentingController.view.layer.transform = CATransform3DIdentity;
// Lower the presented controller again and put it back in to shade.
presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
presentedController.view.alpha = 0.4;
} completion:^(BOOL finished) {
[transitionContext completeTransition: ![transitionContext transitionWasCancelled]];
}];
}
}
Initially, I thought about using CATransition to have custom transition effect when presentViewController:animated:completion: and dismissViewControllerAnimated:completion: a View Controller. But you want to show a portion of View Controller when the setting View Controller is presented, then I think CATransition would not help because you don't have full control of how far you want to move the View Controller.
I think the easiest way is to have one single View Controller with two full screen UIView. For the first UIView (View Controller's view, that is, self.view), you layout the setting, and on the second UIView, it's the regular View. In the ViewDidLoad, you add the 2nd view by using [self.view addSubview:2ndView];. Later when you want to present the setting view, you can do
CGRect frame = secondView.frame;
frame.origin.y = the_y_coordinate_you_like;
UIView animateWithDuration:0.2 animations:^{
secondView.frame = frame;
}];
then do the other way to bring the 2ndView back.
I am doing a PhoneGap iPad application. In this I had added a HybridPage (which contains webView) as a child view controller and then after that screen I am adding a New Controller (also removed the hybridPage from ParentViewController) which contains Native Controller and again same instance of HybridPage Controller (added two controllers as childs).
Here My Native Controller size is (320,704) and Hybrid Controller size is (702,704).
Now the problem is I am not able to interact with Half frame of Hybrid controller from right side (320 width from the end of contentoffset of the view).
I have given the two viewControllers frames even though I am not able to interact with some rect of Hybrid controller.
(void)setDashboardRootViewController:(UIViewController*)rootViewController
{
if ([[self childViewControllers] containsObject:[MCIPadRootViewController getIPadRootViewController].mcWebViewController])
{
[[MCIPadRootViewController getIPadRootViewController] removeMCWebViewController];
}
[self addChildViewController:self.dashboardViewController];
[self.dashboardContainerView addSubview:self.dashboardViewController.view];
if ([rootViewController isKindOfClass:[MCLoginFlowWebViewController class]])
{
[self addChildViewController:[MCIPadRootViewController getIPadRootViewController].mcWebViewController];
[MCIPadRootViewController getIPadRootViewController].mcWebViewController.view.frame = CGRectMake(0, 0, self.mcWebViewContainerView.frame.size.width, self.mcWebViewContainerView.frame.size.height);
[self.mcWebViewContainerView addSubview:[MCIPadRootViewController getIPadRootViewController].mcWebViewController.view];
}
}
Where can i set Child ViewController's Frame. Here i had set the viewcontroller's view frame but it is not effecting.
Could anybody help me to get out from this issue.
Please find the Screen shot for clarity.
You would typically set the frame of a child view controller's view in 2 places.
Right before you add it to the parent. Search for a call to addChildViewController: in the parent view controller. Set frame right before that call (there may be 2 calls to addChildViewController:).
If the size changes due to rotation you'll need to reset the frame of the child view controller's view in the parent view controller's viewWillLayoutSubviews: method.
Here's an example from a custom split view controller.
Notice the 2 places where where #1 is set.
- (void)setMasterViewController:(UIViewController *)controller
{
if (!_masterViewController) {
// Add the new controller
controller.view.frame = self.masterView.bounds;
[self addChildViewController:controller];
[self.masterView addSubview:controller.view];
[controller didMoveToParentViewController:self];
_masterViewController = controller;
}
else if (_masterViewController != controller) {
// Transition from the old to new controller
controller.view.frame = self.masterView.bounds;
[_masterViewController willMoveToParentViewController:nil];
[self addChildViewController:controller];
self.view.userInteractionEnabled = NO;
[self transitionFromViewController:_masterViewController
toViewController:controller
duration:0.0
options:UIViewAnimationOptionTransitionNone
animations:^{}
completion:^(BOOL finished) {
self.view.userInteractionEnabled = YES;
[_masterViewController removeFromParentViewController];
[controller didMoveToParentViewController:self];
_masterViewController = controller;
}
];
}
}
Here is where #2 is set:
- (void)viewWillLayoutSubviews
{
CGRect contentFrame = self.view.bounds;
float contentWidth = contentFrame.size.width;
CGRect masterRect = contentFrame;
CGRect detailRect = contentFrame;
CGRect dividerRect = contentFrame;
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
masterRect.size.width = DEFAULT_SPLIT_POSITION;
detailRect.origin.x = DEFAULT_SPLIT_POSITION + DEFAULT_DIVIDER_WIDTH;
detailRect.size.width = contentWidth - (DEFAULT_SPLIT_POSITION + DEFAULT_DIVIDER_WIDTH);
dividerRect.size.width = DEFAULT_DIVIDER_WIDTH;
dividerRect.origin.x = DEFAULT_SPLIT_POSITION;
}
else {
masterRect.size.width = contentWidth;
detailRect.origin.x = contentWidth;
dividerRect.origin.x = contentWidth;
}
self.masterView.frame = masterRect;
self.detailView.frame = detailRect;
self.dividerView.frame = dividerRect;
}
[Edited]
So can you update your code with the following so that you call all of the view container containment methods are used and called in the correct order.
[MCIPadRootViewController getIPadRootViewController].mcWebViewController.view.frame = CGRectMake(0, 0, self.mcWebViewContainerView.frame.size.width, self.mcWebViewContainerView.frame.size.height);
[self addChildViewController:[MCIPadRootViewController getIPadRootViewController].mcWebViewController];
[self.mcWebViewContainerView addSubview:[MCIPadRootViewController getIPadRootViewController].mcWebViewController.view];
[controller didMoveToParentViewController:self];
Also I don't think your CGRectMake is correct. You normally wouldn't reference the view's frame, you would reference the parent view's bounds. Try setting it like this and see if it helps. That will have it fill the parent view but you can also tweak it.
[MCIPadRootViewController getIPadRootViewController].mcWebViewController.view.frame = self.mcWebViewContainerView.bounds;
I came up with my Own solution.
The problem is my application opens in Landscape mode So i converted my view frame to bounds.
[[[self recurringListController] view] setFrame:[[self view] bounds]];
Using the above method I overcome my issue.
In many situation need to rotate the controller and is not working.
Right now I have the inverse of the problem: it is rotating, and I want to disable.
In that ViewController I have this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown);
}
but it is auto-rotating, because not this UIViewController is asked, but his parent in UI tree. Maybe that is the problem. The root controller must return Yes for all cases, because there are a few other UIViewControllers on the stack, which has / must have Portait / Landscape support.
I can't / don't want to touch other parts, because ... there are several reasons, for eg: the app is huge, with lot of know bugs and I don't want to make 1 and test it for 1 week, other is the deadline.
Please don't suggest it shouldn't be like this and must rewritten. I know.
How to deal with this controller to force Portait ?
Please read the bolded text too: can't force the whole app to support only Portait for 1 view controller, there are many on stack!
Try marking the app's supported Interface orientations in the properties file to only being portrait. But then of course in that function you just return YES on view controllers that you want to allow rotation. But then when you push it back in the stack the other views should be portrait.
detect the Landscape rotation and rotate to Portait:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
UIInterfaceOrientation appOrientation = [UIApplication sharedApplication].statusBarOrientation;
float width = self.view.bounds.size.width;
float height = self.view.bounds.size.height;
//NSLog(#"width %3.0f, height: %3.0f", width, height);
if((fromInterfaceOrientation == UIInterfaceOrientationPortrait || fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)){
// if is rotated from Portait:
if((appOrientation == UIInterfaceOrientationLandscapeLeft || appOrientation == UIInterfaceOrientationLandscapeRight)){
// to Landscape:
CGAffineTransform transform = self.view.transform;
transform = CGAffineTransformRotate(transform, -(M_PI / 2.0));
self.view.transform = transform;
[self.view setBounds:CGRectMake(0, 0, height, width)];
}
}
else {
// it is rotated from Landscape:
if((appOrientation == UIInterfaceOrientationPortrait || appOrientation == UIInterfaceOrientationPortraitUpsideDown)){
// to Portrait:
CGAffineTransform transform = self.view.transform;
transform = CGAffineTransformRotate(transform, +(M_PI / 2.0));
self.view.transform = transform;
[self.view setBounds:CGRectMake(0, 0, height, width)];
}
}
}
it isn't the best programming paradigm, but it does the trick.
Somebody write similar like tis to accept his answer, or write a better method, if you can!