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.
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.
I have a UINavigationController in which I am loading different view controllers. I want to know how can i access the elements (like labels etc) of my previous view.
Here is an eg.
View A
myLabel.text = #"first view";
(User moves to view B)
View B
(user entered a message, that i need to display in View A)
something like ViewA.myLabel.text = #"user entered message"
I tried many things but was not able to find anything very useful. Please help..
I am using Xcode 4 without ARC and without storyboard.
Thanks
Sam
Edited:
I want to update the property declared in viewController of View A and not the labels directly. My labels get updated using that property. Like while pushing the viewController we can pass the values as below.
ViewA *myView = [[ViewA alloc] init];
myView.title = #"View B" ;
myView.tableView.tag = 3;
myView.myTextView.text = #"Some Text";
[self.navigationController pushViewController:myView animated:YES];
[myView release];
Is there any way to pass these values to properties of ViewController of ViewA while popping ViewB and returning back to ViewA ?
The actual scenario is as follows: the user gets and option to write a message in textView or he can use the predefined templates. If he clicks on the templates button he is taken to a list of predefined templates where he can select any of the predefined message. Now I want that when the user click on any of the predefined message the view containing the list of predefined message gets popped of and the message he selected gets automatically populated in the textView of main view. what is the best approach to achieve this ?
TIA
Sam
You should set your AViewController as the delegate of your BViewController so you can message it back after a particular event. Using a delegate will also allow better decoupling of your ViewControllers.
In your BViewController, define a protocol like this :
BViewController.h :
#protocol BViewControllerDelegate <NSObject>
- (void)viewB:(UIViewController *)didEnterMessage:(NSString *)message;
#end
and add a delegate property :
#property (nonatomic, weak) id <BViewControllerDelegate> delegate;
When the user enter the message in your BViewController and hit the button that pops the BViewController to show to AViewController do this :
- (IBAction)messageEntered {
if ([self.delegate respondsToSelector:#selector(viewB:didEnterMessage:)]) {
[self.delegate viewB:self didEnterMessage:self.yourTextField.text];
}
}
Your AViewController should implement the BViewControllerDelegate protocol like this :
AViewController.h :
#interface AViewController <BViewControllerDelegate>
When your AViewController creates the BViewController, it should set itself as its delegate before presenting it. Might look like this :
BViewController *bvc = [[BViewController alloc] init…];
bvc.delegate = self;
And finally, your AViewController should implement the viewB:didEnterMessage: method :
- (void)viewB:(UIViewController *)didEnterMessage:(NSString *)message {
self.myLabel.text = message;
}
That's the cleanest way to do that, IMHO.
You can get the navigation controller's viewControllers property and use it, perhaps like this:
UILabel *label = ((SomeViewController *)[self.navigationController.viewControllers objectAtIndex:1]).myLabel;
However, that is not reliable. Since the “previous” view is off the screen, the system can unload it to free up memory. Then label will be nil.
You could force that other view controller to reload its view (if it has been unloaded) by accessing the view controller's view property.
But really this smells like bad design. You should almost never try to access the views of a view controller when that view controller's view is not on screen. Remember how the system can unload a view controller's view if the view is off-screen? If some UILabel under that view contained the only copy of important data, that data is now gone!
Any important data needs to be stored somewhere other than a view - perhaps in a property of the view controller, or in a model object. You should ask the view controller for the data, or for the model object that contains the data. A view controller's view objects should almost always be considered a private implementation detail of the view controller, not exposed to other classes.
EDIT
Your question is puzzling because you talk about popping ViewB and returning to ViewA, but your code only creates and pushes a ViewA. ViewB is not mentioned in the code.
I will assume that your ViewA creates and pushes a ViewB. So you should give ViewB a property of type ViewA, like this:
#class ViewA; // forward declaration to avoid circular imports
#interface ViewB
#property (weak, nonatomic) ViewA *aView;
Then, when your ViewA creates a ViewB instance, you set the aView property:
#implementation ViewA
- (void)pushViewB {
ViewB *bView = [[ViewB alloc] init];
bView.aView = self;
[self.navigationController pushViewController:bView animated:YES];
}
Now your ViewB has access to the ViewA that created it, and can set the properties of that ViewA.
If you want to write a good code you should follow the Model-View-Controller pattern. Here's rather good tutrial http://www.cocoalab.com/?q=node/24 In a couple of words it means that you should not store data in View (and also a view should not act as controller). I suggest you to write a custom class that will do this management(store data and pass it from one view to another).
If it's just a test app then you can use viewControllers property of UINavigationController to access the controllers which are in navigation stack or just create a variable to store this data for example, in View B
- (void)textFieldDidEndEditing:(UITextField *)textField {
stringToDisplayInFirstController = textField.text;
NSArray * arrayOfControllers = self.navigationController.viewControllers;
UIViewController * viewControllerA = [arrayOfControllers objectAtIndex:[arrayOfControllers count]-1];
viewControllerA.label.text = stringToDisplayInFirstController;
}
recently I joined two Xcode projects together. To get this thing to work, I had to alloc and initialize my view controller.
self.myViewController = [[MyViewController alloc] init];
But why? In the other project I have the same code. The sole difference is hierarchy of the different views. I added a new view to the top (beginning). So the calling View Controller is not the first view on the stack anymore.
I'm pushing my view in this way on the stack:
[[self navigationController] pushViewController:myViewController animated:YES];
In my NIBs I have added a View Controller object with IB and connected the Outlets.
And I have a memory management question too: If I have a property like myViewController, do I have to release it? The "normal" release is done in the dealloc method. But do I have to use an additional release because of the alloc? I don't think so, but I ask you anyway.
I would need to see more code to answer why you had to alloc your view controller, but I'd say that you always alloc them manually (at least in my experience).
As for the memory management question, if your property is declared as a retain property (#property(retain) UIViewController *myViewController), you are indeed leaking memory, since the retain count after alloc will be 1, and after the retain done by your accessor will be 2. Hence, if you release it only once, you'll end up with a leak.
I usually do this instead:
self.myViewController = [[[MyViewController alloc] init] autorelease];
I found it out: In IB I had to set the nib name on my view controller object. So the alloc and initialization is done by IB?
There is one more option:
(IBAction)loginButton:(UIButton *)sender {
NSLog(#"pressed login");
ICMasterViewController *controller = [[self storyboard] instantiateViewControllerWithIdentifier:#"mainnav"];
[self presentViewController:controller animated:YES completion:nil];
}
On your storyboard you must have UIViewController with name mainnav
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];
}
I have presented the viewController which contains a navigation controller whose view is loaded from another nib with the UITableView in it. I would like to dismiss that presented viewController from the UITableViewController. I have tried every combination of self.parentViewController and self.navigationController and self.navigationController.parentViewController but I am still not able to dismiss it. How and what is the best way for it to be dismissed?
I have never gotten self.navigationController.parentViewController to work as advertised, but this does work for me:
NSArray *viewControllerArray = [self.navigationController viewControllers];
int parentViewControllerIndex = [viewControllerArray count] - 2;
[[viewControllerArray objectAtIndex:parentViewControllerIndex] myParentViewMethod:arg1 withArg2:arg2 ...];
Is this not working?
[myViewController presentModalViewController:otherController animated:YES];
// ...
[myViewController dismissModalViewControllerAnimated:YES];
It sounds like you've painted yourself into a corner a bit with your hierarchy, but that's ok I think we can fix it. There the two "usual" ways of doing this are:
From controller A, present controller B, from B do work, from B dismiss, return to A
From controller A, present controller B, from B do work, raise event via delegate protocol to A, from A dismiss B which returns control to A
In your current situation, this may require an uncomfortable ammount of refactoring, in which case it may be easier to just expose a handle to the other controller as a property, just make sure it's a weak reference like so:
#interface ControllerB : UIViewController
{
ControllerA* controllerA;
}
#property (readwrite, nonatomic, assign) ControllerA* controllerA; // weak reference
#end
Or however you like, the point is to scope the variable properly so that it's available when you need it.
I just ended up using delegates to dismiss the parent.