In a view based app, I display a view and once the user is finished with that view, Done is clicked and the view is removed. However, that does not dealloc the view, which is what I want to do. What is the proper way to dealloc this type of view?
Currently, I'm nil'ing out the second view before it is called. That seems to work and the second view is reinitialized. However, isn't it more appropriate for the second view to destroy itself (nil itself after removeFromSuperview)?
In first view:
//show next view
aView = nil;
if(aView == nil)
{
aView = [[AView alloc] initWithNibName:#"aView" bundle:nil];
}
[self.view addSubview: aView.view];
Click Done in aView
[self.view removeFromSuperview];
The method removeFromSuperview will automatically release "aView.view", so you shouldn't release aView in the first view controller. I think you've declared AView *aView in head file, but you don't need to. You may declare the aView as a local variable like this:
// go to second view
SecViewController *sec = [[SecViewController alloc] initWithNibName:#"SecView" bundle:nil];
[self.view addSubview:sec.view];
// go back
[self.view removeFromSuperview];
Immediately after
[self.view addSubview: aView.view];
You should add:
[aView release];
Your subview has been retained by your view controller's view so can be release.
If you previously set your viewController to first responder status (like to respond to motion events) the responder chain will retain the controller. You must resign the first responder to destroy the controller completely.
[self resignFirstResponder];
[self.view removeFromSuperview];
both of the above answers are correct theory for how you should memory manage in objective c.
as per the dev documentation:
http://developer.apple.com/iPhone/library/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/removeFromSuperview
callling removeFromSuperview will actually call release FOR you, so you are okay, I think.
Related
In my main UIViewController I am adding a homescreen view controller as subviews:
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:vc];
controller.navigationBarHidden = YES;
controller.view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
[self addChildViewController:controller];
[self.view insertSubview:controller.view atIndex:0];
[controller didMoveToParentViewController:self];
The issue is that viewDidAppear and viewWillAppear is only called once, just like viewDidLoad. Why is this? How do I make this work?
Basically inside vc I am not getting viewDidAppear nor viewWillAppear.
I also just tried adding the UIViewController without the navigation controller and it still doesn't work:
vc.view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
[self addChildViewController:vc];
[self.view insertSubview:vc.view atIndex:0];
[vc didMoveToParentViewController:self];
In my case, viewDidAppear was not called, because i have made unwanted mistake in viewWillAppear method.
-(void)viewWillAppear:(BOOL)animated {
[super viewdidAppear:animated]; // this prevented my viewDidAppear method to be called
}
The only way I can reproduce the problem of child controllers not receiving appearance methods is if the container view controller does the following (which I'm sure you're not doing):
- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
{
return NO;
}
Perhaps you can try explicitly enabling this:
- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
{
return YES;
}
But my child view controllers definitely are getting the viewWillAppear calls (either if I explicitly automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers or if I omit this altogether.
Update:
Ok, looking at the comments under your original question, it appears that the issue is that the child controller (B) in question is, itself, a container view controller (which is perfectly acceptable) presenting another child controller (C). And this controller B's own child controller C is being removed and you're wondering why you're not getting viewWillAppear or viewDidAppear for the container controller B. Container controllers do not get these appearance methods when their children are removed (or, more accurately, since containers should remove children, not children removing themselves, when the container removes a child, it does not receive the appearance methods).
If I've misunderstood the situation, let me know.
#Rob answer in Swift 4 (which helped me on my case which I was adding a childViewController to a UITabBarController)
override var shouldAutomaticallyForwardAppearanceMethods: Bool {
return true
}
Another case where this will not be called at launch time (yet may be called on when you return to the view) will be is if you have subclassed UINavigationController and your subclass overrides
-(void)viewDidAppear:(BOOL)animated
but fails to call [super viewDidAppear:animated];
Had a same problem
My container view controller did retain a child view controller via a property, but did not add a child view controller to its childViewControllers array.
My solution was to add this line of code in the container view controller
[self addChildViewController: childViewController];
After that UIKit started forwarding appearance methods to my child view controller just as expected
I also changed the property attribute from strong to weak just for beauty
When updating my code to 13.0, I lost my viewDidAppear calls.
In Objective-c, my solution was to add the following override all to the parent master view controller.
This allowed the ViewDidAppear call to get called once again...as it did in previous IOS (12 and earlier) version.
#implementation MasterViewController
//....some methods
(BOOL) shouldAutomaticallyForwardAppearanceMethods {
return YES;
}
// ...some methods
#end
My problem was that I was changing the tab in UITabBarController (selectedIndex = x) and then messing with the child view controllers in that tab. The problem is that it needs to be done the other way round: first mess with the child view controllers in other tab and then change the tab by setting the selectedIndex. After this change methods viewWillAppear/viewDidAppear begun to be called correctly.
Presenting view controllers using presentModalViewController or segues or pushViewController should fix it.
Alternatively, if for some reason you want to present your views without the built-in methods, in your own code you should be calling these methods manually. Something like this:
[self addChildViewController:controller];
BOOL animated = NO;
[controller viewWillAppear:animated];
[self.view insertSubview:controller.view atIndex:0];
[controller viewDidAppear:animated];
[controller didMoveToParentViewController:self];
Within my mainViewController I'm adding a view from another ViewController. I'm removing it's view when finished. What is the proper way to manage this and where do I release the view controller?
mainVc.m
-(void)showView {
helpPage *elementController = [[helpPage alloc] init];
[self.view addSubview:elementController.view];
}
helpPage.m
-(void)removeView {
[self.view removeFromSuperview];
}
Since you called alloc init on the controller in that instance of mainVc, that instance of mainVc owns releasing the controller.
When it calls addSubView, it will retain the view (and add to view hierarchy) and when it's removed from the superview, it will be released. At that point, when it's release, the controller that created the view is still retaining it as well. When both have released the view, it will go away.
Here's a related SO post:
Does UIView's addSubview really retain the view?
I think you are looking for something like this
mainVc.m
-(void)showView {
helpPage *elementController = [[helpPage alloc] init];
[self.view addSubview:elementController.view];
[elementController release];
}
helpPage.m
-(void)removeView {
[self.view removeFromSuperview];
}
Granted, I haven't seen the rest of your code, so that might not be what you are looking for. However, in terms of memory allocation, any time you alloc something, you must release it later. In the case of views and view controllers, once you add that view or view controller, you can release the copy you "alloc'ed."
i like to create a second starting screen in my app.
My Idea is to use the default.png and load an UIView with an fullscreen UIImageView inside.
In viewDidLoad i thought about placing a sleep option and after this load the real app screen.
But also when my function is called in viewDidLoad, nothing happens.
Seems my superview is empty...
Here is a piece of code:
if (self._pdfview == nil)
{
pdfview *videc = [[pdfview alloc]
initWithNibName:#"pdfview" bundle:nil];
self._pdfview = videc;
[pdfview release];
}
// get the view that's currently showing
UIView *currentView = self.view;
// get the the underlying UIWindow, or the view containing the current view
UIView *theWindow = [currentView superview];
theWindow is empty after this line so that might be the reason why the other view is not loaded.
So my question, how do i create a second starting screen ?
Or three starting screens, like in games when i like to mention another company.
If I understand correctly, your point is that when your function above is executed from viewDidLoad of some controller, theWindow is nil, so your new view (startscreen) is not added to it.
A few observations:
if theWindow is nil, then self.view is the topmost UIView; you can try and replace it, or simply add your view to it:
UIView *currentView = self.view;
// get the the underlying UIWindow, or the view containing the current view
UIView *theWindow = [currentView superview];
UIView *newView = _pdfview.view;
if (theWindow) {
[currentView removeFromSuperview];
[theWindow addSubview:newView];
} else {
self.view = newView; //-- or: [self.view addSubview:newView];
}
if you want to get the UIWindow of your app (which seems what you are trying to do), you can do:
[UIApplication sharedApplication].keyWindow;
and from there you can either set the rootViewController (from iOS 4.0)
[UIApplication sharedApplication].keyWindow.rootViewController = ...;
or add newView as a subview to it:
[[UIApplication sharedApplication].keyWindow addSubview:newView];
in the second case, you should possibly remove all subviews previously added to the UIWindow. (Iterate on keyWindow.subviews and call removeFromSuperview).
OLD ANSWER:
I think that you should try and add your pdfview as a subview to the current view:
[currentView addSubview:videc];
or to what you call theWindow:
[theWindow addSubview:pvidec];
and, please, move the release statement after the `addSubview, otherwise the view will be deallocated immediately.
I need to add this to my dismiss button :-
[self dismissModalViewControllerAnimated:YES];
[self release];
else
[self.view removeFromSuperview];
I thought
if( self.navigationController.modalViewController ) {
would work be it nevers true
A couple of things:
1) You shouldn't ever release yourself in an object. If you're presenting a modal view controller, you should perform the release there since the view controller will now be retained by the view controller's .modalViewController property:
(In the parent):
UIViewController *someViewController = [[UIViewController alloc] init];
[self presentModalViewController:someViewController animated:YES];
[someViewController release];
2) The parent will store its child modal view controller in .modalViewController. The child will have its .parentViewController property set in this case. If the view has been added as a subview, its .superview property will be set. These are not mutually exclusive, however, so be careful. Generally speaking, UIViewControllers are intended to host full-screen views, and if you're adding the view as a subview, you should ask yourself if the view should just be a UIView subclass, and move the logic into the parent view controller.
That said, I suppose you could check your case (assuming you don't present modal view controller and add as a subview simultaneously):
if (self.parentViewController) {
[self dismissModalViewControllerAnimated:YES];
} else if (self.view.superview) {
[self.view removeFromSuperview]
}
In the latter superview case, the view controller will still be hanging around, so you'd need to let the other view controller know via delegate method or something to release you. In the first case, if you have released the presented view controller already as I described above, it will be released automatically when the parent view controller sets its .modalViewController property to nil.
Normally for a "dismiss" button I would call a method in the controller that presented the modal controller (use a delegate), not try to dismiss the modal view controller from within itself. I don't quite get what youre trying to do though, but that [self release] looks bad. I don't think you ever want to release self like that.
Try this in you modal viewcontroller:
- (IBAction)close:(id)sender {
[self.parentViewController dismissModalViewControllerAnimated:YES];
}
Then just connect the button's action to that method.
How can I make it so when a tab is selected, the current one is unloaded, and the next one is loaded so only one loaded at a time? Or should I not even do this? I know how to do it with a normal UIViewController as the root VC, but not sure with a UITabBarController. Also, is there a way to animate the transition from one tab to the next? Any help? Thanks!!
EDIT: ... If I unload the view controllers, then their icons on the tab bar are gone... maybe I'll just unload their views..
I can answer both questions in one...
You just need a class that acts as the UITabBarController delegate, then implement a method like so:
// Animate tab selections so they fade in and fade out
-(void)tabBarController:(UITabBarController*)tbc didSelectViewController:(UIViewController*)newSelection
{
[UIView beginAnimations:#"TabFadeIn" context:nil];
[UIView setAnimationDuration:0.6];
for( UIViewController* vc in tbc.viewControllers )
vc.view.alpha = (vc==newSelection) ? 1 : 0;
[UIView commitAnimations];
}
Now my code simply makes the tab bars fade in and out, but you could also do work here to unload non-used tabs. Sometimes that is a good idea if some of the tabs will be using a ton of memory.
You cant really manage the UITabBarController unfortunaly so you cant do lazy loading. You can by managining your own TabBar but you said u knew that already,
to manage your own tab bar though all you gotta do is setup a UITabBar with its TabBarItems in a ViewController, then implement the TabBar Delegate protocol, mainly the – tabBar:didSelectItem: method which is called whenever the tabbarItem selection is changed, then based on the item id you can load your new ViewController and release any others
so: Edit: this code goes in your UIViewController
-(void)addTabBar{
NSMutableArray* items=[[NSMutableArray alloc] init];
UITabBarItem *eventsItem= [[UITabBarItem alloc] initWithTitle:#"Events" image:nil tag:0];
UITabBarItem *albumItems=[[UITabBarItem alloc] initWithTitle:#"Album" image:nil tag:1]; //the tag is how you tell what was clicked
[items addObject:homeItem];
[items addObject:albumItems];
//MyTabBar is of type UITabBar
myTabBar=[[UITabBar alloc] initWithFrame:CGRectMake(0,411,320,49)];
[myTabBar setItems:items];
myTabBar.delegate=self; //you gotta implement the UITabBar delegate protocol
[myTabBar setSelectedItem:eventItem]; //set the selected item
[homeItem release];
[eventsItem release];
[albumItems release];
[items release];
[self.view addSubview:myTabBar]
}
then the protocol method would look something like below
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
if(item.tag == 0 )
{
//load the ViewController that pertains to this item and release others
}
...etc
}
Lazy loading is not an UITabBarController task. Instead, it is responsability of your viewControllers associated with your Tab.
To release the UIView, associated with each UIViewControllers, every time you change the TabBarItem, you must implement the following method in each UIViewController subclass, associated with your UITabBarController.viewControllers property:
-(void)viewDidDisappear {
[self.view removeFromSuperview];
self.view = nil;
}
Obviously, this will remove the self.view associated with your UIViewController. However, if your code is smart enough, this will remove all the related objects.
For example, suppose that your loadView method is as follow:
-(void)loadView {
UIView *contentVew = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = contentView;
…
...
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
…
…
[contentView addSubview:aLabel];
[aLabel release];
…
[contentView release];
}
This means that every object inside the contentView and their memory responsabilities are demanded to the contentView, that is released and attached to the self.view property.
In this scenario, removing the self.view (that's the reference to the contentView) resulting in a domino-style releasing of every object, that's your goal.
Best regards
Not sure why you'd want to do this, the current tab will get unloaded anyway if there's a memory issue involved. That's what -viewWillAppear, -viewDidUnload, etc. are for.
UITabBarController does lazy load all of its view controllers. When a tab is switched out, then it's view is subject to being deallocated in a memory tight situation. It is then recreated when it is chosen the second time. Furthermore, most of your memory hits are in your views and not the view controllers. Hence, don't worry about the memory hit from the view controller. The view is the proze.
If you are running on v3 of the OS, then you can use the -viewDidUnload method to ensure the maximal amount of memory reduction.
Andrew
I'm currently using this to unload inactive view controllers in the tab bar (based on Kendall's answer)
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController: (UIViewController *)viewController {
// reload all inactive view controllers in the tab bar
for (UIViewController *vc in tabBarController.viewControllers) {
if(vc != viewController)
[vc didReceiveMemoryWarning];
}
}