I read somewhere that in a programmatically created view in a UIViewController, not using Interface Builder, -viewDidLoad and -viewDidUnload should not be used. Is this right? Why? Where would I release subviews that I have retaining properties of? Or should I just not use properties for them?
EDIT: Read my comments on Rob Napier's answer.
Create your subviews in -viewDidLoad. If you need ivars for them then only assign their values. The reference is hold by adding the views as subviews to you main view.
Then when your view is unloaded you should set your ivars to nil, because the object have been released since your view was removed and released.
So in your header
#interface MyViewController : UIViewController {
IBOutlet UIView *someSubview; // assigned
}
#property (nonatomic, assign) IBOutlet UIView someSubview;
#end
And in your implementation
#implementation MyViewController
//... some important stuff
- (void)viewDidLoad;
{
[super viewDidLoad];
someSubview = [[UIView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:someSubview]; // retains someSubview
[someSubview release]; // we don't hold it
}
- (void)viewDidUnload;
{
[super viewDidUnload];
someSubview = nil; // set the pointer to nil because someSubview has been released
}
//... more important stuff
#end
If you wish you can also not release someSubview in -viewDidLoad, but then you have to release it in -viewDidUnload AND -dealloc since (if I remember right) -viewDidUnload isn't called before -dealloc. But this isn't necessary if you don't retain someSubview.
the strange thing here is that an UIViewController not loaded from a NIB file is not notified about its view unloading (and so its viewDidUnload method is not called) unless you offer a base implementation of the loadView method, such as:
- (void)loadView {
self.view = [[[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds] autorelease];
[self.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
}
- (void)viewDidLoad {
[super viewDidLoad];
// create views...
}
- (void)viewDidUnload {
// destroy views...
[super viewDidUnload];
}
this only happens to base UIViewController, an UITableViewController for example don't need to be fixed with this workaroud.
So Robs is right.
Related
I have 2 view, named viewcontroller1,viewcontroller2. every viewcontroller has load about 3 pictures(load it in XIB) , when i try to use [self.navigationcontroller pushviewcontroller], to push view1 to view2, the memory cannt release.(i want to konw it cannt release viewcontrller1 or cannt release uiimageview)any error for my code ??
viewcontroller1 code just like this:
viewcontroller1.h
#interface ViewController : UIViewController
#property(strong,nonatomic)IBOutlet UIImageView *mainImageView;
#property(strong,nonatomic)IBOutlet UIImageView *topwallImageView;
#property(strong,nonatomic)IBOutlet UIImageView *buttonImageView;
viewcontroller1.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
self.mainImageView = nil;
self.topwallImageView = nil;
self.buttonImageView = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
}
-(void)dealloc
{
[mainImageView release];
[topwallImageView release];
[buttonImageView release];
[super dealloc];
}
-(IBAction)main2ViewController:(id)sender
{
Main2ViewController *main2ViewController =[[Main2ViewController alloc]initWithNibName:#"Main2ViewController" bundle:nil];
[self.navigationController pushViewController:main2ViewController animated:NO];
[main2ViewController release];
}
You have to study more detailed about Object Communication and Memory management in iOS.
Also please try to use ARC. Automatic reference counting (ARC) was introduced in the iOS 5 sdk to free Objective-C programmers from having to handle memory management by making memory management the job of the compiler.
You can convert your project to ARC enabled using this link
You can't release viewcontroller1 and its IBOutlet properties while viewcontroller2 is visible or after pushed viewcontroller2. Because you pushed viewcontroller2 from viewcontroller1.viewcontroller1 is the container/parent of viewcontroller2. viewcontroller2 will not exist with out viewcontroller1
About the IBOutlet imageview images, you can set it to nil .Eg: yourImageview.image = nil; However it will not release the IBOoutlet Imageview memory.
IBOutlet imageview will get released when a release method called to viewcontroller1 because you released it properly in viewcontroller1 dealloc method :)
About your Main2ViewController method:You handled memory properly in your code
When you call the pushViewController , it will retain the controller which is pushed by default. note that your are not the owner. It will release automatically when the controller is popped out.However you have to release the allocated Main2ViewController. So your code is fine :)
I am trying to add a static fixed image to a UITableViewController, but when I do the standard [self.view addSubview:imageView]; the image is placed on the tableview and moves with the scrolling.
Is there any way to do this so that the image stays fixed?
I know one method would be to create a UIViewController, then add the UIImageView and a UITableView, but unfortunately, I am using a custom UITableViewController (just a library found on gihub to do what I needed), so my controller must be a UITableViewController.
Is there any way to do this? I've been going at this for a while with no luck.
Cheers,
Brett
There is no problem using UIViewController idea. You just keep 2 view controllers: 1) UIViewController, which has the UIImageView inside, and subview the view of 2) the UITableViewController. If necessary, make the UITableViewController a strong reference of the UIViewController.
I have done something similar all the time.
Yes, there are few ways. You could create your view hierarchy programmatically at
viewDidLoad or use a NIB file. Make sure that you correctly link the delegates and view properties.
If a nib file is specified via the initWithNibName:bundle: method (which is declared by the superclass UIViewController), UITableViewController loads the table view archived in the nib file. Otherwise, it creates an unconfigured UITableView object with the correct dimensions and autoresize mask. You can access this view through the tableView property.
If a nib file containing the table view is loaded, the data source and delegate become those objects defined in the nib file (if any). If no nib file is specified or if the nib file defines no data source or delegate, UITableViewController sets the data source and the delegate of the table view to self.
As https://stackoverflow.com/a/6961973/127493 say, UITableViewControllers can be replaced by simple UIViewControllers.
In fact, the trick is to add an UITableView to you UIViewController, make it delegate and etc..., and add it to your UIViewController.view.
So you will be able to add some "sister" views to your controller main view.
In my case, I am adding a an Image ( actually button with image) and when user touches on image, it will disappear and tableview will be shown.
so i am disabling scroll first then enable it back
find code below
// in viewDidLoad
[self.view addSubview:imgview];
tbl.scrollEnabled = NO;
// in -(IBAction)btnClicked:(id)sender
[imgview removeFromSuperview];
tbl.scrollEnabled = YES;
Thats working for me.
Do NOT use UITableViewController at all (I never use it and as I've heard nearly any developer uses it). It is a nightmare when you want to customize design with it.
Create your own subclass of UIViewController (MYTableViewController), add UITableView *tableView instance #property and #synthetize it:
#interface MYTableViewController : UIViewController <UITableViewDelegate,UITableViewDataSource> {
UITableView *tableView;
}
#property (nonatomic, retain) IBOutlet UITableView *tableView;
#end
Then in implementation add it to the view (using XIB or viewDidLoad method):
#implementation MYTableViewController
#synthesize tableView;
// If not XIB used:
-(void)viewDidLoad{
[super viewDidLoad];
CGRect frame = self.view.bounds;
self.tableView = [[[UITableView alloc] initWithFrame:frame style:UITableViewStylePlain] autorelease];
tableView.dataSource = self;
tableView.delegate = self;
[self.view addSubview:tableView];
// And here you van add your image:
[self.view addSubview:imageView];
}
// Do not forget to release it and clear delegate and datasourcce when view uloads:
#pragma mark - Memory management:
-(void)dealloc{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
self.tableView = nil;
[super dealloc];
}
- (void)didReceiveMemoryWarning {
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
self.tableView = nil;
[super didReceiveMemoryWarning];
}
-(void)viewDidUnload{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
self.tableView = nil;
[super viewDidUnload];
}
#end
My current understanding is that superviews have retains each of their subviews. For a subclass of a UIView, do I need to remove all of my subviews from their superview as part of dealloc? I'm currently just releasing my IBOutlets, removing observed notifications, and clearing up any pesky ivars.
Or is removing and releasing subviews part of a UIView's [super dealloc]?
As a part of the view's dealloc, the subviews are removed automatically. So you don't need to remove them. However, if your view has retained any of its subviews [aside from the automatic retain], you should release them during dealloc.
So for example suppose your view contained the following code:
[header file]
UILabel *myLabel;
#property (nonatomic, retain) UILabel *myLabel;
[implementation file]
someLabel = [[UILabel alloc]initWithFrame: someFrame];
[self addSubview: someLabel];
self.myLabel = someLabel;
[someLabel release]; // now retained twice, once by the property and once as a subview
someButton = [[UIButton alloc]initWithFrame: someOtherFrame];
[self addSubview: someButton];
[someButton release]; // retained once as it is a subview
then your dealloc method would look like this:
- (void) dealloc {
[myLabel release];
[super dealloc];
}
UIView retains its subviews, so it's responsible for releasing them. Your subclass doesn't own those views (unless you explicitly retain them), so you don't need to worry about releasing them.
So it sounds like you're doing the right thing.
I have a tab based application I am working on.
I have a view controller named DetailsView.m, with an accompanying nib file called DetailsView.xib. This has a couple of UILabels in, which are linked using IBOutlet to DetailsView.m view controller. When I load this view controller/view using the tab bar controller, it works fine and the UILabels are populated dynamically.
Now I want to load this entire view inside a UIScrollView instead so I can fit more content in.
So I created another view controller called DetailsHolder.m with a nib file called DetailsHolder.xib, and assigned this to the tab bar controller.
I wrote this code below to load the first view (DetailsView) into the UIScrollView in the second view (DetailsHolder). I wrote it in the viewDidLoad method of DetailsHolder:
DetailsView* detailsView = [[DetailsView alloc] initWithNibName:#"DetailsView" bundle: nil];
CGRect rect = detailsView.view.frame;
CGSize size = rect.size;
[scrollView addSubview: detailsView.view];
scrollView.contentSize = CGSizeMake(320, size.height);
This correctly loads the sub view into the UIScrollView, however, the labels inside DetailsView no longer do anything. When I put an NSLog inside viewDidLoad of DetailsView - it never logs anything. It's as if I've loaded the nib ok, but its no longer associated with the view controller anymore. What am I missing here? I'm a bit of a newbie in obj C/iOS (but have many years Actionscript/Javascript knowledge.
Thanks in advance,
Rich
Edit: Contents of DetailsView as requested:
DetailsView.h:
#import <UIKit/UIKit.h>
#import "AppModel.h"
#interface DetailsView : UIViewController {
IBOutlet UITextView* textView;
IBOutlet UIImageView* imageView;
}
#end
DetailsView.m
#import "DetailsView.h"
#implementation DetailsView
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewWillAppear:(BOOL)animated
{
AppModel* model = [AppModel sharedInstance];
[model loadData];
int selectedLocation = [model getSelectedLocation];
NSArray *locations = [model getLocations];
NSArray *data = [locations objectAtIndex:selectedLocation];
textView.text = [data objectAtIndex: 0];
UIImage *theImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[data objectAtIndex: 4] ofType:#"jpg"]];
imageView.image = theImage;
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
Essentially all its doing is grabbing selectedLocation (int) from a singleton (which is my model), then grabbing an array from the model, and then trying to insert an image and some text into my view. In my nib file, i have a UIImageView and a UITextView, which I have linked to the two IBOutlets declared in DetailsView.h
When you use the view controller like this, many events will not occur in the view controller, e.g. viewWillAppear, viewWillDisappear and device rotation event. My best guess that you did some initialisation in some of those event methods. Posting your code for DetailsView would make it easier to find the problem.
I'm trying to work out the "best" way to use a UISegmentedControl for an iPhone application. I've read a few posts here on stackoverflow and seen a few people's ideas, but I can't quite sort out the best way to do this. The posts I'm referring to are:
Changing Views from UISegmentedControl
and
How do I use a UISegmentedControl to switch views?
It would seem that the options are:
Add each of the views in IB and lay them out on top of each other then show/hide them
Create each of the subviews separately in IB, then create a container in the main view to populate with the subview that you need
Set up one really tall or really wide UIView and animate it left/right or up/down depending on the selected segment
Use a UITabBarController to swap out the subviews - seems silly
For tables, reload the table and in cellForRowAtIndex and populate the table from different data sources or sections based on the segment option selected (not the case for my app)
So which approach is best for subview/non-table approaches? Which is the easiest to implement? Could you share some sample code to the approach?
Thanks!
I've come across this requirement as well in an iPad application.
The solution I came to was to create specialized view controllers for
each style of view to handle business logic relating to those views
(ie. relating to each segment), and programatically add/remove them as
subviews to a 'managing' controller in response to selected segment
index changes.
To do this, one has to create an additional UIViewController subclass that manages
UISegmentedControl changes, and adds/removes the subviews.
The code below does all this, also taking care of a few caveats/extras:
viewWillAppear/viewWillDisappear/etc, aren't called on the subviews
automatically, and need to be told via the 'managing' controller
viewWillAppear/viewWillDisappear/etc, aren't called on 'managing'
controller when it's within a navigation controller, hence the
navigation controller delegate
If you'd like to push onto a navigation stack from within a
segment's subview, you need to call back on to the 'managing' view
to do it, since the subview has been created outside of the
navigation hierarchy, and won't have a reference to the navigation
controller.
If used within a navigation controller scenario, the back button is
automatically set to the name of the segment.
Interface:
#interface SegmentManagingViewController : UIViewController <UINavigationControllerDelegate> {
UISegmentedControl * segmentedControl;
UIViewController * activeViewController;
NSArray * segmentedViewControllers;
}
#property (nonatomic, retain) IBOutlet UISegmentedControl * segmentedControl;
#property (nonatomic, retain) UIViewController * activeViewController;
#property (nonatomic, retain) NSArray * segmentedViewControllers;
#end
Implementation:
#interface SegmentManagingViewController ()
- (void)didChangeSegmentControl:(UISegmentedControl *)control;
#end
#implementation SegmentManagingViewController
#synthesize segmentedControl, activeViewController, segmentedViewControllers;
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController * controller1 = [[MyViewController1 alloc] initWithParentViewController:self];
UIViewController * controller2 = [[MyViewController2 alloc] initWithParentViewController:self];
UIViewController * controller3 = [[MyViewController3 alloc] initWithParentViewController:self];
self.segmentedViewControllers = [NSArray arrayWithObjects:controller1, controller2, controller3, nil];
[controller1 release];
[controller2 release];
[controller3 release];
self.navigationItem.titleView = self.segmentedControl =
[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"Seg 1", #"Seg 2", #"Seg 3", nil]];
self.segmentedControl.selectedSegmentIndex = 0;
self.segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
[self.segmentedControl addTarget:self action:#selector(didChangeSegmentControl:) forControlEvents:UIControlEventValueChanged];
[self didChangeSegmentControl:self.segmentedControl]; // kick everything off
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.activeViewController viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.activeViewController viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.activeViewController viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.activeViewController viewDidDisappear:animated];
}
#pragma mark -
#pragma mark UINavigationControllerDelegate control
// Required to ensure we call viewDidAppear/viewWillAppear on ourselves (and the active view controller)
// inside of a navigation stack, since viewDidAppear/willAppear insn't invoked automatically. Without this
// selected table views don't know when to de-highlight the selected row.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
[viewController viewDidAppear:animated];
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
[viewController viewWillAppear:animated];
}
#pragma mark -
#pragma mark Segment control
- (void)didChangeSegmentControl:(UISegmentedControl *)control {
if (self.activeViewController) {
[self.activeViewController viewWillDisappear:NO];
[self.activeViewController.view removeFromSuperview];
[self.activeViewController viewDidDisappear:NO];
}
self.activeViewController = [self.segmentedViewControllers objectAtIndex:control.selectedSegmentIndex];
[self.activeViewController viewWillAppear:NO];
[self.view addSubview:self.activeViewController.view];
[self.activeViewController viewDidAppear:NO];
NSString * segmentTitle = [control titleForSegmentAtIndex:control.selectedSegmentIndex];
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:segmentTitle style:UIBarButtonItemStylePlain target:nil action:nil];
}
#pragma mark -
#pragma mark Memory management
- (void)dealloc {
self.segmentedControl = nil;
self.segmentedViewControllers = nil;
self.activeViewController = nil;
[super dealloc];
}
#end
Hope this helps.
I'd go with the second option you mention, creating the subviews in IB and swapping them in and out of a main view. This would be a good opportunity to use UIViewController, unsubclassed: in your initial setup, create a controller using -initWithNibName:bundle: (where the first parameter is the name of the NIB containing the individual subview, and the second parameter is nil) and add its view as a subview of your main view as necessary. This will help keep your memory footprint low: the default behavior of a UIViewController when receiving a memory warning is to release its view if it has no superview. As long as you remove hidden views from the view hierarchy, you can keep the controllers in memory and not worry about releasing anything.
(edited in response to comment:)
You don't need to subclass UIViewController, but you do need separate XIBs for each view. You also don't need to add anything to the containing view in IB.
Instance variables, in the interface of whatever class is handling all this:
UIViewController *controllerOne;
UIViewController *controllerTwo;
UIViewController *currentController;
IBOutlet UIView *theContainerView;
In your setup (-applicationDidFinishLaunching: or whatever)
controllerOne = [[UIViewController alloc] initWithNibName:#"MyFirstView" bundle:nil];
controllerTwo = [[UIViewController alloc] initWithNibName:#"MySecondView" bundle:nil];
To switch to a controller:
- (void)switchToController:(UIViewController *)newCtl
{
if(newCtl == currentController)
return;
if([currentController isViewLoaded])
[currentController.view removeFromSuperview];
if(newCtl != nil)
[theContainerView addSubview:newCtl.view];
currentController = newCtl;
}
Then just call that with, e.g.,
[self switchToController:controllerOne];
Here's a great tutorial that explains this concept further: http://redartisan.com/2010/5/26/uisegmented-control-view-switching
and the github location to it: https://github.com/crafterm/SegmentedControlExample.git