Navigation Bar on top when rotating - objective-c

I have UIViewController (for example, loginVC) and I'm trying to add it's view on top of all views.
I tried to add this view to AppDelegate
[[AppDelegate sharedDelegate].window addSubview:loginVC.view];
But in this case autorotation doesn't work, so I tried to add this view to NavigationController's view. NavigationController is rootViewController:
[[AppDelegate sharedDelegate].navigationController.view addSubview:loginVC.view];
It looks good and autorotating, but it has strange behavior when rotating.
After beginning of rotation, navigation bar is showing on top of loginVC.view and at the end of rotation is going behind this view, like it shown on screenshots (I've set red background to make it more visible, background is transparent, to see all stuff behind this view):
What I've tried:
I found this somewhere on stackoverflow: disable UIView animations before rotating and enable them after rotating - doesn't look good, because rotating occurs without animation (it's a bit obvious)
tried to make navigationBar hidden before rotation and make it visible after rotation, but in this case navigationBar bringing on top of loginVC.view
Next thing I gonna do - add this view on AppDelegate's window and handle rotation manually, but maybe there is some better way to do this?
UPD:
screenshots:
You can see issue on second screenshot: navigation bar is on top

add your viewController in uinavigationcontroller and push uinavigationcontroller then always navigation bar is visible.

My friend helped me with this problem
Here is the solution:
In AppDelegate I've created UIWindow property:
//AppDelegate.h
#property (nonatomic, strong) UIWindow *loginWindow;
Initialized it when application starts
//AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
....
self.loginWindow = [[UIWindow alloc] init];
self.loginWindow.windowLevel = UIWindowLevelStatusBar;
self.loginWindow.frame = [[UIScreen mainScreen] bounds];
self.loginWindow.backgroundColor = [UIColor clearColor];
....
return YES;
}
And then, in loginVC:
#interface loginVC ()
#property (nonatomic, weak) UIWindow *loginWindow;
#end
#implementation
....
- (void)show {
// setting up loginVC view
if (!self.loginWindow) {
self.loginWindow = [[AppDelegate sharedDelegate] loginWindow];
}
if (![self.loginWindow.rootViewController isEqual:self]) {
[self.loginWindow setRootViewController:self];
}
self.loginWindow.hidden = NO;
//UPD:
//[self.loginWindow makeKeyAndVisible];
//UPD2:
[self.loginWindow makeKeyWindow];
}
- (void)hide {
// hiding view and stuff
[[[AppDelegate sharedDelegate] loginWindow] setHidden:YES];
//UPD:
//[[[AppDelegate sharedDelegate] window] makeKeyAndVisible];
//UPD2:
[[[AppDelegate sharedDelegate] window] makeKeyWindow];
}
#end
UPD:
No need to use makeKeyAndVisible method of UIWindow, second window will be always on top of first one.
UPD2:
Again updating my answer, maybe it will be useful for somebody.
Without makeKeyAndVisible I couldn't use UITestFields so I uncommented that code and faced another problem:
I have UIViewController, create an instance of another UIViewController inside this controller and call [self presentViewController:...]. In presented UIViewController I'm creating loginVC, but when I call
[[[AppDelegate sharedDelegate] window] makeKeyAndVisible];
presented viewController disappears, but first view controller still has this controller as presentedViewController, so I can't present other view controllers.
My solution was change makeKeyAndVisible on makeKeyWindow.

Related

How do I implement a UINavigationController in this case?

