I have a custom ViewController which is an instance variable of my root viewController.
I intend to modally present it whenever a button is touch. Therefore the viewController will be presented and dismissed potentially many many times.
I obviously only want to alloc init my instance variable once as the modal viewController is not deallocated each time it's dismissed, so should I have code like this inside my button action to ensure that it's only alloc and inited once?:
if(!myViewController)
{
ViewController *myViewController = [[ViewController alloc] init];
}
[self presentViewController:myViewController animated:YES completion:NULL];
I usually use lazy instatiation in those cases:
Declare a property for your ViewController:
#property(nonatomic, strong) UIViewController *myViewController;
After that you can override the get of myViewController
-(UIViewController*) myViewController {
if(!_myViewController) {
_myViewController = [[UIViewController alloc] init];
}
return _myViewController;
}
This way you guarantee that was only instantiated once and is always there when you needed.
ATTENTION
This works well if you always use self.myViewController. I consider a good practice that properties' generated iVars should only be accessed in their setters/getters.
You can use the following way to ensure that only one instance of the view controller active at a time.
if(myViewController) {
[myViewController release];
myViewController = nil;
}
myViewController = [[ViewController alloc] init];
[self presentViewController:myViewController animated:YES completion:NULL];
You need to make myViewController as class variable.
Related
Let's say you're pushing a new View Controller onto the navigation stack and need to set a UI property (ex: a UI label's text or something). When you initialize the VC, it's views are not set (so they could be nil). Therefore, setting properties won't work. For example:
SomeViewController *vc = [[SomeViewController alloc] init];
SomeViewController.someUILabel.text = #"foo";
[self.navigationController pushViewController:vc animated:YES];
This won't set the UI label's text because vc.view and its subviews are nil. A couple of ways to remedy this are:
After calling init on the VC, do something like [vc view] which will load the view and then allow you to set properties.
Set a non-UI ivar and then in viewDidLoad set up the UI like so:
SomeViewController *vc = [[SomeViewController alloc] init];
SomeViewController.uiTextLabel = #"foo";
[self.navigationController pushViewController:vc animated:YES];
// in viewDidLoad
self.someUILabel.text = self.uiTextLabel
Is there an accepted way to get rid of this problem? Is one of these better than the other or is there a different solution?
You should not set the label value from another view controler. A controller controls its views.
You should have a NSString property in you SomeViewController, and set that public property instead with the string you want. Then, in SomeViewController viewDidLoad method set the value of the label to the one in the property.
Initialize the views after pushing it in the stack and in the pushed vc not the one that pushed it.
I found that the most elegant method to do this is:
in the main VC:
SomeViewController *vc = [[SomeViewController alloc] init];
SomeViewController.textForLabel = #"foo";
[self.navigationController pushViewController:vc animated:YES];
And in the pushed VC:
#interface SomeViewController : UIViewController
#property (nonatomic, strong) UILabel *textLabel;
#end
- (void)setTextForLabel:textForLabel
{
_textForLabel = textForLabel;
self.textLabel.text = self.textForLabel;
}
- (void)viewDidLoad
{
self.textLabel.text = self.textForLabel;
}
So basically you set the property as an NSString and then adjust the UI in two separate places.
I have a UINavigationController in which I push several UIViewControllers.
I want that every time a new UIViewController is pushed, the older ones get released from memory.
For that, in every UIViewController I'm putting this piece of code:
-(void)viewDidAppear{
self.navigationController.viewControllers = #[self];
}
This way the viewControllers array gets reduced only to the one being displayed. But since I'm using ARC, every UIViewController is a strong reference and it's not being released from memory.
I've tried creating a weak instance of every UIViewcontroller when pushing them using this code:
FirstViewController.m
-(IBAction)goToSecond:(id)sender{
SecondViewController *secondVC = [[SecondViewController alloc]init];
__weak SecondViewController *weakSecondVC = secondVC;
[self.navigationController pushViewController:weakSecondVC animated:NO];
}
But this way I'm creating two instances: the weak one that is being pushed and the strong one that stays in memory.
I have also tried creating just the weak reference and pushing it:
FirstViewController.m
-(IBAction)goToSecond:(id)sender{
__weak SecondViewController *weakSecondVC;
[self.navigationController pushViewController:weakSecondVC animated:NO];
}
But then I get the following:
Application tried to push a nil view controller on target <UINavigationController: 0x127606210>.
Is there any way to achive this?
EDIT:
As suggested in the answer I've tried doing the following:
-(void)goToSecond:(id)sender{
SecondViewController *pistasVC = [[EYSPistasViewController alloc] init];
[self.navigationController setViewControllers: #[secondVC]];
[self.navigationController popViewControllerAnimated:NO];
}
The UINavigationController stack of UIViewController it's reduced to the one I'm setting, but the memory still keeps adding up.
Here you can see a comparison of both methods:
You can try this code to make navigation controller to contain only one viewController.
-(IBAction)goToSecond:(id)sender
{
SecondViewController *secondVC = [[SecondViewController alloc]init];
[self.navigationController setViewControllers:#[secondVC] animated:NO];
}
I am trying to add a UIView to a view controller, but through an object of another class whose objects are currently present on the view controller in question.
myViewController is on screen -> It has objects of myNodeView added to it.
I click on myNodeView and call a method in myViewController class to add a view to it.
I instantiate using myVc = [[myViewController alloc]init];
and then call the method. Is this the problem, that this is a new instance and that's why it does not add to the view currently visible. Please help me out.
Code -
// in nodeView
-(void)loadMap{
if(myVc==nil){
myVc=[[MyViewController alloc]init];
}
[myVc loadMapView];
}
// in MyViewController
-(void)loadMapView
{
if(mapView==nil)
{
self.mapView = [[MapView alloc]initWithFrame:CGRectMake(0, 0, 400, 400)];
self.mapView.backgroundColor=[UIColor whiteColor];
}
[self.view addSubview:self.mapView];
}
You are creating a new instance of MyViewController each time you call loadMap method. You can do something like this:
// Getting current viewcontroller
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController)
topController = topController.presentedViewController;
// Call the method
MyViewController *myVc = (MyViewController *)topController;
[myVc loadMapView];
In MyViewcontroller, when you create the nodeView, you should be assigning the nodeView's myVc property, like this nodeView.myVc = self.
You should also make sure that nodeView's myVc property is assign.
You also don't need to check if myVc == nil in the nodeView.
I think make MyViewController is childViewController (Implementing a Container View Controller) is more predictable in behavior of views and events passed.
I was thinking about this today, and now i've tested i'm a little confused…
When using viewControllers either by pushing a viewController onto the Navigation Stack or Presenting a ViewController modally I'm wondering about memory management.
Lets use the modal example as a thought experiment, here is the source to create and present the view, in my example it doesn't matter if ARC or not so here's both:
With ARC:
ViewController *myViewController = [[ViewController alloc] init];
myViewController.delegate = self;
[self presentViewController:myViewController animated:YES completion:NULL];
Without ARC:
ViewController *myViewController = [[ViewController alloc] init];
myViewController.delegate = self;
[self presentViewController:myViewController animated:YES completion:NULL];
[myViewController release]; //As it's now 'owned' by the presenting View controller
This would be my understanding about how to present a viewController modally over an existing ViewController.
Lets say for our example the above code resides in a method which is called when a button is touched to present the ViewController.
Now to my question,
What I am doing is calling this code each time a button is touched, During testing with Instruments I didn't seem to have any leaks. - However because I have NSLog statements in the myViewController dealloc & viewDidLoad methods I know that it's getting instanciated everytime I touch the button but never deallocated.
So...
A) Why am I not getting a leak showing (or a rise in Live Bytes) in instruments (when either using ARC or not) because I am seemingly creating a new viewController and leaking the old one each time I go to present it.
B) What is the correct way to write the above code if this is not memory safe? I see this kind of code snippets all over Apple's example code and internet. Should I (and they) not be wrapping the alloc init line in an if statement to check if the object is already created?
i.e.
if(!myViewController)
{
ViewController *myViewController = [[ViewController alloc] init];
}
myViewController.delegate = self;
[self presentViewController:myViewController animated:YES completion:NULL];
Thanks for taking the time to read and answer, I really wonder about this as I've been creating, pushing and presenting ViewControllers using the above code the whole time, and never noticed a leak! - might have to go back and rewrite it all!
To avoid confusion please note: The delegate property is a custom property of my UIViewController subclass (where I've implemented a delegate protocol), required to dismiss the Modally present Viewcontroller properly. As per coding guidelines.
Regards,
John
EDIT As Requested, Creation of the delegate:
.h
#protocol NotificationManagementViewControllerDelegate;
#interface NotificationManagementController :
{
__weak NSObject <NotificationManagementViewControllerDelegate> *delegate;
}
#property (nonatomic, weak) NSObject <NotificationManagementViewControllerDelegate> *delegate;
#protocol NotificationManagementViewControllerDelegate <NSObject>
#optional
- (void)didFinishSettingNotification:(NotificationManagementController *)notificationManagementController;
.m
- (void)sendMessageToDismiss {
if ([[self delegate] respondsToSelector:#selector(didFinishSettingNotification:)]) {
[self.delegate didFinishSettingNotification:self];
}
}
And finally in the delegates .m:
- (void)didFinishSettingNotification:(NotificationManagementController *)notificationManagementController
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
You are not getting a leak because you create a new controller and ARC will release this allocation for you.
But, it's better to create a #property for your new view controller.
and modify your i.e. implementation like :
#property (nonatomic, strong) ViewController *myViewController;
if (!_myViewController)
self.myViewController = [[ViewController alloc] init];
self.myViewController.delegate = self;
[self presentViewController:_myViewController animated:YES completion:nil];
Here, you have a lazy property and you don't create a new one ViewController after the first creation.
But, you need to pass your delegate (or any property) outside your test.
Furthermore, if you use your first implementation and add this controller in a subview of the current controller without property, this will work but you will get a leak.
I got this experience with the code below :
RootViewController
- (void)viewDidLoad
{
[super viewDidLoad];
ViewController *myViewController = [[ViewController alloc] init];
[self.view addSubview:myViewController.view];
}
myViewController will be add on the screen but released immediately without keeping any reference of the object, so if you add an action in 'ViewController`, your application will crash without explanation of XCode.
So, the correct way to write this without leak will be :
- (void)viewDidLoad
{
[super viewDidLoad];
if (!_myViewController)
self.myViewController = [[ViewController alloc] init];
[self.view addSubview:self.myViewController.view];
}
The answer is a bit longer and can be improved so don't hesitate !
Hope it's going to help some people.
I have a navigation based application.On click of a button on the navigation bar in the first screen , I am able to push another view controller as follows :
-(void) buttonClicked:(id)sender
{
UIViewController* mv = [[SecondViewController alloc] init];
[[self navigationController] pushViewController:mv animated:YES];
}
Now i have a UIView(separate .h and .m files) as part of the first screen. On click of a button in the UIView, i want to push the SecondViewController.
I have tried the following :
UIViewController* mv = [[SecondViewController alloc] init];
UIViewController * home=[[FirstViewController alloc]init];
[[home navigationController] pushViewController:mv animated:YES];
It doesnt work!! Kindly help
UIViewController* mv = [[SecondViewController alloc] init];
UIViewController * home=[[FirstViewController alloc]init];
[[home navigationController] pushViewController:mv animated:YES];
The problem here is that home isn't part of the navigation stack, so [home navigationController] is surely nil. I'm not quite clear on what you're trying to do here, but just creating a view controller doesn't mean that it's actually part of the view controller graph.
Why would it work? Randomly creating view controllers whose view is not even visible, is not the solution. You can either keep a reference to the VC in the view like this:
#imlementation ViewController
- (id) init
{
// ...
aView = [[CustomView alloc] init];
aView.viewController = self;
// ...
}
#end
#interface CustomView
#property (assign) ViewController *viewController;
#end
Or you can search the responder chain at runtime:
UIResponder *next = [view nextResponder];
while (next)
{
if ([next isKindOfClass:[ViewController class]])
{
break;
}
next = [next nextResponder];
}
And now "next" will contain the view controller (or nil if it can't be found).
Try using the same navigationController to push view, this keeps the same stack of ViewControllers.
UIViewController* mv = [[SecondViewController alloc] init];
[[self navigationController] pushViewController:mv animated:YES];
[mv release];
I see your problem now! You need to #import your FirstViewController, then #class it. Then do your push.
So:
//.h
#import "FirstViewContoller.h"
#class FirstViewController;
#interface...
//.m
-(void)return {
FirstViewController *firstview = [[FirstViewController alloc]init(withnibname:)];
[firstView.navigationController pushViewController: firstView.navigationController.topViewController animated: TRUE];
}
If I am not wrong, your UIView though is in separate files, is still added to the screen from a UIViewController class.
Simply, post a notification from UIView to your FirstViewController class where you have access to the navigation controller. Then push the SecondViewController from there.
You Can use this. It Works very well for me:-
Firstly Create Object of AppDelegate in UIView Class and initialize it. Then create Navigationcontroller object in Appdelegate.h :-
#property(strong,nonatomic) UINavigationController *navControl;
In your UIView Class implement this code where you want to push :-
ViewController *objview = [[ViewController alloc]init]; [appDelegate.navControl pushViewController:objview animated:YES];