I am trying to make a sidebar menu but i have a little problem.
I explain :
I created an UIViewController that i called sideMenuViewController
In my viewController Class (the initial view controller), in the header file, i import my class SideMenuViewController and i wrote :
-(IBAction)openSideMenu:(id)sender;
#property(nonatomic, retain) SideMenuViewController *sideMenu;
The openSideMenu action is associated to the menu button.
I implemented this method like this :
- (IBAction)openSideMenu:(id)sender {
CGRect destination = self.view.frame;
if(destination.origin.x > 0){
destination.origin.x = 0;
}else{
destination.origin.x += SideMenuX;
}
[UIView animateWithDuration:0.4 animations:^{
self.view.frame = destination;
}completion:^(BOOL finished) {
if(finished){
}
}];
}
SideMenuX is a macro : #define SideMenuX 154.4
My viewDidLoad method looks like this :
- (void)viewDidLoad
{
[super viewDidLoad];
_sideMenu = [[SideMenuViewController alloc] init];
[self.view sendSubviewToBack:_sideMenu.view];
// Do any additional setup after loading the view, typically from a nib.
}
The problem is that when i click on the menu button, i get a black screen and not my side menu view.
Thank you in advance !
Two problems:
You are not adding the sideMenu at all. Try adding it to the parent view (self.view.superview), which in your case most likely will be the UIWindow: [self.view.superview insertSubview:_sideMenu.view belowSubview:self.view]; If you are using a navigation controller, use self.navigationController.view instead self.view.
Not sure if you initialized the view with a NIB or the Storyboard (see below if you didn't).
Here is a working example. I created the left view controller inside the storyboard like this:
Throw a View Controller component on the storyboard.
Select the controller on the left column, and go to the Identity Inspector on the right column (alt+cmd+3):
Set the Class to SideMenuViewController
Set the Storyboard ID to SideMenuViewController
Instantiate the controller inside viewDidLoad with
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
self.sideMenu = (SideMenuViewController*)[storyboard instantiateViewControllerWithIdentifier:#"SideMenuViewController"];
then insert it as child of the superview.
(Answering the comment below)
This line is the problem:
[self.view.superview addSubview:_sideMenu.view];
In a NIB based project the superview is UIWindow, but in a Storyboard project, the self.view.superview of a UIViewController is nil. You can solve this, for example, adding a UINavigationViewController. Follow these steps:
Throw in a "Navigation Controller"
Delete the view controller it points to.
Press Ctrl and drag the pointer from the UINavigationController to your view controller, and select "root view controller" on the dialog that appears.
Drag the arrow pointing to your view controller to the UINavigationController (the one that marks the initial view controller, not the one that comes from UINavigationController).
Then change your code to
_sideMenu = [[SideMenuViewController alloc] initWithNibName:#"SideMenuViewController" bundle:nil];
[self.navigationController.view.superview insertSubview:_sideMenu.view belowSubview:self.navigationController.view];
To hide the navigation bar of the UINavigationController, select it in the Storyboard and click Hidden in the Attributes Inspector (alt+cmd+4).
All you're seeing is black because you don't have the side menu view added. Try this:
- (void)viewDidLoad {
[super viewDidLoad];
_sideMenu = [[SideMenuViewController alloc] init];
[self.view addSubview:_sideMenu.view];
[self.view sendSubviewToBack:_sideMenu.view];
}
Related
I have an application that has an initial view controller that allows the user to log in. After the users logs in I'm trying to change the view to a custom tab bar controller that is of class type TabViewController. The problem is that when I switch to the tab bar controller, the screen is black and the bottom tab bar is gray and empty.
Here is some relevant code:
in ViewController.m (initial log in view)
- (IBAction)logInButtonClicked:(UIButton *)sender
{
TabViewController *tabView = [[TabViewController alloc] initWithSession:session];
[self presentViewController:tabView animated:YES completion:nil];
}
in TabViewController.m (class assigned to the tab bar controller)
-(id) initWithSession: (Session*) s
{
self = [super init];
if (self)
{
session = s;
}
return self;
}
Note that when I do the default initialization like so:
TabViewController *tabView = [[TabViewController alloc] init];
I get the same result.
How can I make my tab view controller look like it does in my storyboard on initialization?
Storyboard:
What the tab view controller looks like in the simulator:
I'm not sure this is the best way but it's exact what I did in my last app and it works fine.
Try making the tab bar view controller the root/initial view controller of your app.
According to Apple's developer class reference:
When deploying a tab bar interface, you must install this view as the root of your window. Unlike other view controllers, a tab bar interface should never be installed as a child of another view controller.
After doing this, set up a modal segue in the storyboard from the tab bar view controller to the login view controller, name it "segueLogin" and call it manually in viewDidAppear method of your tab bar view controller class.
if(!userHasLogin){
[self performSegueWithIdentifier:#"segueLogin" sender:self];
}
its really easy,
i will try to solve your problem in two step.
step 1-- select your TabViewController in storyboard and give it a identifier(below the custome class of TabViewController)
step 2--
- (IBAction)logInButtonClicked:(UIButton *)sender
{
UIStoryboard *storyBoard=[UIStoryboard storyboardWithName:#"Your_Story_Board_Name" bundle:nil];
TabViewController *tabView = [storyBoard instantiateViewControllerWithIdentifier:#"TabViewController_Identifier_From_Storyboard"];
[self presentViewController:tabView animated:YES completion:nil];
}
You should create your's TabViewController with UIStoryboard's - (id)instantiateViewControllerWithIdentifier:(NSString *)identifier
In yours case creating with [[TabViewController alloc] init] is wrong, you doesn't create all tabs programmatically.
I've just created a simple subclass of UINavigationController.
#import "TTNavigationController.h"
#implementation TTNavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage *logo = [UIImage imageNamed:#"logo"];
UIImageView *logoView = [[UIImageView alloc] initWithImage:logo];
self.navigationItem.titleView = logoView;
}
But when I run the project the navigation bar has not the titleView with the logo.
Otherwise if I try to move those 3 lines of code in viewDidLoad of my view controller works fine. Why?
You can't use self.navigationItem because the navigation controllers own navigationItem is never used. It has this property because it is a type of view controller but it isn't actually displayed because the viewControllers (or, more specifically, the topViewControllers) navigationItem is displayed.
So, change the view controller navigationItem (either directly, or when it is pushed).
I'm working on my first app. Here's what I want to accomplish:
There will be a menu with several different options. For simplicity, assume this is comprised of UIButtons with IBAction outlets and the functionality exists to pull up the menu at any time.
Each menu button, when pressed, should display a different navigation controller's content. If the user brings up the menu and makes a different selection, the navigation controller in which he is currently operating should not be affected; the newly selected navigation chain is displayed on top of the old, and through the menu, the user can go back to the view where he left off on the previous navigation chain at any time.
visual illustration (click for higher resolution):
Please note that there are 3 different navigation controllers/chains. The root view controller (which is also the menu in this simplified version) is not part of any of them. It will not suffice to instantiate one of the navigation chains anew when it has been previously instantiated, and here's why: if the user was on screen 3 of option 2 and then selects option 1 from the menu and then selects option 2 (again) from the menu, he should be looking at screen 3 of option 2--right where he left off; the view controller he was viewing when he previously left the navigation chain should be brought back to the top.
I can make a button instantiate and present a view controller from the storyboard if there is NOT a navigation controller:
- (IBAction)buttonPressed:(id)sender {
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"View 2"];
[self presentViewController:controller animated:YES completion:nil];
}
However, I can't figure out how to make those two methods work with a navigation controller involved. Moreover, I'm not sure those two methods are the right choice, because I won't always want to instantiate a new view controller: when a menu button is pressed, a check should be performed to see if the view (navigation?) controller with the corresponding identifier has already been instantiated. If so, it should simply be made the top view controller.
In summary, here are my questions:
1) How should I instantiate and display a view controller that is embedded in a navigation controller, preferably using a storyboard ID? Do you use the storyboard ID of the navigation controller or of the view controller?
2) How should I check whether an instance already exists? Again, should I check for an extant navigation controller or for a view controller, and what's the best method to do so?
3) If the selected navigation chain has already been instantiated and is in the stack of view controllers somewhere, what is the best method for bringing it to the top?
Thank you!!
side note -- it would be nice to know how to paste code snippets with indentation and color formatting preserved :)
As Rob has suggested, a tab bar controller would make a good organising principle for your design.
Add a UITabBarController to your storyboard, give it a storyboard iD. Assign each of your three sets of viewControllers ( with their respective navController) to a tab item in the tabBarController.
UITabBarController
|--> UINavigationController --> VC1 ---> VC2 -->
|--> UINavigationController --> VC1 ---> VC2 -->
|--> UINavigationController --> VC1 ---> VC2 -->
In you app delegate make a strong property to hold your tab bar controller's pointer. As the tab bar controller keeps pointers to all of it's tab items, this will take care of state for each of your sets of viewControllers. You won't have to keep separate pointers for any of them, and you can get references to them via the tabBarController's viewControllers property.
#property (strong, nonatomic) UITabBarController* tabVC;
Initialise it on startup
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIStoryboard storyBoard =
[UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
self.tabVC = [storyBoard instantiateViewControllerWithIdentifier:#"tabVC"];
//hide the tab bar
for (UINavigationController* navController in self.tabVC.viewControllers)
[navController.viewControllers[0] setHidesBottomBarWhenPushed:YES];
return YES;
}
An alternative way to hide the tab bar is to check the "Hides bottom bar on push" box in the Attributes Inspector for each of the (initial) viewControllers. You don't have to do this for subsequent viewControllers, just the first one that will be seen in that tab item.
Then when you need to navigate to one of your navController groups…
- (IBAction)openTab:(UIButton*)sender {
AppDelegate* appDelegate =
(AppDelegate*)[[UIApplication sharedApplication] delegate];
if ([sender.titleLabel.text isEqualToString: #"Option 1"]) {
appDelegate.tabVC.selectedIndex = 0;
}else if ([sender.titleLabel.text isEqualToString: #"Option 2"]){
appDelegate.tabVC.selectedIndex = 1;
}else if ([sender.titleLabel.text isEqualToString: #"Option 3"]){
appDelegate.tabVC.selectedIndex = 2;
}
[self presentViewController:appDelegate.tabVC
animated:YES completion:nil];
}
(this example uses presentViewController, your app design may navigate this in other ways…)
update
If you want to do this without a tab bar controller, you can instantiate an array holding pointers to each of your nav controllers instead:
UINavigationController* ncA =
[storyboard instantiateViewControllerWithIdentifier:#"NCA"];
UINavigationController* ncB =
[storyboard instantiateViewControllerWithIdentifier:#"NCB"];
UINavigationController* ncC =
[storyboard instantiateViewControllerWithIdentifier:#"NCC"];
self.ncArray = #[ncA,ncB,ncC];
Which has the benefit of not having a tab bar to hide…
Then your selection looks like…
- (IBAction)openNav:(UIButton*)sender {
AppDelegate* appDelegate =
(AppDelegate*)[[UIApplication sharedApplication] delegate];
int idx = 0;
if ([sender.titleLabel.text isEqualToString: #"option 1"]) {
idx = 0;
}else if ([sender.titleLabel.text isEqualToString: #"option 2"]){
idx = 1;
}else if ([sender.titleLabel.text isEqualToString: #"option 3"]){
idx = 2;
}
[self presentViewController:appDelegate.ncArray[idx]
animated:YES completion:nil];
}
1 / You can instantiate a viewController in your viewDidLoad method of your main viewController, so it will be instantiate 1 time only.
Now if you want display your controller, you would better push it :
- (IBAction)buttonPressed:(id)sender {
// Declare your controller in your .h file and do :
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"View 2"];
// Note you can move this line in the viewDidLoad method to be called only 1 time
// Then do not use :
// [self presentViewController:controller animated:YES completion:nil];
// Better to use :
[self.navigationController pushViewController:controller animated:YES];
}
2 / I'm not sure, but if you want to check if an instance already exist just check :
if (controller) {
// Some stuff here
} // I think this checks if controller is initiated.
3 / I know it's not a good advice but I would tell you to not worry about checking if your controller already exist, because I think it's easier to access your viewController by using the 2 lines again :
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"View 2"];
[self.navigationController pushViewController:controller animated:YES];
4 / I'm not sure if colors can be used here because of a specific style sheets.
I'm not sure to really have the good answer to your question but I hope this will help you.
Reusable button bars? gets me part of the way here, but now I'm having trouble with the "back button" requirements.
I need a layout solution that:
will work on iOS 5.0 and 6.0
has a custom view at the top with several buttons; this view should be reusable across every screen (scene), as opposed to duplicating the buttons manually in Interface Builder for each scene.
has a custom "back" button in that top custom view. With the design I have to implement, I cannot just use the default navigation bar
works well with the UINavigationController; when the user taps the "back" button, the main view controller (with the button bar) should stay, but the child view controller representing the actual scene content should go back to the previous scene.
The problem currently is that the "back" button won't change the child controller--it changes the parent controller, returning to the previous scene before the scene with the button bars. I've tried this several different ways. I'm not sure if I'm not doing it right, or if it can't be done.
One possibility is to implement my own "back" functionality, keeping a stack of child view controllers and manually changing them when the user taps "back." This is awkward, however, and poor design compared to using UINavigationController.
Perhaps I am going the wrong way with this. I can't accept duplicating the button bar across every single scene in Interface Builder... but perhaps I should create it programmatically, and then I can easily call that code from each and every scene. Then I would have "normal" view controllers, and using UINavigationController would be easier. But before I go that route and completely scrap what I have so far, I wanted to see if there was another way.
Here's an overview of some parts of my solution:
I created a ButtonBarController, laying out the Storyboard with a UIView for the buttons I wanted, and a UIView for the content pane. I also layered a button with the app logo (to go to the app's main screen) on top of a back button.
Then I created a controller for each of those other screens. In those subscreens/child view controllers, I would first add a UIView at the correct size to fit in my content pane, and then would add all the other controls I wanted. I had all of those child view controllers inherit from another controller, which took care of a few common tasks--such as procuring a reference to the button bar controller, and code to help resize the views for 3.5" versus 4" screens.
I created a changeToControllerWithIndex method; I call this when the app loads, when the user clicks one of the buttons in the main button bar to change scenes, or when anything happens in a scene requiring another scene change. I overload this method to provide two additional pieces of information: providing an NSDictionary with any extra information the child view controller needs, and to tell it whether this is a top-level scene, or whether we need a back button.
(Note: it's important to set the Storyboard ID for those child view controllers in the Identity Inspector. I kept accidentally setting the Title in the Attribute Inspector instead)
- (void)changeToControllerWithIndex:(NSInteger)index {
[self changeToControllerWithIndex:index withPayload:nil isRootView:YES];
}
// This is the method that will change the active view controller and the view that is shown
- (void)changeToControllerWithIndex:(NSInteger)index withPayload:(id)payload isRootView:(BOOL)isRootView
{
if (YES) {
self.index = index;
// The code below will properly remove the the child view controller that is
// currently being shown to the user and insert the new child view controller.
UIViewController *vc = [self setupViewControllerForIndex:index withPayload:payload];
if (isRootView) {
NSLog(#"putting navigation controller in");
childNavigationController = [[UINavigationController alloc] initWithRootViewController:vc];
[childNavigationController setNavigationBarHidden:YES];
[self addChildViewController:childNavigationController];
[childNavigationController didMoveToParentViewController:self];
if (self.currentViewController){
[self.currentViewController willMoveToParentViewController:nil];
[self transitionFromViewController:self.currentViewController toViewController:childNavigationController duration:0 options:UIViewAnimationOptionTransitionNone animations:^{
[self.currentViewController.view removeFromSuperview];
} completion:^(BOOL finished) {
[self.currentViewController removeFromParentViewController];
self.currentViewController = childNavigationController;
}];
} else {
[self.currentView addSubview:childNavigationController.view];
self.currentViewController = childNavigationController;
}
[self.currentView addSubview:childNavigationController.view];
//We are at the root of the navigation path, so no back button for us
[homeButton setHidden:NO];
[backButton setHidden:YES];
} else {
//Not a root view -- we're in navigation and want a back button
[childNavigationController pushViewController:vc animated:NO];
[homeButton setHidden:YES];
[backButton setHidden:NO];
}
}
}
Then I have an overloaded method to set up each individual view controller... some require a little more preparation than others.
- (UIViewController *)setupViewControllerForIndex:(NSInteger)index {
return [self setupViewControllerForIndex:index withPayload:nil];
}
// This is where you instantiate each child controller and setup anything you need on them, like delegates and public properties.
- (UIViewController *)setupViewControllerForIndex:(NSInteger)index withPayload:(id)payload {
UIViewController *vc = nil;
if (index == CONTROLLER_HOME){
vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Home"];
} else if (index == CONTROLLER_CATEGORIES){
SAVECategoryViewController *child = [self.storyboard instantiateViewControllerWithIdentifier:#"Categories"];
if (payload) {
child.currentCategory = [(NSNumber *) [(NSDictionary *)payload objectForKey:ATTRIBUTE_CAT_ID] integerValue];
} else {
child.currentCategory = CATEGORY_ALL;
}
vc = child;
} //etc for all the other controllers...
payload = nil;
return vc;
}
I mentioned my difficulty with managing the "back" navigation. The above code ensures the navigation controllers maintain a proper "back" history, starting fresh whenever we use one of the button bar buttons to change screens. When we do use buttons inside a child controller to navigate from scene to scene, this is how we can go back:
- (IBAction)backButtonPressed:(id)sender {
[childNavigationController popViewControllerAnimated:YES];
if ([[childNavigationController viewControllers] count] <= 1) {
//Root view
[homeButton setHidden:NO];
[backButton setHidden:YES];
}
}
I think you need to implement at least one custom container view controller - the root view controller. That would be the one to host the custom button bar. Below the button bar you would add a UINavigationController the manage your other VCs. Look at this for starters:
#implementation RootVC
//...
- (void)viewDidLoad
{
self.navVC = [[UINavigationController alloc] initWithRootViewController:someOtherVC];
self.navVC.navigationBarHidden = YES;
self.navVC.view.frame = ...;
[self addChildViewController:self.navVC];
[self.view addSubview:self.navVC.view];
[self.navVC didMoveToParentViewController:self];
}
- (void)backButtonTouched:(UIButton *)button
{
[self.navVC popViewControllerAnimated:YES];
}
I have a simple app that has two view controllers. Both of them have a UINavigationBar at the top, as a header. The second UIViewController is displayed as a modal view, when the user clicks on a button on the first one.
When my app first launches, the initial view doesn't completely cover the main UIView and seems "pushed" to the top (see image below).
After I click on the "instructions" button, which displays another view with presentModalViewController:animated:, and dismiss the modal ViewController, everything is displayed correctly.
Anybody knows what I might be doing wrong?
I have nothing in viewWillAppear, and this is my viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
if (!self.model) {
self.model = [[FRRSushiRiceModel alloc] init];
[[self.header.items objectAtIndex:0] setTitle: #"Perfect Sushi Rice: Ingredients"];
}
}
and my application:didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Create and add the main controller (ingredients)
self.ingredientsController = [[FRRIngredientsViewController alloc] init];
[window addSubview:self.ingredientsController.view];
[window makeKeyAndVisible];
return YES;
}
This small project reproduces this behavior:
Test Case
Did you untick the "Wants Full Screen" setting in IB, either for the UINavigationController or UIViewController?
I found the error, guys.
Basically I was trusting the system to correctly set the frame of my views to match the usable portion of the screen. This works when you add it to some controller of controllers (such as UINavigationController), or add it via IB.
If you add your controllers programmatically, you need to set the view's frame explicitly. A good default is:
[[UIScreen mainScreen] applicationFrame]
represents the part of the screen available to applications: the whole screen minus the status bar.