current version of my project :
I have 5 different UIViewControllers in my app. I've set my
FirstViewController to be the Initial View Controller using the
Attributes Inspector. I move back and forth from one ViewController to
another by using buttons to which I assign modal segues, from one
ViewController to another, using the StoryBoard
What I want to change:
I want to keep the navigation buttons obviously, delete the modal segues and use
a UINavigationController instead. If I understand the concept
correctly, when using a UINavigationController I need to go into each
UIButton-IBAction and at the very end of the method I have to push the next
ViewController I want to move to, onto my NavigationController (do I also
have to pop the current one first?). However, I can't figure out how
to implement all that correctly.
What I've done so far:
I removed all modal segues from the storyboard and kept the navigation buttons along with their corresponding IBActions
I unchecked the box in the Attributes Inspector that was making my FirstViewController the initial View Controller of my app
I went into my AppDelegate.m and tried to create the Navigation Controller there and make my FirstViewController be the RootViewController
MyAppDelegate.m
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIViewController *myFirstViewController = [[FirstViewController alloc] init];
UINavigationController *myNavigationController = [[UINavigationController alloc] initWithRootViewController:myFirstViewController];
[myNavigationController pushViewController:myFirstViewController animated:YES];
// Override point for customization after application launch.
return YES;
}
I then tried to test if the above was working by going into the IBAction of a
navigation button on my FirstViewController and implemented the
following in order to move to my SecondViewController when the
button is pressed :
FirstViewController.m
- (IBAction)goRightButton:(UIButton *)sender
{
// some code drawing the ButtonIsPressed UIImageView on the current View Controller
UIViewController *mySecondViewController = [[SecondViewController alloc] init];
[self.navigationController pushViewController:mySecondViewController animated:YES];
}
but nothing happens. What am I doing wrong ?
You are not linking your XIB file. Please add your navigation controller as
UIViewController *myFirstViewController = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
navigationController = [[UINavigationController alloc] initWithRootViewController:myFirstViewController];
Use following code to move from one view to another
UIViewController *mySecondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
[self.navigationController pushViewController:mySecondViewController animated:YES];
If you are using a storyboard, you should just drag in the navigation controller there and hook it up to your app delegates. As long as it is the main storyboard, and you have identified a view controller to load first, you do not need to load any views in your app delegate.
In order to push a view programmatically that's in a storyboard, you need to do something like the following:
//bundle can be nil if in main bundle, which is default
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
MyCustomViewController *customVC = (MyCustomViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"customVC"];
//standard way
[self.navigationController pushViewController:customVC animated:YES];
//custom animation
[UIView transitionWithView:self.navigationController.view duration:0.5 options:UIViewAnimationOptionTransitionCurlUp animations:^{
[self.navigationController pushViewController:customVC animated:NO];
} completion:nil];
You identify the view controller with the identifier you add in the storyboard editor. Below are some screenshots to help show what I mean.

UIPageViewControllerSpineLocation Delegate Method Not Firing

