I have been wondering which is the best way to load a navigation view. I have found that there are 3 ways I can do it without having major errors
What I was wondering is which one is best for memory and as a recommended practice ??
1)
no declaration in .h file (the code below IS NOT writen in the .h file)
#interface companyViewController : UIViewController {
EmployeeViewController *employeeDetailViewController;
}
#property (nonatomic, retain) EmployeeViewController *employeeDetailViewController;
then no #syntesize in .m file, no release in dealloc and no nil in viewDidUnload and when I call the new view I do:
EmployeeViewController *employeeController = [[EmployeeViewController alloc]
initWithNibName:#"EmployeeViewController" bundle:nil];
[self.navigationController pushViewController:employeeController animated:YES];
[employeeController release];
2)
I create it in the .h file (the code below IS written in the .h file)
#interface companyViewController : UIViewController {
EmployeeViewController *employeeDetailViewController;
}
#property (nonatomic, retain) EmployeeViewController *employeeDetailViewController;
then I #syntesize in .m file, with a release in dealloc and a nil in viewDidUnload and when I call the new view I do:
EmployeeViewController *employeeController = [[EmployeeViewController alloc]
initWithNibName:#"EmployeeViewController" bundle:nil];
employeeDetailViewController = employeeController;
[self.navigationController pushViewController:employeeController animated:YES];
[employeeController release];
3)
I do like 2 but I call the new view like this
employeeDetailViewController = [[EmployeeViewController alloc]
initWithNibName:#"EmployeeViewController" bundle:nil];
[self.navigationController pushViewController:employeeController animated:YES];
I feel like #3 is wrong because from what I understand in the memory management, I allocate it once in the #property (nonatmoic, retain) and I also retain it when I alloc it when I decide to call it. This will make the view have a retain count of 1 and lead to leaks.
To make sure I do not create an excessive amount of new views and get EXC_BAD_ACCESS or memory leaks, which one should be best ?
Thanks for the help
In none of these examples are you actually using the property you declare. I'll go through them in turn.
You created a property called employeeDetailViewController, but you never synthesize it or store anything in it. Your view controller is only ever stored in a local variable before passing it off to the navigation controller.
You create an instance variable called employeeDetailViewController, and a property also called employeeDetailViewController. However, you never store anything in the property, you only access the instance variable directly. Since you don't retain the view controller (it doesn't happen automatically when you use instance variables), you've got a situation where you might over-release.
This one will actually crash. You told it to pushViewController:employeeController without having defined employeeController.
So, let's consider what's right. There are two issues here: firstly, how to use properties, and secondly, whether you need an instance variable/property to refer to the view controller at all.
Considering properties:
To access a property, you use self.propertyName. If you just use propertyName directly, you're writing directly into the instance variable, and the memory management stuff (like automatically retaining stuff that's put in the property) is completely bypassed. Generally, you should only ever do that in your init or dealloc method: everywhere else you should access the property properly by self.propertyName.
Do you need an instance variable/property for the view controller?
I would say you don't actually need an instance variable or property for the view controller you're pushing. Once it's been handed off to the navigation controller, in general you won't need to access it again. My version of your code would be:
aViewController = [[EmployeeViewController alloc]
initWithNibName:#"EmployeeViewController" bundle:nil];
[self.navigationController pushViewController:aViewController animated:YES];
Unless you're wanting to refer to that particular view controller from elsewhere in your code, this is all you need. Nothing in the header, no property anywhere.
Property access is more like
self.employeeDetailViewController = [[[EmployeeViewController alloc] initWithNibName:#"EmployeeViewController" bundle:nil] autorelease];
And then,
-(void)dealloc {
self.employeeDetailViewController = nil;
[super dealloc];
}
Related
I was trying to push a viewcontroller B into navigation controller from A and then assigning some properties of B in A.
In this case, the assigning of properties was done and then viewDidLoad of viewcontroller A was executed.
Here, assigning properties in A should be done only after viewDidLoad of A has done.
For example,
[b.navController pushViewController:a animated:YES];
a.status = #"loaded";
Here, status was assigned first and then viewDidLoad of A was executed.
This happens only in iOS 7 whereas in iOS6 it works fine.
Can anyone please let me know where the problem is?
UPDATE: For me in some cases in iOS7, Push view is not working. How cna I debug and fix it?
Just access the viewcontroller.view (set any thing immediately after the alloc) property after the alloc init;
Which will loadview/viewdidload.
See Apple Documentation
In my experience, a UIViewController view is loaded lazily, no matter which iOS version you're working on. If you want to trigger a view load, and therefore its UIViewController viewDidLoad, you should access the view after the UIViewController is allocated. For example:
UIViewController *aViewController = [[CustomViewController alloc] init];
[aViewController view];
Make sure you don't code it as
aViewController.view
since that would generate a compiler warning.
So, in your case it would have to be something like this:
...
CustomViewController *a = [[CustomViewController alloc] init];
[b.navController pushViewController:a animated:YES];
[a view];
a.status = #"loaded";
Let me know if you have further problems with it.
You can know when a View Controller has been pushed onto the stack by implementing the UINavigationControllerDelegate and setting yourself as the delegate self.navigationController.delegate = self; then you will get this callback after every push
navigationController:didShowViewController:animated:
So you can check if the shown viewController is the one your interested in, then set your a.status.
I would suggest you call a delegate method once the view is loaded.
Set the delegate to be controller B.
and after viewDidLoad finishes (in controller A) call the delegate method. You can even pass parameters as you wish to the delegate.
Here's some example code:
Controller B:
a.delegate = self;
[b.navigationController pushViewController:a animated:YES];
Implement the delegate method:
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status
{
a.status = status;
}
Controller A .h file:
#class ControllerA;
#protocol ControllerADelegate <NSObject>
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status;
#end
#interface ControllerA : UIViewController
#property (nonatomic, weak) id <ControllerADelegate> delegate;
Controller A .m file:
- (void)viewDidLoad:(BOOL)animated
{
[super viewDidLoad:animated];
/* your viewDidLoad code here... */
if ([_delegate respondsToSelector:#selector(controllerIsLoaded:status)])
[_delegate controllerIsLoaded:self status:#"Loaded"];
}
Turn off animation for ios7, in my case its work
[b.navController pushViewController:a animated:NO];
a.status = #"loaded";
No documentation provides enough information to know exactly when viewDidLoad would be called.
UIViewController's documentation just says this
This method is called after the view controller has loaded its view hierarchy into memory
I would suggest that you create a custom initializer like this
- (id)initWithStatus:(NSString *)status {
}
But, if you are trying to use this variable to check if the viewController's view has 'loaded' or not, it may not be possible to do that because the pushViewController or presentViewController methods are not guaranteed to be synchronous.
Even in iOS 6, there was no explicit guarantee that the view would be loaded as soon as that method returned.
Please write the code in viewWillAppear method instead of viewDidLoad in next class i.e. where you are pushing the object to
-(void)viewWillAppear:(BOOL)animated
{
}
I'm understand of your question like this:
B *b = [[B alloc] init];
b.status = #"loaded";
[self.navigationController pushViewController:b animated:Yes];
If you want to pass a value from one controller to another means, you have to assign a value before using pushViewController method.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Passing Data between View Controllers
I have two view controllers and I want to get some information from the previous view in my app. e.g:
In my app I go from the first page to the second. Depending on what button the user pushes, I want to change the info on the second screen. What's the fastest and easiest way to do this? I tried importing the class and rebuilding but that recreates the string obj and doesn't keep the info that I want.
Well there are (at least) two possibilities:
Add a property to the next view controller, do something like
NewVC *vc = [[NewVC alloc] init]; // or initWithNibName...
[vc setMyInformation:information];
Create a custom init method:
NewVC *vc = [[NewVC alloc] initWithMyInformation:information andNibName:#"nibName" bundle:nil]; // well you should get the point...
In your second ("child") view controller, keep a property for the string (see section 9).
When you instantiate the second view controller and before you push it onto the stack from the first view controller, set the string property's value, e.g., retain the first controller's string:
mySecondViewController.infoString = myFirstViewController.infoString;
Make sure your second view controller manages the memory for the string (usually with a release message in the controller's dealloc method, assuming your property is defined with a retain).
A second option is to keep properties in your application delegate, or another singleton that manages data for the application. But the first approach is a bit more lightweight.
If I understand you correctly, what you need is to create an instance variable in vc2. And then when you create an instance of vc2 from vc1 you can access that iVar to assign value, etc. before you present vc2. Here is an example:
In ViewController2.h file:
#interface ViewController2
{
NSString *string2; //create an instance variable
}
#property (nonatomic, retain) NSString *string2;
In ViewController2.m file:
#implementation ViewController2
#synthesize string2;
In ViewController1.m file:
#implementation ViewController1
//ViewController2 *viewController2 = [[ViewController2 alloc] initWithNibName:#"ViewController2" bundle:nil]; //one way to instantiate
viewController2.string2 = #"whatever string"; //here you assign the value to the instance variable string2 in viewController2
//[self.navigationController pushViewController:childController animated:YES]; //etc. it depend on how you present viewcontroller2
I have some weird issue here as somehow I am unable to pass NSString from one class to another. I deployed the same method that worked on other classes.
I am trying to pass a string from secondViewController to my firstViewController. Here's what I did.
firstViewController.h
NSString *pickUpAddressString;
#property (nonatomic, retain) NSString *pickUpAddressString;
firstViewController.m
#synthesize pickUpAddressString;
-(void) viewWillAppear:(BOOL)animated {
NSLog(#"pickUpAddressString is %#", pickUpAddressString); // why it's null here?
PickUpAddress.text = pickUpAddressString; // PickUpAddress is a UITextField
}
secondViewController.m
FirstViewController *controller = [[FirstViewController alloc]init];
controller.pickUpAddressString = selectedAddress; // here, I set the string
NSLog(#"selected address :%#\npickUpAddressString:%#", selectedAddress, controller.pickUpAddressString); // I had verified that both strings are valid.
[self.navigationController popViewControllerAnimated:YES]; // pop to firstView
You are creating a new instance of FirstViewController...
FirstViewController *controller = [[FirstViewController alloc]init];
...different from the original instance that (I'm assuming) pushed the SecondViewController and to which you are returning via popViewControllerAnimated:.
Basically, what you need is to pass data back to the controller that pushed the SecondViewController, in this case, the FirstViewController.
Perhaps, the easiest way to achieve this is what #Ladislav suggested in his comment:
NSArray *viewControllers = [self.navigationController viewControllers];
FirstViewController *firstVC = [viewControllers objectAtIndex:[viewControllers count] - 2];
However, keep in mind that this introduces a direct dependency between SecondViewController and FirstViewController. Or, in other words, SecondViewController is now tightly coupled to FirstViewController.
In a nutshell, when it comes to pass data back up the hierarchy, it is a best practice to use loose coupling to avoid direct dependencies between your view controllers (tight coupling). The benefits of doing so include code reusability and testability. In order to achieve loose coupling you need to define a generic interface for observers (e.g. delegation, notifications, etc).
It is also worth mentioning the importance of putting state information in model objects. Avoid putting data inside the controllers, unless it's strictly presentation data.
More on this topic: What's the best way to communicate between view controllers?
I'm trying to send an array from one viewController to another using protocols and delegates. I declared the delegate on viewcontroller B and used the following code in viewcontroller A to send the message from A to B. The protocol's method is didReceiveMessage. Unfortunately, the message never arrives.
Attached is the code from viewController A
- (IBAction) graphPressed:(UIButton *)sender {
GraphingViewController *gvc=[[GraphingViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:gvc animated:YES];
[gvc release];
[delegate didReceiveMessage:brain.internalExpression];
}
and the code from viewcontrollerB
- (IBAction) ViewdidLoad {
CalculatorViewController *cvc =[[CalculatorViewController alloc] init];
cvc.delegate=self;
[cvc release];
}
- (void) didReceiveMessage:(NSMutableArray *)expression {
NSLog(#"message received from CalculatorAppDelegate");
}
Any suggestions would be greatly appreciated.
I'm not sure what are you doing in second sample? You created an object, assign its delegate property and then released it. Why?
If that is code from your application it could probably be from releasing cvc at the end of your ViewDidLoad method.
init would give it a retain count of 1, and the release would take it back to 0.
Also the code seems sort of backwards, if you wanted to set view A as the delegate for view B, you would do so in view A.
Unless there is a more complex reason to use a delegate that I'm not seeing from the code, I would just keep a pointer around to the child if you really need to send it messages.
Like others have posted, you are just getting rid of the Calculator VC after creating it. I would recommend adding an #property to the second VC and using it to store a pointer to the Calculator. It should be a retain property. Then set the delegate of that property to self.
Also, you make sure to use an assign property for the delegate value, and try to use the property syntax (self.delegate) whenever possible.
There could absolutely be more going on here, so if this doesn't actually solve the problem try and post up more of your code (header files, what connections are made in IB, etc.)
Edit: For the record, the method is -(void)viewDidLoad, not -(void)ViewDidLoad, so that could be contributing to the problem.
As you said you are trying to pass an array from one view controller to another.Well i use in this way.Here is the code
- (IBAction) graphPressed:(UIButton *)sender {
GraphingViewController *gvc=[[GraphingViewController alloc] initWithNibName:nil bundle:nil];
gvc.myArray=infoArray;
[self presentModalViewController:gvc animated:YES];
[gvc release];
}
Where myArray is array in your GraphingViewController,You just need to make property of this array with retain attribute,simply as
#property(nonatomic,retain)NSMutableArray *myArray;
And you need to Synthesize it as well.
I have two views, the parent view opens a pop over that has a child view.
In child controller
#property (nonatomic, assign) ParentController *parent;
In parent controller
ChildController *child = [[ChildController alloc] init];
child.parent = self;
My question is in the dealloc method of the child controller, do you set self.parent = nil or release?
This has a bad code smell. It doesn't make much sense and I'm surprised it compiles:
ChildController *child = [[ParentController alloc] init];
And I'm not sure what you mean by "pop over" -- that term has a specific meaning now in iOS (see: Consider Using Popovers for Some Modal Tasks in the iPad Human Interface Guidelines).
Your question "in the dealloc method of the child controller, do you set self.parent = nil or release?" can't be answered properly, as it's also a bad code smell. There's no reason for a child view controller to be fiddling with any reference to a parent view controller like that.
(Although some people have answered your question while I was typing this up, I think you have some design problems that need to be acknowledged)
How are you presenting your "ChildView"? Modally? If so, your code might look something like this:
- (void)showChildView
{
ChildViewController* childViewController = [[ChildViewController alloc] initWithNibName:#"ChildView" bundle:nil];
childViewController.delegate = self;
childViewController.someProperty = #"Some Value";
[self.navigationController presentModalViewController:childViewController animated:YES];
[childViewController release];
}
You should then create a delegate protocol with your ChildViewController class that your ParentViewController class will implement, so it knows when the ChildViewController is finished so it can deal with removing the view appropriately.
Generally, the idea of the ChildViewController needing a pointer back to the ParentViewController is a bad code smell because it sets up a circular dependency.
Set it to nil (or do nothing at all). Releasing would be wrong because your property doesn't retain (it just assigns).
Why do you need a property for the parent view controller anyway? UIViewController already contains a property called parentViewController, so you don't need to define another one.
But if you must do this, you should:
Use retain instead of assign in your property declaration.
Use [self.parent release] in your dealloc method in your child view controller.