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.
Related
iOS Developers will surely knows about the issue about status bar and the famous "slide/hamburger/drawer". The issue is well explained here: http://uxmag.com/articles/adapting-ui-to-ios-7-the-side-menu
I'm using MMDrawerController library and it has a nice hack that lets us to create a dummy status bar just above the container view controller. Unfortunately this doesn't work really good. What's the news? The news is that I stumbled upon an app (Tinder) that perfectly solve this mind blowing issue. I've created a gif that perfectly shows what Tinder does.
You need to wait a few seconds for seeing the gif because there's a bug in it and I don't know how to get rid of. Just wait one/two seconds and you will able to see the gif correctly.
Anyway, what Tinder does? When the user taps on the top left menu button and begin to swipe right the status bar fades out neatly. And when the view is revert to the original position the status bar will show up again.
I am both happy and a bit sad for this because this means that a way must be to do it but I really don't know how to implement it (perhaps hacking MMDrawerController). Any help will be so much appreciated.
IMPORTANT
Please pay attention to the fact that the method setStatusBarHidden: will completely hide the status bar, this means that the entire view is with a height -20px. This is obviously not the solution because as you can see from the gif the view is not stretched.
Your main problem is with MMDrawerController. If you'll digg into it you'll find a lot of methods statusbar related such as setShowsStatusBarBackgroundView setStatusBarViewBackgroundColor and more. Something in their code pushes the view up when the statusbar is hidden.
Alternatively you can use another drawer controller or use custom code.
Here's a simple way how to accomplishe this:
ViewControllerA:
-(BOOL)prefersStatusBarHidden
{
return _hidden;
}
- (void)statusHide
{
[UIView animateWithDuration:0.4 animations:^() {[self setNeedsStatusBarAppearanceUpdate];
}completion:^(BOOL finished){}];
}
ViewControllerB: (Container in ViewControllerA)
- (IBAction)move:(UIButton *)sender
{
parent = (ViewController*)self.parentViewController;
parent.hidden = !parent.hidden;
CGRect frame = parent.blueContainer.frame;
if(parent.hidden)
{
frame.origin.x = 150;
}
else
{
frame.origin.x = 0;
}
[UIView animateWithDuration:1 animations:^() {parent.blueContainer.frame = frame;}completion:^(BOOL finished){}];
[parent statusHide];
}
For iOS 6 compatieblty use:
[[UIApplication sharedApplication] setStatusBarHidden:_hidden withAnimation:UIStatusBarAnimationFade];
The table view and other subviews will stay in their location and won't be pushed up.
Edit:
Adding a NavigationBar:
UINavigationController will alter the height of its UINavigationBar to
either 44 points or 64 points, depending on a rather strange and
undocumented set of constraints. If the UINavigationController detects
that the top of its view’s frame is visually contiguous with its
UIWindow’s top, then it draws its navigation bar with a height of 64
points. If its view’s top is not contiguous with the UIWindow’s top
(even if off by only one point), then it draws its navigation bar in
the “traditional” way with a height of 44 points. This logic is
performed by UINavigationController even if it is several children
down inside the view controller hierarchy of your application. There
is no way to prevent this behavior.
Taken from here
You could very simply subclass UINavigationController and create your own navbar to avoid this annoyness.
i don't know if it will sove your problem but i got almost the same effect using the SWRevealViewController project. In the appDelegate I've set the delegate method from this class to do this:
- (void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position {
#ifdef DEBUG
NSArray *teste = #[#"FrontViewPositionLeftSideMostRemoved",#"FrontViewPositionLeftSideMost",#"FrontViewPositionLeftSide",#"FrontViewPositionLeft",#"FrontViewPositionRight",#"FrontViewPositionRightMost",#"FrontViewPositionRightMostRemoved"];
NSLog(#"%# %d", teste[position], position);
#endif
if (position == FrontViewPositionRight)
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
UINavigationController *frontViewController = (id)revealController.frontViewController;
frontViewController.navigationBar.centerY += (position == FrontViewPositionRight) ? 20 : 0; // 20 == statusbar heihgt
}
- (void)revealController:(SWRevealViewController *)revealController didMoveToPosition:(FrontViewPosition)position {
if (position == FrontViewPositionLeft)
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
}
centerY is a category in the UIView which sets the center.y without dealing the boring part of setting frame variables.
Here is how you should do that in iOS 7:
#implementation ViewController
{
BOOL _hideStatusBar;
}
-(UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleDefault;
}
-(UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return UIStatusBarAnimationFade;
}
-(BOOL)prefersStatusBarHidden
{
return _hideStatusBar;
}
-(void)setStatusBarHidden:(BOOL)hidden
{
[UIView animateWithDuration:1.0 animations:^{
_hideStatusBar = hidden;
[self setNeedsStatusBarAppearanceUpdate];
}];
}
#end
Check out the method setStatusBarHidden:withAnimation: on UIApplication. It will allow you to show or hide the status bar and the animation can be none, fade, or slide. You just need to add a call to hide the bar and one to show the bar at the correct times and decide if you like the fade as you illustrated or if the slide works better for you.
https://developer.apple.com/library/ios/DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#//apple_ref/occ/instm/UIApplication/setStatusBarHidden:withAnimation:
You can used -setStatusBarHidden:withAnimation: if you adjust your views frame in -viewDidAppear:, then you will not see any stretch.
Note that autolayout is disabled.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGRect frame = self.view.frame;
// adjust root view frame
frame.origin.y -= 20;
frame.size.height += 20;
[self.view setFrame:frame];
// adjust subviews y position
for (UIView *subview in [self.view subviews])
{
CGRect frame = subview.frame;
frame.origin.y += 20;
[subview setFrame:frame];
}
}
- (IBAction)sliderChanged:(id)sender
{
UISlider *s = (UISlider *)sender;
if (s.value > .5)
{
UIApplication *app = [UIApplication sharedApplication];
if (![app isStatusBarHidden])
[app setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
}
else
{
UIApplication *app = [UIApplication sharedApplication];
if ([app isStatusBarHidden])
[app setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
}
}
I have a tab bar application where everything is working fine. I have rotations of the device all working fine with the various Tab Bar View controllers.
Alas it was suggested that a couple of the View Controllers needed a help page. To this end I created a new ViewController that contains a UIWebView (where help can be built into an HTML file).
I create the new "HelpViewController" as follows:
mpHelpPage = [[HelpPageViewController alloc] init];
[mTabBarController.view addSubview: mpHelpPage.view];
[mpHelpPage retain];
mpHelpPage.view.alpha = 0.75f;
This brings up the help page no problems when I'm in portait mode. Unfortunately when I'm in landscape mode and I do the above code it adds the HelpViewController in Portrait (meaning it extends off the bottom of the screen).
As such when I alloc the ViewController above I need some way of telling the ViewController to rotate to the current device orientation.
I am, however, at a loss as to how to get it to do this. Any help would be much appreciated!
I handle this annoyance by putting an orientation check in viewWillAppear:, e.g.
if (self.interfaceOrientation == UIInterfaceOrientationPortrait ||
self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
// custom code or call willRotate
} else {
// custom code or call willRotate
}
You can also do this if you prefer
if (UIDeviceOrientationIsPortrait(self.interfaceOrientation)) {
// custom code or call willRotate
} else {
// custom code or call willRotate
}
you should either set the frame-property of your subview in willRotateToInterfaceOrientation:duration: of your ViewController
or you write your own View and set the frame-property in layoutSubviews of your View
The added Subview should handle the layout of its subviews.
Since you've added HelpViewController as a subview and no UIViewController controls it, it will not be resized. You can resize HelpViewController's view manually by detecting a change in the orientation in the shouldAutorotateToInterfaceOrientation: method of the current UIViewController. This method passes the current orientation as its argument, so just check which is the current orientation and set a frame accordingly as:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if((interfaceOrientation==UIInterfaceOrientationLandscapeLeft) || (interfaceOrientation==UIInterfaceOrientationLandscapeRight))
mpHelpPage.view.frame = CGRectMake(0,0,480,300);
else
mpHelpPage.view.frame = CGRectMake(0,0,320,460);
return YES;
}
Or, Instead of adding HelpViewControlleras a subView, try [self.navigationController pushViewController:HelpViewController animated:YES];
When I am switching between Portrait to Landscape view (&Vice Versa) in iPad, position of my popover view gets garbled. Here is how I am calculating frame of my popover view:
aRect = self.myElement.frame;
aRect.origin.x += aRect.size.width;
[aPopOver presentPopoverFromRect:aRect inView:self.myElement.superview permittedArrowDirections:UIPopoverArrowDirectionRight animated:YES];
Please tell me whats wrong here?
From the UIPopoverController documentation: (emphasis mine)
If the user rotates the device while a
popover is visible, the popover
controller hides the popover and then
shows it again at the end of the
rotation. The popover controller
attempts to position the popover
appropriately for you but you may have
to present it again or hide it
altogether in some cases. For example,
when displayed from a bar button item,
the popover controller automatically
adjusts the position (and potentially
the size) of the popover to account
for changes to the position of the bar
button item. However, if you remove
the bar button item during the
rotation, or if you presented the
popover from a target rectangle in a
view, the popover controller does not
attempt to reposition the popover. In
those cases, you must manually hide
the popover or present it again from
an appropriate new position. You can
do this in the
didRotateFromInterfaceOrientation:
method of the view controller that you
used to present the popover.
ok, i notice something weird about your code.
any reason you are adding the size of the wide to the origin of aRect's x position?
aRect.origin.x += aRect.size.width;
im assuming you want this to be the top right corner....
You can uncomment the code in your .m file and make it like so:
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
Return YES; // for supported orientations
//otherwise return (interfaceOrientation == UIInterfaceOrientationLandscape); if you want only landscape mode.
}
Or what i would do in your situation if you want to layout your subviews is use the didRotateFromIntferfaceOrientation: like so:
(void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
[self layoutSubviews];
}
and also layoutSubviews
- (void)layoutSubviews
{
NSLog(#"layoutSubviews called");
...recalc rects etc based on the new self.view.bounds...
}
It works like so.
PK
This is an old question, but I see it wasn't clear to OP what he should do with Kris Markel's suggestion. This is documented in Technical Q&A QA1694. Just present the popover again.
Let's say you've put your popover code above in a method called showPopover. Just call that method on rotation, first making sure it's visible:
-(void)showPopover {
aRect = self.myElement.frame;
aRect.origin.x += aRect.size.width;
[self.aPopOver presentPopoverFromRect:aRect inView:self.myElement.superview permittedArrowDirections:UIPopoverArrowDirectionRight animated:YES];
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)oldOrientation
{
if ([self.aPopover isPopoverVisible]) {
[self showPopover];
}
}
Use the below mentioned UIPopoverPresentationControllerDelegate method in the viewcontroller.
func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController,
willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>,
in view: AutoreleasingUnsafeMutablePointer<UIView>) {
rect.pointee = self.view.bounds
}
I have a subclass of UIViewController which handles a UIView. The viewcontroller is presented modally (it slides up from the bottom of the screen). At the top of the view, i have added a navigation bar. Note that this bar is not handled by a navigation controller.
I want to get the navbar to shrink in height when the view rotates to landscape (similar to how it behaves when it is handled by a UINavigationController). However, I can't set its autoresizing mask to flexible height in IB, and doing so in code causes the navbar to disappear completely.
Is there a way to do this? How is it done by the UINavigationController?
P.S. I would prefer not having to resort to a scaling transform, since this would mess up the text in the title.
EDIT: I solved it with a little help, read the answer posted below.
Rather than set it's autoresizing mask, why don't you just check the current orientation in viewWillAppear, as well as in didRotateFromInterfaceOrientation, and set the appropriate frame?
- (void) updateNavBar {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if ((UIInterfaceOrientationLandscapeLeft == orientation) ||
(UIInterfaceOrientationLandscapeRight == orientation)) {
myNavBar.frame = CGRectMake(0, 0, 480, 34);
} else {
myNavBar.frame = CGRectMake(0, 0, 320, 44);
}
}
- (void) viewWillAppear {
[self updateNavBar];
// ... SNIP ...
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[self updateNavBar];
// ... SNIP ...
}
I found the solution, and in hindsight i feel rather stupid. I just had to include flexible bottom margin in the navbar's autoresize mask. Credit is due to user RayNewbie in this thread, which pointed me to the solution:
http://discussions.apple.com/thread.jspa?messageID=8295525
I'm having a real issue with UITabBarController.
The outcome I'm after is the following:
1) in portrait mode, a simple tab bar based application (with navigation bars) nothing too fancy.
2) in landscape mode, I want to use my own UIViewController ignoring the UITabBar completely.
The approach (I tried many variants) I tried last which I fail to understand why is not "working" is the following:
I have a custom UIViewController (Call this AA) that is suppose to manage "everything".
This controller is added to the window in application start and in its loadView creates two controllers: a UITabBarController (Call this TBC) and a UILandscapeController (Call this LSC). then I add the tabbarcontroller view as a subview of AA's view.
now in AA class I override the didRotate blah or willRotate blah and basically want to switch between the two views, by this I means something like: (pseudo code):
going from portrait to landscape:
[TBC.view removeFromSuperView];
[AA.view addSubview:LSC.view];
and when returning to portrait reverse it.
[LSC.view removeFromSuperView];
[AA.view addSubview:TBC.view];
The amount of problems I have (well, it simple rotates wrongly creating a real messed up interface) are something completely unexplained. It seems like the tabbarcontroller view does not "like" at all to be in the standard view heirarchy but rather it wants to be attached directly to the screen.
I wonder what is the best approach to achieve my goal and why the tabbar does not like to be a subview of a view,
any hints mostly appreciated.
-t
Just in case you still need the answer, or someone else stumbles onto this, I've done the same thing and got it working, but there are a couple of hoops you have to jump through. In order to rotate a UITabBarController's view, there are four things you have to do:
Remove the status bar before switching to the view
Rotate the view to the new frame
Add the status bar back to the view
Switch to the view.
I've got a RootRotationController that does this that looks like this:
#implementation RootRotationController
#define degreesToRadian(x) (M_PI * (x) / 180.0)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if ((UIInterfaceOrientationPortrait == interfaceOrientation) || (UIInterfaceOrientationPortraitUpsideDown == interfaceOrientation)) {
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
}
// Return YES for supported orientations
return YES;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
if (UIInterfaceOrientationLandscapeLeft == interfaceOrientation) {
self.view = self.landscape.view;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(-90));
self.view.bounds = CGRectMake(0, 0, 480, 300);
} else if (UIInterfaceOrientationLandscapeRight == interfaceOrientation) {
self.view = self.landscape.view;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0, 0, 480, 300);
} else if (UIInterfaceOrientationPortrait == interfaceOrientation) {
mainInterface.view.transform = CGAffineTransformIdentity;
mainInterface.view.transform = CGAffineTransformMakeRotation(degreesToRadian(0));
mainInterface.view.bounds = CGRectMake(0, 0, 300, 480);
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];
self.view = mainInterface.view;
} else if (UIInterfaceOrientationPortraitUpsideDown == interfaceOrientation) {
mainInterface.view.transform = CGAffineTransformIdentity;
mainInterface.view.transform = CGAffineTransformMakeRotation(degreesToRadian(180));
mainInterface.view.bounds = CGRectMake(0, 0, 300,480);
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];
self.view = mainInterface.view;
}
}
In addition, you should know that shouldAutorotateToInterfaceOrientation is called just after adding the root controller's view to the window, so you'll have to re-enable the status bar just after having done so in your application delegate.
Your problem comes from the typo, I think. Change removeFromSuperView to removeFromSuperview.
Though, it still has a problem. Tab bar doesn't rotate properly. It go upwards till it disappers.
How about not removing the tab bar, and make it transparent.
Check out the UIViewController instance method rotatingFooterView in the docs.
Or, you may manage TabBar by yourself, not through the UITabBarController.