Let's say I have a viewcontroller "ViewBViewController". In that viewcontroller I create an instance of the "ViewAViewController" and use the addSubView: method to display this ViewAViewController. It then processes a bunch of information, and is now done. I want it to automatically get removed as a subview when it's done.
I was looking at the removeFromSuperview method, but can't seem to call that from within the viewcontroller whose view I'm trying to remove (my first instinct was [self.view removeFromSuperview], but that gets rid of the entire view, not just the subview I'm after).
The only way I can think of is setting up a delegate protocol, and have View B take care of the unloading of View A on behalf of View A as its delegate. However this approach seems a bit overkill. Am I missing an easier solution?
Thanks in advance!
UIViewController does not respond to removeFromSuperview, because a UIViewController is not a UIView but a UIViewController. No surprises there. You can call removeSuperview on any view, such as the view associated to a view controller (here self):
[self.view removeFromSuperview];
or if you just want to remove one subview:
[mySubview removeFromSuperview];
or if your subview is a member of self (i.e. declared in the interface say):
[self.mySubview removeFromSuperview];
Have you tried: setHidden: YES ?
There are two basically correct solutions here:
Use a navigation controller. You can hide the navigation bar if you don't want it to be part of your interface. Then you can dispose of the top view controller and its view from either view controller by calling
// argument can be YES or NO, as you like
[self.navigationController popViewControllerAnimated:YES];
Use a delegate call that tells the parent view controller to do something like
-(void)removeViewA {
// remove the view from the view hierarchy
[self.viewAController.view removeFromSuperview];
// dispose of the view controller so it doesn't leak.
self.viewAController = nil;
}
It's important to make sure that you don't leak the child view controller and its view.
Either of these approaches works, but using a navigation controller seems more idiomatic to me.
Related
I have a problem. I am working on an app which is tab bar based app. In this app, we call [self.view addSubview:newVC.view] when we want to navigate to a new view. newVC is the view controller of the new view that we want to display. Also we use [self.view removeFromSuperview] when we want to go back to previous view.
So in other words, there is no navigation controller. Now problem is that I want to update the previous view. Since we are using [self.view removeFromSuperview], viewDidAppear of the previous view is not get called and so we have no way of refreshing that view.
I know the approach that we used has flaw but since its a large scale app and changing it to implement navigation controller with take lot of time so I need you to please help me find the solution of this problem. How can I call the viewDidLoad or viewDidAppear or the previous view on calling [self.view removeFromSuperview] from its subview?
Yes, as Sarah said you should hold a reference to previous controller in "stack".
And when "poping" controller from stack, call appropriate method on previous controller.
Certainly you should not call viewDidLoad (it is not called when you pop controller from navigation stack of real UINavigationController).
You can call viewWillAppear or viewDidAppear, but better use your own method, like viewRevealed (you can also call it from viewWillAppear or viewDidAppear). It is useful to make
base class where implement all this functionality and derive all you controller from the base class. It may look like:
- (void) pushViewController:(BaseViewController *)baseController{
[self.view addSubview:baseController.view];
baseController.parentController = self;
}
- (void) pop{
[self.view removeFromSuperview];
[self.parentController viewRevealed];
}
viewDidLoad method call only when when you jump into a controller through pushViewController method. If you call removeFromSupreView, it will call viewWillAppear method. Here if you want to navigate from one view to another view over tabbar you must use UINavigationController in Mainwindow.xib and connect its viewController with App delegate.
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.
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.
I think I'm missing something fundamental and so I want to ask the community for some help. I'm building an app based around a basic iPhone Utility Application. My MainView and FlipsideView share some elements so I have created separate ViewControllers and nib files for those pieces. In order to do this I have done the following:
1. Created a viewcontroller called searchDateViewController which is the file's owner of searchDateView.xib
2. searchDateView.xib is basically a UIView with a UILabel inside, the view is wired up correctly
3. Inside both MainViewController.m and FlipsideViewController.m I add a subview as folllows:
- (void)loadView{
[super loadView];
searchDateViewController = [[SearchDateViewController alloc] initWithNibName:#"SearchDateView" bundle:nil];
[[searchDateViewController view] setFrame:[searchDateView frame]];
[[self view] addSubview:[searchDateViewController view]];
...
}
Everything displays and works just fine. Basically depending on actions that happen in each of the main and flipside views the UILabel of the nib is changed. However, I wanted to do something slightly different if the searchDateViewController is loaded from the MainView or the FlipsideView. However, I can't seem to figure out which ViewController is adding the searchDateViewController subview.
In searchDateViewController I tried:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"superview %#", self.view.superview);
NSLog(#"parentviewcontroller %#", self.parentViewController);
}
In both cases I get nil.
So my question is - can I find out which ViewController is adding searchDateViewController a a subview? If so how? Or if my logic here is completely messed up, how should I be doing this?
Thanks!
viewDidLoad is invoked when the view controller has loaded its view. In your case, that happends in this line:
[[searchDateViewController view] setFrame:[searchDateView frame]];
At that moment, you haven't yet called addSubview: so it is no wonder the view's superview is nil.
To solve your problem, you should define a property inside SearchDateViewController to distinguish between the different cases. This property would then be set accordingly by the parent controller that creates the SearchDateViewController instance.
Generally, I do not think it is a good idea to use a UIViewController subclass as a controller for a view that is used as a subview of one or several fullscreen views rather than be used as a fullscreen view itself. Much of UIViewController's logic works on the assumption that it is used to manage a fullscreen view. For instance, with your design, I think it's possible that SearchDateViewController will modify the view's frame when the device orientation changes etc. Since you don't need all this functionality for a non-fullscreen subview, I suggest you subclass your SearchDateViewController directly from NSObject.
ViewController and views are completely separate.
In most cases, when you add a subview to a parent view you don't add its controller to the parent's viewController. The exception to this rule is the navigation controller which adds the controller instead of the view to maintain a hierarchy of view controllers.
Your SearchDate viewController can't find a parent controller because you never assigned one and the system does not do it automatically. You can just assign a parent controller when you evoke the view from another controller.
searchDateViewController.parentController=self;
I have a UIViewController (named VC) that inherits from UITableViewDelegate and UIScrollViewDelegate. The previous UIViewController loads VC like this:
[self.view addSubview:VC.view];
which means viewWillAppear doesn't fire. I can add that method just after the above line:
[VC viewWillAppear];
but then it will fire before cellForRowAtIndexPath, which results in an empty tableview reference when I try to scroll.
I would like to scroll to a particular row in the table on load of VC. But because I don't know when the tableview's cellForRowAtIndexPath has completed (lack of viewWillAppear), I don't have any place to put the scroll code. I already keep a reference to the tableview and can use it for scrolling. But where can I place the scroll code?
Do you mean that your UIViewController conforms to the UITableViewDelegate and UIScrollViewDelegate protocols rather than 'inherits'?
You could invoke [VC viewWillAppear:] directly from your parent view controller. If the view handled by VC is always a sub-view of another view controller's view then do you really need a full UIViewController instance to back your UITableView? You could just provide a delegate and datasource implementation for the table view.
You could then instantiate your delegate and datasource either in the NIB that defines the table view (by adding an object in Interface Builder) or in the viewDidLoad method of the view controller that is currently the parent to VC.
Surely when the new view controller's view is added and becomes visible, it will call the viewDidAppear rather than the viewWillAppear. I'd stick the scroll code in there.
If that still doesn't work then try a delay in your init method. Something like this:
[self performSelector:#selector(myScrollingFunction) withObject:nil afterDelay:2.0];
You could play around with the delay time to get something that suits, although your view controller's view may not always appear after the same amount of time.