Major head-scratcher all day on this one :-(
I have an instance of a UIPageViewController that does not appear to be firing the delegate method:
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
I have tried various methods of displaying the UIPageViewController and have settled on a programatic approach (as opposed to a Storyboard one) that appears to be working correctly, with one exception... when rotating the iPad to landscape the spine does not appear mid-point as expected. I simply cannot find out why the delegate method does not get called.
Code Explanation (simplified for example)
Consider three classes as follows:
RootViewController - loaded when the app starts
PageViewController - loaded by RootViewController upon user initiation
PageContentViewController - loaded by PageViewController when pages are needed
Fairly self-explanatory. The RootViewController is loaded by the app upon launch. When the user taps an image within this view controller's view (think magazine cover opening a magazine) it launches the PageViewController as follows:
PageViewController *pvc = [[PageViewController alloc] initWithNibName:#"PageView"
bundle:[NSBundle mainBundle]];
pvc.view.frame = self.view.bounds;
[self.view addSubview:pvc.view];
In the actual app there is animation etc to make the transition all nice, but essentially the PageViewController's view is loaded and takes fullscreen.
PageViewController
This is the workhorse (only relevant methods shown). I have tried various examples from the infinite world of Google and written directly from the Apple docs...
#interface PageViewController : UIViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
#property (nonatomic, strong) UIPageViewController *pageViewController;
#property (nonatomic, strong) NSMutableArray *modelArray;
#end
#implementation TXCategoryController
-(void)viewDidLoad
{
[super viewDidLoad];
// Simple model for demo
self.modelArray = [NSMutableArray alloc] init];
for (int i=1; i<=20; i++)
[self.modelArray addObject:[NSString stringWithFormat:#"Page: %d", i]];
self.pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
PageContentViewController *startupVC = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
startupVC.pageLabel = [self.modelArray objectAtIndex:0];
[self.pageViewController setViewControllers:[NSArray arrayWithObject:startupVC]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
self.pageViewController.view.frame = self.view.bounds;
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
// Setting a break point in here - never gets called
if (UIInterfaceOrientationIsPortrait(orientation))
{
// Relevant code to create view...
return UIPageViewControllerSpineLocationMin;
}
// Relevant code to create 2 views for side-by-side display and
// set those views using self.pageViewController setViewControllers:
return UIPageViewControllerSpineLocationMid
}
#end
This all works perfectly well as I mentioned earlier. The PageViewController's view gets shown. I can swipe pages left and right in both portrait and landscape and the respective page number appears. However, I don't ever see two pages side-by-side in landscape view. Setting a breakpoint in the spineLocationForInterfaceOrientation delegate method never gets called.
This is such a head-scratcher I have burned out of ideas on how to debug/solve the problem. It almost behaves like the UIPageViewController isn't responding to the orientation changes of the device and therefore isn't firing off the delegate method. However, the view gets resized correctly (but that could be just the UIView autoresizing masks handling that change).
If I create a brand new project with just this code (and appropriate XIb's etc) it works perfectly fine. So something somewhere in my actual project is causing this. I have no idea where to continue looking.
As usual, any and all help would be very much appreciated.
Side Note
I wanted to add the tag 'uipageviewcontrollerspinelocation' but couldn't because it was too long and I didn't have enough reputation (1500 required). I think this is a devious ploy on Apple's part to avoid certain tags in Stackoverflow... ;-)
Finally found the problem. It was something of a red herring in its symptoms, but related just the same.
Putting a break point in the shouldAutorotateToInterfaceOrientation: method was a natural test to see if the UIViewController was even getting a rotation notification. It wasn't which led me to Apple's technical Q&A on the issue: http://developer.apple.com/library/ios/#qa/qa1688/_index.html
The most relevant point in there was:
The view controller's UIView property is embedded inside UIWindow but alongside an additional view controller.
Unfortunately, Apple, in its traditional documentation style, doesn't provide an answer, merely confirmation of the problem. But an answer on Stack Overflow yielded the next clue:
Animate change of view controllers without using navigation controller stack, subviews or modal controllers?
Although my RootViewController was loading the PageViewController, I was doing it as a subview to the main view. This meant I had two UIViewController's in which only the parent would respond to changes.
The solution to get the PageViewController to listen to the orientation changes (thus triggering the associated spine delegate method) was to remove addSubview: and instead present the view controller from RootViewController:
[self presentViewController:pac animated:YES completion:NULL];
Once that was done, the orientation changes were being picked up and the PageViewController was firing the delegate method for spine position. Only one minor detail to consider. If the view was launched in landscape, the view was still displaying portrait until rotated to portrait and back to landscape.
That was easily tweaked by editing viewDidLoad as follows:
PageContentViewController *page1 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
NSDictionary *pageViewOptions = nil;
NSMutableArray *pagesArray = [NSMutableArray array];
if (IS_IPAD && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
pageViewOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:UIPageViewControllerSpineLocationMid]
forKey:UIPageViewControllerOptionSpineLocationKey];
PageContentViewController *page2 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
[pagesArray addObject:page1];
[pagesArray addObject:page2];
}
else
{
[pagesArray addObject:page1];
}
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:pageViewOptions];
self.pageViewController.delegate = self;
[self.pageViewController setViewControllers:pagesArray
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
Job done and problem solved.

Reversal of Custom Segue with Storyboarding

I have a custom segue animation that occurs when pushing a new view controller onto the stack. When popping the view controller that was presented with said custom segue, however, the default navigation controller animation happens (that is, the current view controller animates to the right while the parent view controller translates on-screen from the left edge).
So my question is this: is there a way to write a custom pop segue animation which happens when popping a view controller off the stack?
Edit (solution):
I ended up defining a custom segue similar to the selected answer. In the Storyboard, I dragged a custom segue from the child view controller back to its parent, gave it an identifier and the newly written reverse segue as its class. Yes, I realize it is virtually identical to a modal transition. Client requirements necessitated this madness, so before anyone comments, understand that I know one shouldn't have to do this under normal circumstances.
- (void)perform {
UIViewController *src = (UIViewController *)self.sourceViewController;
UIViewController *dest = (UIViewController *)self.destinationViewController;
[UIView animateWithDuration:0.3 animations:^{
CGRect f = src.view.frame;
f.origin.y = f.size.height;
src.view.frame = f;
} completion:^(BOOL finished){
src.view.alpha = 0;
[src.navigationController popViewControllerAnimated:NO];
}];
}
Yes. Here is an example where I pop to the top level. When your create the segue in Storyboard. Use select or enter the new new segue class in the attributes inspector.
//
// FlipTopPop.h
#import <UIKit/UIKit.h>
#interface FlipTopPopToRoot : UIStoryboardSegue
#end
and
// FlipTopPop.m
#import "FlipTopPopToRoot.h"
#implementation FlipTopPopToRoot
- (void) perform {
UIViewController *src = (UIViewController *) self.sourceViewController;
[UIView transitionWithView:src.navigationController.view duration:0.5
options:UIViewAnimationOptionTransitionFlipFromBottom
animations:^{
[src.navigationController popToViewController:[src.navigationController.viewControllers objectAtIndex:0] animated:NO];;
}
completion:NULL];
}
#end
If you want to pop up just one level change use this custom segue:
// PopSegue.h
#import <UIKit/UIKit.h>
#interface PopSegue : UIStoryboardSegue
#end
and
// PopSegue.m
#import "PopSegue.h"
#implementation PopSegue
- (void) perform {
UIViewController *src = (UIViewController *) self.sourceViewController;
[src.navigationController popViewControllerAnimated:YES];
}
#end
For anyone following this now, iOS 7 lets you animate both ways:
Set the segue to Push, then see code below for a push implementation.
https://github.com/Dzamir/OldStyleNavigationControllerAnimatedTransition
As the commenter Linus pointed out, the other solutions presented will create another instance of the UIViewController. I think this link here describe other alternatives to enabling reverse segue animations.
http://robsprogramknowledge.blogspot.com/2012/05/back-segues.html

Resizing UINavigationController?

I want my navigationcontroller to only take half of the screen. Is this possible? In IB, when I drag, it forces me to fill up my entire screen, I can't resize it. If it's not possible, is there an alternative?
Thanks.
You can but you can only do it within iOS 5 because when I tried to do any type of direct view manipulation within a UINavigation controller than pushed or popped another view controller. The navigation controller would not display them.
Here's what you need to do.
-(void)viewDidLoad {
UINavigationController * navController = [[UINavigationController alloc] initWithRootViewController:[[UIViewController alloc] initWithNibName:nil bundle:nil]];
//Used to recieve callbacks (like shouldAutorotateToInterfaceOrientation:)
[self addChildViewController:navController];
//View manipulation
navController.view.frame = CGRectInset(navController.view.frame, 20, 20);
[self.view addSubview:navController.view];
//Calls all the standard methods (viewDidLoad,viewDidUnload,etc.)
[navController didMoveToParentViewController:self];
}
Also if your a registered developer there is a really good video on view controller containers, that might be helpful to you, from last years WWDC.
Based off what you have written I am assuming you have a #property in your header file. Something like:
#property (nonatomic, strong) IBOutlet UINavigationController *navController;
Then have this IBOutlet connected to your navigationController in your xib file. In that same xib file have a view that is connected to the file owner. Set this view's dimensions in interface builder so in your case to fill only half the screen. Then you will add the navController as this view's subview. If you also have a viewController already added in the xib file in the navController then in viewDidLoad do:
-(void)viewDidLoad {
[self addChildViewController:self.navController];
[self.view addSubview:self.navController.view];
[self.navController didMoveToParentViewController:self];
}
Otherwise if you are programmatically setting the rootviewcontroller do:
-(void)viewDidLoad {
self.navController = [[UINavigationController alloc] initWithRootViewController:yourViewController];
[self addChildViewController:self.navController];
[self.view addSubview:self.navController.view];
[self.navController didMoveToParentViewController:self];
}
You have to add self.navController as a childViewController so that it will take on the dimensions of the view it is being added into. Adding it as a subview is not enough to make the view resize. Hope this helps!

Orientation problem

i have created and ipad application.
i started off with window based application and added 2 view controllers(loginviewcontroller , detailviewcontroler ) . both have their own XIB'S. the added the loginviewcontroller object in in the appdelegate applicationdidfinishlaunch method , i wrote code to move back and forth between 2 views. Here is the code.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
Login *mylogin = [[Loginviewcontroller alloc] init];
[window addSubview:mylogin.view];
//detailview *tv=[[detailviewcontroller alloc] init];
// [window addSubview:tv.view];
[self.window makeKeyAndVisible];
return YES;
}
The problem is that the willrotatetointerfaceorientation method runs only from the loginviewcontroller class even if i am in the detailviewcontroller.
if i add the tickets view to the appdelegate then it runs the willrotatetointerfaceorientation method from detailviewcontroller , so in summary it runs the willrotatetointerfaceorientation method from only the object which was added to appdelegate.
how do i make the 2 view controllers run their respective willrotatetointerfaceorientation methods?
UIViewController has the method
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
override this method in your both viewcontroller classes(i.e. (loginviewcontroller , detailviewcontroler )
write this code
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
//write your code which you want to during rotation
return Yes;
}
The rotation messages are being sent only to the controller of topmost view. You should forward them manually in appropriate methods.