New to iPhone development, but I've been given a big project as a first go and I'm a bit stuck.
Basically the app will start with a settings screen, then you click a button to go to a dashboard with multiple option buttons. Each button will lead to a different Navigation View with tables.
The way I've approached this is to start with a UIViewController with a button, which I've got wired up but when you hit the button and I do:
[self.view removeFromSuperview];
UIViewController *newView = [[UIViewController alloc] initWithNibName:#"Dashboard" bundle:nil];
[self.view addSubview:newView.view];
the second view isn't loading. I just get a blank screen. Do I need to make a reference in the first controller to the second?
Also, am I approaching this in the right way? As long as I removeFromSuperview will I be able to load the navigation controllers on the press of a button?
Sorry if this isn't too clear, I've been through books and lots of websites but don't seem to be able to get my head around this.
Thanks
There is nothing here with the new view, rather the problem is with current view. You have removed the self.view from super view.
[self.view removeFromSuperview];
So anything added to self.view will not be shown, as self.view itself is removed.
When presenting child controller/view from a parent controller, you should consider using presentViewController. Eventually, use dismissViewControllerAnimated when you want child to disappear and parent to reappear.
In parent view controller:
ChildViewController * child = [[ChildViewController alloc] init];
[self presentViewController:child animation:YES completion:Nil];
In child view controller, ie. in some action handler:
-(IBAction)close:(id)sender
{
[self dismissViewControllerAnimated:YES completion:Nil];
}
IMHO you should also get in the habit of naming instance variables to what they are instantiated from. In your example you name the instance newView, when it should be something like newViewController. That way you make sure you don't mix up views with view controllers.
[self.view removeFromSuperview];
You've removed the view from the superview
[self.view addSubview:newView.view];
But you're adding the new view to the same view that you have just removed from the superview. It's not displaying anywhere.
Your third line adds newView as a subview of self.view, but you just removed self.view from it's superview.
I'd suggest reading more about view controllers. You'll want to have one view controller per "screen", so one for your settings screen, one for your dashboard, one for each table, and so on. Then, manage which one is visible by pushing and popping these view controllers from the nav controller's stack.
This removes self.view, which will most likely destroy the object since there will be no other references to it:
[self.view removeFromSuperview];
Here you are creating an UIViewController, and adding it's view to self.view, which is probably not what you want:
UIViewController *newView = [[UIViewController alloc] initWithNibName:#"Dashboard" bundle:nil];
[self.view addSubview:newView.view];
Look into UINavigationController so that you can easily swap screens in and out with some built in animations. Here's a bit more about them. Here's a tutorial.
The UIViewController's view should not be removed from or added to a view hierarchy outside the control of the view controller. While you might be able to get that manipulation to work now it won't in the future.
Read up on view controllers here.
The basic idea is that you present the view controller then it will take care of manipulating the view hierarchy for you.
So a better approach to get started would be to do something like this;
[viewController1 presentModalViewController:viewController2 animated:YES];
This line of code will present viewController2 with the default modal animation (slide in from the bottom). If you'd like a different animation you can change the modalPresentationStyle to one of the constants in the UIModalPresentationStyle enum on viewController1 (note thats a viewController1, not viewController2).
If you want something more like the Clock app look into the tab bar controller. If you want something more like the Mail app look into the navigation controller.
Related
In my iPhone app I am trying to have the allusion of having a single static navigation bar, so the title and buttons don't ever swipe across when switching views.
I can't think a way of doing it (simply at least), so do you have any suggestions? I need to have a static title and buttons up in the nav bar space (even if I don't use the UINavigationBar, but make something custom) so that when I do something such as push a view controller, when it swipes across my nav bar doesn't move and the buttons change function for the new view.
Edit
Ok, I have thought of a possible method. Each of my views have a secondary view in which gold the view contents, except the nav bar objects. Can I override the pop and push methods to just animate this subview on and off screen?
Just do a push as normal but set it to not animate.
i.e.
[self.navigationController pushViewController:newViewController animated:NO];
Then when you want to go back...
[self.navigationController popViewControllerAnimated:NO];
Then you can have two navigation bars but it will look like it's the same bar as it isn't animating.
EDIT TO ALSO ANIMATE IN THE VIEW AND KEEP THE NAV BAR
I'm not sure of the whole flow of the app but if you want to keep the nav bar and swipe the new UI in then you could create a scroll view (with paging) and put the views of each VC on different frames of the scroll view or something.
Why do you want to keep the nav bar still anyway? There is nothing wrong with animating the nav bar and keeping the same buttons etc on it.
Having said that, if you are using different VCs then the nav bar should change anyway to show the details (i.e. title) of the VC you are currently looking at.
ANOTHER MORE RADICAL APPROACH
OK, thinking laterally now :D
How about, you use the not animated push and pop (as above) but instead of just displaying the UI you can animate it in from the relevant side. (A singleton or a VC subclass which you then subclass for your UI could do this for you across all view controllers).
The next problem is that it will look like the UI has gone instantly blank before animating in the new UI so you need to animate out the old UI. This means both UIs (the old and the new) have to be on the screen at the same time.
You can get round this by converting the entire view of the old UI into an image (not hard to do will find a link) and then passing this image into the new VC. The new VC will then instantly display this image and animate it out of the screen at the same time as animating its own UI onto the screen.
Really not as hard to do as it sounds. Especially if you subclass UIViewController ad give it a function animateUI and a property oldUIImage and direction. Then you can override viewWillAppear in this class to do the animation for you. Then the only thing you have to do is give each VC an image and a direction when you push/pop to it.
This is just giving the illusion of what you're after and means you can still keep a fairly simple object model and flow of the app.
...or something.
Just a riff on #Fogmeister's good idea...
In the presenting view controller, get self.view's image by implementing the suggestion here. Then, when it's time to present...
UIImage *image = [self makeImage]; // what it was called in the other post, consider a better name
MyViewController *newVC = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
newVC.presentationImage = image;
[self.navigationController pushViewController:newVC animated:NO];
In MyViewController, give it a UIImage property called presentationImage, then ...
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
UIImageView *imageView = [[UIImageView alloc] initWithImage:self.presentationImage];
imageView.frame = self.view.bounds;
imageView.tag = 128;
[self.view addSubview:imageView];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIImageView *imageView = (UIImageView *)[self.view viewWithTag:128];
[UIView animateWithDuration:0.5 animations:^{
imageView.frame = CGRectOffset(imageView.frame, -self.frame.size.width, 0);
}];
}
FYI - I didn't test or even compile this. Just liked the idea and decided to stretch my fingers.
To do this in a "clean" way, you'd need to abandon UINavigationController and write a custom container controller that does not push new navigation items onto it's navigation bar when pushing a new view controller (or allows you to push the navigation item in a non-animated fashion while animating the push of the view controllers views).
However doing this will take some time. If you decide to do this, i recommend the WWDC Session on UIViewController containment.
Another alternative that springs to my mind is to (by subclassing or method swizzling) alter the behaviour of UINavigationController to push the navigation items non-animated while animating the viewcontroller-push.
I have in no way tested this, but overriding push (and pop respectively) in a subclass like this might work:
- (void)pushViewController:(UIViewController *)vc animated:(BOOL)animated {
[super pushViewController:vc animated:animated];
[self.navigationBar setItems:self.navigationBar.items animated:NO];
}
If this method doesn't work, it might be worth inspection what animations are going on inside the nav bar directly after the call to pushViewController:animated:. Maybe you can cancel some animations to go to the final state directly.
So I'm building an app and I am running through a few ViewControllers that don't need to know about each other, so I start off switching through views like so...
// remove the previous view in order to load in the new view
NSArray *sViews = [self.view subviews];
[sViews makeObjectsPerformSelector:#selector(removeFromSuperview)];
// create the new view, in this case the user wishes to
BaseViewController *baseVC = [[BaseViewController alloc] initWithNibName:#"BaseViewController" bundle:[NSBundle mainBundle]];
self.baseViewController = baseVC;
[baseVC release];
// add the newly created view to the screen
[self.view insertSubview:baseViewController.view atIndex:0];
The above is the view controller that I want the navigation controller to reside in. So within the .m of this view controller I created a UINavigationController as a member variable and named it navController. I then tried implementing a UINavigationController using the code below.
UIViewController *control = [[BusinessDisplayViewController alloc] initWithNibName:#"BusinessDisplayViewController" bundle: nil];
navController = [[UINavigationController alloc] initWithRootViewController:control];
[self presentModalViewController:navController animated:YES];
The problem I'm running into is two fold. First, when the BusinessDisplayViewController (below) is loaded there is a 20 pixel or so gap between my mapView and tableView that isn't there when I was loading it using insertSubview: not sure why that would be. Second, once I'm in BusinessDisplayViewController.m I'm not sure how to access the navigationController created in BaseViewController. Could someone explain why my view would be effected, how I could access the navigationController or if I'm even going about this the right way.
UINavigationController is designed for use in one of three possible contexts on iPhone:
As the app's root view controller, with its view added as a subview of the app's window.
As one of the viewControllers of a UITabBarController.
Presented as a full screen view controller via presentModalViewController:animated:.
In your case, the UINavigationController has configured itself for presentation as a subview of the window. This is why you see the 20 pixel gap at the top. Since the window object underlaps the status bar, UINavigationController offsets the position of its navigation bar by 20 pixels (or more, if you're on a phone call).
The standard way to use UINavigationController as your root view controller is to construct it as a property of your app delegate in application:didFinishLaunchingWithOptions:, and add its view as a subview of the window. Then within any view controller you push onto your navigation stack, you can access the navigation controller object using self.navigationController.
Usually, you want your UINavigationController to be at the root level, Is there a specific reason for having your app setup this way? To answer your question though, you can access the variable by setting a property for it, then using the dot notation: baseVC.navController.
For the 20 pixel space problem, post your BaseViewController view related code. It is probably a bounds vs frame issue.
I am trying to remove two viewcontrollers (that have been added on top of each other) with one method. I have made the views in interfacebuilder. they all have their own .h and .m files to go with it.
Scenario I am in:
I have a main menu which has the view2 header file imported.
In a method I add the second view on top of the superview like so
view2ViewController * view2 = [[view2ViewController alloc] initWithNibName:#"view2ViewController" bundle:nil];
[self.view addSubview:view2.view];
then in view 2 I have added the view 3 header file so i can add view 3 as a subview ontop of view2. i have another method which is connected again to interface builder to a UIButton so upon button press a method gets called in view2 which adds view 3 on top in exactly the same way like so:
view3ViewController * view3 = [[view3ViewController alloc] initWithNibName:#"view3ViewController" bundle:nil];
[self.view addSubview:view3.view];
What im trying to solve:
I have a button in view 3 which should remove view 3.... and then it should also remove view 2 aswell so the main screen is visible.
How can this be achieved?
What I have so far:
[self.view removeFromSuperview];
This however only removes View 3... but leaves view 2 in place.
What needs to be modified so that i can remove view 2 as well??
Any help is appreciated.
I would normally do this as adding view2 and view3 as subviews of the main view. Then when the button actions are triggered, the adding and removing of subviews will be executed by the main view's view controller.
For a quick hack, I think you can try this in your button handler.
[self.view removeFromSuperview];
[self.view.superview removeFromSuperview];
Though I'm not sure if you should be doing it. :P
Is this what you need?
[[[self.view subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
I don't know what you're trying to do exactly, but I get the impression that pushing a new view controller is what you want. If you have a UINavigationController in your app, you'd just have to do a
[navigationController pushViewController:view2 aimated:YES]
To go back to the main menu when the button is pressed, you should define a delegate protocol that looks something like this::
#protocol View3ViewControllerDelegate
- (void)view3ControllerBackToMainMenuButtonPressed:(View3ViewController*)controller;
#end
This protocol is then implemented by the class that actually pushes the other view controllers. In the implementation, you'd pop all view controllers you don't want to be displayed anymore. To do this, you could use
[navigationController popToViewController:myMainMenuViewController animated:YES]
or if your main menu view controller is actually the root view controller:
[navigationController [navigationController popToRootViewControllerAnimated:YES]
That way only one class is responsible for pushing and popping all view controllers and handling that "Back to Main Menu" button. Using a custom protocol as described above is the recommended way to handle the popping of pushed view controllers in a scenario like this.
Hope that helps!
I have a problem with transitioning between views in different ViewControllers.
Here is the situation:
My App is a TabBarApplication done with IB which contains a UIViewController for each Tab. The UIViewController (PlayerTabViewController) of the first tab contains another UIViewController (PlayerCreationViewController) to manage a view that will be added as subview.
I was able to add the subview using
[self.view addSubview:playerCreationViewController.view];
In the PlayerTabViewController.
The problem is that from the subview I have to return to the parent view and reload it because it contains a tableview that must be refreshed.
Using [self.view removeFromSuperview]; in the PlayerCreationViewController I can switch back to the parent view, but I'm not able to reload the tableview or do other actions.
I tried to implement the -(void)willRemoveSubview:(UIView *)subview method in PlayerTabViewController but it seems the function is never called.
Do you have an Idea of what am I doing wrong?
you are using wrong method to go on next view. just use navigation view controller to switch from one view to another view.
create a object of view
PlayerCreationViewController *playerViewController = [[PlayerCreationViewController alloc] initWithNibName:#"PlayerCreationViewController" bundle:nil];
[self.navigationController pushViewController:playerViewController animated:YES];
[playerViewController release];
I am sure this is an easy question, but one that has escaped me for some time now.
Say I have a UIViewController, either defined as a root in an XIB or on a stack. At some point in my code I want to replace it with another view controller. Just flat out replace it. How would I do that?
I have tried defining the controller and assigning, but not sure what actually makes it push on the screen with the absence of a navigation controller.
I think when you say that you want to replace the view controller, what you actually mean is that you want to replace the view. Bear in mind that view controllers aren't visible, but every view controller maps to a view, which can become visible by getting added as a subview of a visible view.
Your solution of replacing self.view with the new view controller's view may work in your particular case, but it's probably not the "correct" answer to your question. There are going to be cases where this solution won't work for you.
Let's say you have a simple view based application with no navigation controller and no tab bar controller. In your app delegate you construct an instance of YourFirstViewController, and you call [window addSubview:yourFirstController];. Your view hierarchy now consists of a UIWindow with a single subview -- the view for YourFirstViewController.
Now let's say the user presses a button on that view, which is handled by an IBAction defined in YourFirstViewController. You want to respond by "replacing" YourFirstViewController's view with a view associated with YourSecondViewController. I put "replacing" in quotes because we more commonly present a view by pushing its view controller onto a navigation stack, or calling presentModalViewController:animated: to present the view modally, but let's assume that you've rejected those options for some reason, and you actually do want to manually replace YourFirstViewController's view with YourSecondViewController's view.
This is a simple matter of manipulating the view hierarchy. You want to remove YourFirstViewController's view from its superview (the UIWindow in this case), and you want to add YourSecondViewController's view as a subview to replace it. Your action would therefore look something like this:
- (IBAction)replaceButtonClicked {
UIView *mySuperview = self.view.superview;
YourSecondViewController *secondController = [[YourSecondViewController alloc] init];
[mySuperview addSubview:secondController.view];
[self.view removeFromSuperview];
[secondController release];
}
When we use a methods like -pushViewController:animated: or -presentModalViewController, the receiving controller manipulates the view hierarchy for us. This may make it seem like we're looking at view controllers on the screen, but we're not. We're just looking at a big hierarchy of nested views going all the way up to a UIWindow at the top.
You can present a new view controller modally:
[self presentModalViewController:aViewController animated:YES];
This won't outright replace the current VC, but it will display a new view over the current view.