I am creating my first tab controller app. I have 2 tabs with 2 UIViews in them. I did this mostly from Interface Builder all I did in Xcode was add 2 files firstControllerView and SecController view. I can see the tab controller is working went I run the app (I simply changed the background color on the 2 UIViews in the tabs to see the effect).
Now I want to add a label to the secondView and set its text programmatically from code. This is whats breaking for me! I am doing something wrong. In my SecondViewController.h it looks like this:
#interface SecondViewController : UIViewController {
IBOutlet UILabel *title;
}
#property (nonatomic,retain) UILabel *title;
#end
and the .m looks like this...
#import "SecondViewController.h"
#implementation SecondViewController
#synthesize title;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[title setText:#"Hello Nick"];
[super viewDidLoad];
}
- (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.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[title release];
[super dealloc];
}
#end
After this I went back to Interface Builder and dragged the outlet reference to the label. When I run the simulator it crashes.
What am I missing here? It must be something simple.
Forgot to create an outlet for a tabbarcontroller in the app delegate then connect that outlet to the tabbar controller in interface builder.
Related
I have a written a simple example where I have a Utility Application project with one UIScrollView on it. When I click the info button to flip the screen and return the UIScrollView now is unresponsive. Not only that but I purposely placed the scroller in the Interface Builder in the upper left corner and then programmatically set it in the center. when I come back from the flipside its shifted up to the top left corner and unresponsive. why?
This is my .h file:
#import "POCFlipsideViewController.h"
#interface POCMainViewController : UIViewController <POCFlipsideViewControllerDelegate, UIScrollViewDelegate>
#property (nonatomic,retain) IBOutlet UIScrollView *scroller;
#end
This is my .m file
#import "POCMainViewController.h"
#interface POCMainViewController ()
#end
#implementation POCMainViewController
#synthesize scroller =_scroller;
- (void)viewDidAppear:(BOOL)animated
{
[_scroller setContentSize:CGSizeMake(80.0f, 320.0f)];
[_scroller setFrame:CGRectMake(120, 131, 80, 214)];
[super viewDidAppear:animated];
}
- (void)viewDidLoad
{
_scroller.delegate = self;
_scroller.showsVerticalScrollIndicator = NO;
_scroller.decelerationRate = UIScrollViewDecelerationRateFast;
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Flipside View
- (void)flipsideViewControllerDidFinish:(POCFlipsideViewController *)controller
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showAlternate"]) {
[[segue destinationViewController] setDelegate:self];
}
}
#end
The rest is boiler plate straight from Xcode.
I have found that the culprit with UIScrollView is often Auto Layout. In Storyboard, highlight the view element under Main View Controller and then open the "Show the File Inspector" window in the right-hand column. Under "Interface Builder Document", deselect "Use Auto Layout" and see if that corrects your issue.
Update:
To clarify the context of the question; the question is not on how to avoid the problem, but to clarify what the piece of documentation means, as my experiment suggests that the views I was expecting to unload, based on the documentation, are not unloading. I would like to understand if this is a bug, or if I am misunderstanding the documentation. To solve the problem instead, I know that setting the images in viewWillAppear instead of viewDidLoad, and setting the images to nil in viewDidDisappear, is releasing the memory and the app doesn't crash. However, I would like to understand if the memory should have been released with the initial code, as the experiment is to simulate having such view controllers as outlets, and setting your UI images (backgrounds...) in Interface Builder, instead of setting them in code in viewWillAppear...
Original:
I am trying to understand some new aspect of iOS6, as documented in the View Controller Programming Guide:
On iOS 6 and Later, a View Controller Unloads Its Own Views When Desired
The default behavior for a view controller is to load its view hierarchy when the view property is first accessed and thereafter keep it in memory until the view controller is disposed of. The memory used by a view to draw itself onscreen is potentially quite large. However, the system automatically releases these expensive resources when the view is not attached to a window. The remaining memory used by most views is small enough that it is not worth it for the system to automatically purge and recreate the view hierarchy.
Let's say I am creating a simple app, with a rootViewController.
This rootViewController has a few child View Controllers, all declared as IBOutlets instead of being allocated in code.
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController1;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController2;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController3;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController4;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController6;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController7;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController8;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController9;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController10;
The rootViewController has a few buttons, pressing each of those does a simple presentModalViewController operation on each childViewController
-(IBAction)showChild1Action:(id)sender{
[self presentModalViewController:self.childViewController1 animated:true];
}
Each childViewController has a close button that dismisses the child view controller.
-(IBAction)closeAction:(id)sender{
[self dismissModalViewControllerAnimated:true];
}
My expectation from the documentation was that the child view controller view objects would be released from memory, as the child view controllers are dismissed.
However I purposely tested with large view objects, and running profile on such an app, the memory usage just keeps growing as each child controller gets presented, and the app eventually crashes after I present child controller #7 or so.
What is your understanding of what has changed in iOS6 in that aspect?
RootViewController.h
#import <UIKit/UIKit.h>
#import "ChildViewController.h"
#interface RootViewController : UIViewController
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController1;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController2;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController3;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController4;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController5;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController6;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController7;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController8;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController9;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController10;
-(IBAction)showChild1Action:(id)sender;
-(IBAction)showChild2Action:(id)sender;
-(IBAction)showChild3Action:(id)sender;
-(IBAction)showChild4Action:(id)sender;
-(IBAction)showChild5Action:(id)sender;
-(IBAction)showChild6Action:(id)sender;
-(IBAction)showChild7Action:(id)sender;
-(IBAction)showChild8Action:(id)sender;
-(IBAction)showChild9Action:(id)sender;
-(IBAction)showChild10Action:(id)sender;
#end
RootViewController.m
#import "RootViewController.h"
#interface RootViewController ()
#end
#implementation RootViewController
#synthesize level2ViewController1;
#synthesize level2ViewController2;
#synthesize level2ViewController3;
#synthesize level2ViewController4;
#synthesize level2ViewController5;
#synthesize level2ViewController6;
#synthesize level2ViewController7;
#synthesize level2ViewController8;
#synthesize level2ViewController9;
#synthesize level2ViewController10;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
-(IBAction)showChild1Action:(id)sender{
self.level2ViewController1.index=0;
[self presentModalViewController:self.level2ViewController1 animated:true];
}
-(IBAction)showChild2Action:(id)sender{
self.level2ViewController2.index=1;
[self presentModalViewController:self.level2ViewController2 animated:true];
}
-(IBAction)showChild3Action:(id)sender{
self.level2ViewController3.index=2;
[self presentModalViewController:self.level2ViewController3 animated:true];
}
-(IBAction)showChild4Action:(id)sender{
self.level2ViewController4.index=3;
[self presentModalViewController:self.level2ViewController4 animated:true];
}
-(IBAction)showChild5Action:(id)sender{
self.level2ViewController5.index=4;
[self presentModalViewController:self.level2ViewController5 animated:true];
}
-(IBAction)showChild6Action:(id)sender{
self.level2ViewController6.index=5;
[self presentModalViewController:self.level2ViewController6 animated:true];
}
-(IBAction)showChild7Action:(id)sender{
self.level2ViewController7.index=6;
[self presentModalViewController:self.level2ViewController7 animated:true];
}
-(IBAction)showChild8Action:(id)sender{
self.level2ViewController8.index=7;
[self presentModalViewController:self.level2ViewController8 animated:true];
}
-(IBAction)showChild9Action:(id)sender{
self.level2ViewController9.index=8;
[self presentModalViewController:self.level2ViewController9 animated:true];
}
-(IBAction)showChild10Action:(id)sender{
self.level2ViewController10.index=9;
[self presentModalViewController:self.level2ViewController10 animated:true];
}
#end
ChildViewController.h
#import <UIKit/UIKit.h>
#interface ChildViewController : UIViewController
#property(nonatomic,weak) IBOutlet UIImageView *image1;
#property NSInteger index;
-(IBAction)closeAction:(id)sender;
#end
ChildViewController.m
#import "ChildViewController.h"
#interface ChildViewController ()
#end
#implementation ChildViewController
#synthesize image1;
#synthesize index;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
if(self.index==0)
[self.image1 setImage:[UIImage imageNamed:#"IMG1.JPG"]];
if(self.index==1)
[self.image1 setImage:[UIImage imageNamed:#"IMG2.JPG"]];
if(self.index==2)
[self.image1 setImage:[UIImage imageNamed:#"IMG3.JPG"]];
if(self.index==3)
[self.image1 setImage:[UIImage imageNamed:#"IMG4.JPG"]];
if(self.index==4)
[self.image1 setImage:[UIImage imageNamed:#"IMG5.JPG"]];
if(self.index==5)
[self.image1 setImage:[UIImage imageNamed:#"IMG6.JPG"]];
if(self.index==6)
[self.image1 setImage:[UIImage imageNamed:#"IMG7.JPG"]];
if(self.index==7)
[self.image1 setImage:[UIImage imageNamed:#"IMG8.JPG"]];
if(self.index==8)
[self.image1 setImage:[UIImage imageNamed:#"IMG9.JPG"]];
if(self.index==9)
[self.image1 setImage:[UIImage imageNamed:#"IMG10.JPG"]];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
-(IBAction)closeAction:(id)sender{
[self dismissModalViewControllerAnimated:true];
}
When using Instruments I get these observations:
The memory allocation instrument is showing what I would expect to see: total allocation that goes up when child view controller is presented, goes down when child view is dismissed
However the activity indicator is telling a different story, with real memory increasing with every presentModalViewController, and never decreasing when dismissing them
The answer I eventually got back from Apple:
Your "large resources" happen to be images that you've loaded cached
via +imageNamed:. Because they are loaded cached, they are exempted
from the automatic cleanup. Generally only content generated via
-drawRect: or by Core Animation is automatically released here. Because your views continue to exist, they continue to hold a
reference to these cached images, and we can't purge them on memory
warning either
Looks like cached resource isn't part of the automatic cleanup mentioned in the doc, and those resources only get deallocated when they stop being referenced.
Sounds like you have a leak somewhere. Even if the system isn't releasing those views, memory use should max out after you've presented all 10 child controllers. If memory use grows without bound, your child controllers are probably giving up their views (and so creating new ones each time they're presented), but the views aren't being deallocated -- classic symptom of an over-retain situation. Try cutting the number of child controllers down to two and see if the same thing eventually happens. Or, use Instruments to look for a leak.
Update: -[UIImage imageNamed] has a reputation for never releasing the images it loads -- that may be the reason for your memory growth. Try loading the images using a different method, or not at all (since they're not really important to the experiment).
Instead of using imageNamed: use initWithContentsOfFile.
For example:
__weak NSString *filePath = [[NSBundle mainBundle] pathForResource:#"image" ofType:#"png"];
self.imageView.image = [[UIImage alloc] initWithContentsOfFile:filePath];
Because alloc method is being used, GC is marking this object. You can even mark the filePath string for GC cleanup.
I'm currently learning iPhone applications development, and I made some online tutorials to learn how all this is working.
I'm now quite used to the Objective-C concepts, and I'm trying to build a first application based on two views :
The first view would be the "Login view", simply with a kind of login system : a login field and a password field, and a "connect" button.
The second view is the "Home view" of the application, which will be called after the login.
I made a push segue to make the relation between the Login view and the view that is called after login. Here's what the storyboard looks like :
What I don't know actually is how to call a function that will check if the credentials are correct, and the switch to the other view if the login succeed.
Can anyone explain me, or give me some tips / tutorials for this please ?
Here are the sources for my LoginController :
LoginController.h
#interface LoginController : UIViewController {
IBOutlet UITextField *TFLogin;
IBOutlet UITextField *TFPassword;
}
#property (strong, nonatomic) IBOutlet UITextField *TFLogin;
#property (strong, nonatomic) IBOutlet UITextField *TFPassword;
- (IBAction)Connect:(UIButton *)sender;
#end
LoginController.m
#implementation LoginController
#synthesize TFLogin;
#synthesize TFPassword;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (IBAction)Connect:(UIButton *)sender
{
if ([TFLogin.text isEqualToString:#"myLogin"] && [TFPassword.text isEqualToString:#"myPassword"]) {
[self performSegueWithIdentifier:#"LoginSegue" sender:sender];
NSLog(#"Connection OK");
}
else {
NSLog(#"Connection Not OK");
}
}
#end
Thanks !
You have two choices for triggering a segue. The easy way is just to ctrl-drag in interface builder from the button to the next view controller. You can also do it in code (in an IBAction), by calling performSegueWithIdentifier:sender:.
If you go with the IBAction, you can validate the data there.
If you go with the interface builder method, you can't validate -- prepareForSegue:sender: will be too late. Anyway, there's a possible stumbling block here -- as I recall, UINavigationController doesn't forward prepareForSegue:sender: to its children. You can mitigate this with a category on UINavigationController or by subclassing.
I have written a sample code for button click action in xcode 4.2 for iOS5.
Here is the code
.h
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController
#property(strong,nonatomic) IBOutlet UIButton *button;
-(IBAction)changed;
#end
.m
#import "FirstViewController.h"
#implementation FirstViewController
#synthesize button=_button;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
// Custom initialization
}
return self;
}
- (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
{
[_button addTarget:self action:#selector(changed) forControlEvents:UIControlEventTouchUpInside];
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
-(IBAction)changed
{
NSLog(#"clicked");
}
- (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 YES;
}
#end
But when I click the button. I am getting exception. how to solve this issue? the same is working in iOS 4.3
first change this code into this one
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[_button addTarget:self action:#selector(changed) forControlEvents:UIControlEventTouchUpInside];
}
I have found solution for my problem.
The main problem is that, if I create a view controller object locally (i.e., inside any method) and add it as subview then, when you invoke any IBAction, at that time it will raise an exception because, the memory for that viewController is getting deallocated automatically when declared locally.
I guess he needs to add data member for that button as well. I mean code should look like this.
#interface FirstViewController : UIViewController{
IBOutlet *UIButton *button;
}
#property(strong,nonatomic) IBOutlet UIButton *button;
-(IBAction)changed;
#end
and try replacing
#synthesize button = _button;
with
#synthesize button;
in your .m file.
You have the wrong message signature for your changed action. It should be:
- (IBAction)changed:(id)sender
and in the addTarget line of code:
#selector(changed:)
PS: Why are you using _button? I don't think this is related to the problem, but you should be using self.button instead. Accessing instance variables directly should be avoided, especially in this case, where you are allowing the compiler to decide what name the variable should have.
PPS: As mentioned by #InderKumarRathore, you should also be calling [super viewDidLoad] before running your own code.
I have literally 24 IBOutlets in one view of a NavigationController (none are retained as properties). Is there a good way to release these? I have a feeling they are causing memory issues in slower 3G and 3GS devices.
No ARC
Any thoughts?
As you push UIViewControllers on to a UINavigationController, the view of the UIViewControllers which have been "pushed onto" may be unloaded to save memory (as they are invisible). The views are then reloaded when necessary (and you get the viewDidLoad callback). You should have IBOutlets to UIViewControllers not UINavigationController so that they can be released on viewDidUnload. The usual way to release them is to declare them as retained properties and set them to nil (with the self.outlet accessor) in viewDidUnload and dealloc. Or just use ARC.
ViewController.h
#interface ViewController : UIViewController
{
}
#property (retain, nonatomic) IBOutlet UILabel *myLabel;
#end
ViewController.m
#import "ViewController.h"
#implementation ViewController
#synthesize myLabel;
#pragma mark - View lifecycle
- (void)viewDidUnload
{
[self setMyLabel:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[myLabel release];
[super dealloc];
}
#end