I have a button click handler that saves an object and then presents another controller. My problem is that with each click the memory allocated increases.
if (success) {
ALRollsViewController *rollsController = [[UIStoryboard storyboardWithName:#"Entry" bundle:nil]instantiateViewControllerWithIdentifier:#"RollsController"];
rollsController.camera= selectedCamera;
[self presentViewController:rollsController
animated:YES
completion:nil];
}
If I use dismissViewControllerAnimated as opposed to presentViewController:rollsController there is no buildup. Do I need to release the instantiated controller somehow?
What do you expect?
Each click instanticates a new instance of UIStoryboard. Naming convention here is, that a method beginning with the name of the object (name without prefixes) returns a newly created instance of the object.
See and compare to NSArray arrayWith... or NSString stringWith...
Plus you need an instance of the view controller every time to be presented.
Both instances are kept until the view controller is dismissed.
(I am not 100% positive about the UIStoryboard instance to be kept that long, but the newly presented view controller will eat up your heap and a bit of your stack.)
Related
I am developing an app that is a single NSWindow and clicking a button inside the window will present a NSViewController, and a button exists in that controller that will present a different NSViewController. I know how to swap out views in the window, but I ran into an issue trying to do this with the multiple view controllers. I have resolved the issue, but I don't believe I am accomplishing this behavior in an appropriate way.
I originally defined a method in the AppDelegate:
- (void)displayViewcontroller:(NSViewController *)viewController {
BOOL ended = [self.window makeFirstResponder:self.window];
if (!ended) {
NSBeep();
return;
}
[self.box setContentView:viewController.view];
}
I set up a target/action for an NSButton to the AppDelegate, and here's where I call that method to show a new view controller:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self displayViewcontroller:newVC];
}
This does work - it presents the new view controller's view. However if I then click any button in that view that has a target/action set up that resides within its view controller class, the app instantly crashes.
To resolve this issue, I have to change didTapContinue: to the following:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self.viewControllers addObject:newVC];
[self displayViewcontroller:[self.viewControllers lastObject]];
}
First of all, can you explain why that resolves the issue? Seems to be related to the way the controller is "held onto" in memory but I'm not positive.
My question is, how do I set this up so that I can swap out views from within any view controller? I was planning on getting a reference to the AppDelegate and calling displayViewcontroller: with a new controller I just instantiated in that class, but this causes the crash. I need to first store it in the array then send that reference into the method. Is that a valid approach - make the viewControllers array public then call that method with the lastObject, or how should this be set up?
What is interesting in your code is that you alloc/init a new view controller every time that you call the IBAction. It can be that your view its totally new every time you call the IBAction method, but I would think that you only have a limited number of views you want to show. As far as my knowledge goes this makes your view only to live as long as your IBAction method is long. That the view still exists, is because you haven't refreshed it. However, calling a method inside a view controller that is not in the heap anymore (since you left the IBAction method and all local objects, such as your view controller are taken of the heap thans to ARC) makes the app crash, because you reference a memory space that is not in use or used by something else.
Why does the app work when you ad the view to the viewcontrollers array? I assume this array is an array that has been initiated in the AppDelegate and now you add the view controller with a strong reference count to the viewcontrollers array. When you leave the IBAction method, the view controller still has a strong reference and ARC will not deallocate the view controller.
Is this the proper way? Well, it works. I would not think it is considered very good programming, since you don't alloc/init an object in a method that needs to stay alive after leaving the method. It would be better practice to allocate and initialize your view controller(s) somewhere in an init, awakeFromNIB or a windowDidLoad method of your AppDelegate. The problem with your current solution is that you are creating an endless array of view controllers of which you only use the last. Somewhere your program will feel the burden of this enormously long array of pretty heavy objects (view controllers) and will run out of memory.
Hope this helps.
By the way, this is independent of whether you use Mavericks or Yosemite. I was thinking in a storyboard solution, but that wouldn't answer your question.
Kind regards,
MacUserT
in any iPhone app, consider the following sequence (VC=ViewController) :
VC-A is displayed ... user hits a button to load VC-B
VC-B is now pushed and displayed ... user moves a slider from 0 to 5 ... then hits Back on navBar
VC-B is popped, VC-A is displayed ... user hits a button again to load VC-B
VC-B is pushed and displayed ... but the slider is back to 0
This is happening because VC-B instance in step 2 above is different than VC-B instance in step 4, hence the state is lost. To avoid this, I can either make VC-B a singelton, or keep a strong reference to VC-B somewhere and reuse it instead of creating a new instance.
This logic used to work well when I was loading my VCs from a nib, since I would create the instance of the VC manually inside the code. With storyboards, this doesnt work, since it is the segue that creates the instance automatically. How can I then ensure that I reuse my VCs instead?
I tried the following trick to maintain a singelton:
- (id) initWithCoder:(NSCoder *)aDecoder{
if(instance==nil){
NSLog(#"LMHomeViewController init called");
instance = [super initWithCoder:aDecoder];
super.screenType = [NSNumber numberWithInt:home_screen];
}
return instance;
}
but I got a runtime error :
Terminating app due to uncaught exception 'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
Any suggestions here? My interest is to maintain the state of the VC as the user goes back and forth in the navigation tree.
Note: the state in my case is a UIWebView, hence its not possible to persist its state.
This could be a case for using NSUserDefaults - read state in on viewWillAppear, write it out on viewWillDisappear, (and wherever else appropriate)
In any case, it's better MVC to persist in your model rather than in the view hierarchy. So if not NSUserDefaults, consider a singleton that only needs to store the state data.
Another idea. if you do want to keep the viewController object around - don't use a storyboard segue. Keep a strong reference in a property and use
[self.navigationController pushViewController:self.persistingViewController];
The persistingViewController accessor can lazily-instantiate the viewController first time round. So long as you keep the reference, you can reuse:
- (UIViewController*) persistingViewController
{
if (!_persistingViewController) {
UIStoryboard *story = [UIStoryboard storyboardWithName:#"MainStoryBoard" bundle:nil];
_persistingViewController = [story instantiateViewControllerWithIdentifier:#"myViewController"];
}
return self.persistingViewController
}
I have a UITableView which loads through it's navigationController a new viewcontroller.
This code goes in the tableView:didSelectRowAtIndexPath method:
ConcertDetailViewController *detailVC = [[ConcertDetailViewController alloc] initWithNibName:#"ConcertDetailViewController" bundle:nil];
The UITableView has a model, I want to sent an element of this model to the newly created ViewController.
detailVC.aProd = [_prod objectAtIndex:indexPath.row];
When the value is set I want the detailVC to draw the data on the screen. I thought a custom setter, overwriting the one generated by #synthesize would work.
-(void)setaProd:(NSMutableDictionary *)aProd {
_aProd = aProd;
[self displayAProd];
}
displayAProd just takes the values in aProd and put's them on the screen, or rather I'm setting some value of an outlet , created in my nib file.
self.prodNameLbl.text = [_aProd objectForKey:#"name"];
Nothing special about this. But it just doesn't work. I figured out why, I think.
It's because the setter executes way faster then, loading the whole view into memory.
If I put self.prodNameLbl.text = #"something"; in the viewDidLoad method it does display the correct value in the label.
A quick workaround would be the see if _concerts has been set and from there call displayAProd. Here I'm doubting myself if it's a good way to load a view. What if the custom setter takes longer to execute the loading the view. The test to see if _concerts has been set will be false and nothing will be displayed. Or is that just impossible to happen ?
Or maybe there's a better pattern for loading views and passing data to them to be displayed.
Thanks in advanced, Jonas.
The problem is that when you load the view controller from the NIB, the IBOutlets will not be connected to your UILabel and other similar properties during the initWithNibName call.
You need to wait for viewDidLoad to be called on detailVC and call [self displayAProd] from there. At this point, the connections will have been made.
Do a quick test. Put a break point in your didSelectRowAtIndexPath method and, after initialising detailVC, check to see if prodNameLbl is null or not.
What I am doing:
I am implementing a navigation controller with 2 view Controllers, a root Controller (displayed at launch) and a table View Controller. I am creating a NSMutableArray in the table View Controller to store a list of contacts selected from the address book.
Issue:
When I click back to the root view controller from the table view controller, my NSMutable Array gets refreshed, so when I click to display my table view again, nothing is displayed. What is the best way to prevent this from happening? I instantiated my NSMutable Array as such in my table view controller
if (!personArray)
{
self.personArray = [[NSMutableArray alloc] init];
}
Any advise is greatly appreciated!
Thanks
Zhen
One way to do it is to make the person array a property of your root view controller or your app delegate, so that it's created (possibly read from a file) when the app launches and continues to live until the app terminates.
Another way is to leave it as is, but prevent your table view controller from being deallocated when the user goes back to the root view controller. Your root view controller code probably looks something like:
-(IBAction)showMeTheTable:(id)sender
{
MyTableViewController *mtvc = [[MyTableViewController alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:mtvc animated:YES];
[mtvc release];
}
You can change that so that the table view controller is created early in the root view controller's lifetime and retained, and then always push the same table view controller rather than creating a new one every time:
-(IBAction)showMeTheTable:(id)sender
{
[self.navigationController pushViewController:self.tableViewController animated:YES];
}
Your tableview is going to be deallocated when going back and then recreated when going forward again. Therefore it is not possible to keep it around in the tableview. Instead, you should create a new class that will not be destroyed during this process. Possibly you can store the array in your application delegate, but it would probably be better to create a new class specifically meant for cacheing data.
I have a rootController and 5 contentControllers, each as its own class.
I want to be able to call the next content controller from the current content controller. For example, if I'm currently showing contentController1, I want a way to show contentController2.
It'd be ideal if I could add a short method to every controllers' implementation file that passed the number of the controller to be called to the actual method that loads and shows the new controller.
For example:
#implementation ContentController1
- (int) loadNextController {
//take the 1 from ContentController1, add 1 to it, and pass it somewhere else
}
Then somewhere else (the root controller? the app delegate?) add the following method that then loads and shows the contentController based on the int sent from the (int) loadNextController method:
-(void) loadNextController: (int) nextController {
//init and show controller
}
If you could show me the code and, more importantly, where it goes, I would really appreciate it.
Trevor
It's not clear exactly how you want these view controllers to relate to each other. For example, you might want to push each one in turn onto a navigation controller's stack, so that the user always has the option of going back through the previous view controllers. Or, you might have a requirement that says that the user has to go through the five controllers in succession without the option to go back, in which case you'd probably set the window's rootViewController property to each controller when its time comes. Your decision will determine how you write the code that presents each controller's view. Read View Controller Programming Guide for details.
The design you describe above has each view controller deciding which view controller to present next. That's often appropriate, especially where the view controllers form a hierarchy. From your description, though, it seems that it might be helpful to concentrate all the knowledge about the sequence in which controllers are presented in one object. Make that object the delegate of each controller, and have that object (possibly the application delegate) determine the order of the controllers. As an (untested) example, you might add this to your app delegate:
-(void)application:(UIApplication*)app didFinishLaunchingWithOptions:(NSDictionary*)options
{
//...
self.controllers = [NSArray arrayWithObjects:
[[[ViewControllerA alloc] initWithNibName:nil bundle:nil] autorelease],
[[[ViewControllerB alloc] initWithNibName:nil bundle:nil] autorelease],
//...
[[[ViewControllerN alloc] initWithNibName:nil bundle:nil] autorelease]];
// Make self the delegate for each of the view controllers.
[self.controllers setValue:self forKey:#"delegate"];
[self.navigationController pushViewController:[self.controllers objectAtIndex:0] animated:NO];
}
-(void)viewControllerDidFinish:(UIViewController*)controller
{
NSUInteger index = [self.controllers indexOfObject:controller];
NSUInteger nextIndex = index + 1;
if (nextIndex < [self.controllers count]) {
[self.navigationController pushViewController:[self.controllers objectAtIndex:nextIndex animated:YES];
}
}
Then, whenever a view controller is ready to switch to the next controller, it can just call:
[self.delegate viewControllerDidFinish:self];
Again, the idea here is that the order of the view controllers in the array determines the order in which the controllers will be presented. I've used a nav controller here, but you don't have to. Also, you'll probably want to declare a protocol with your -viewControllerDidFinish:(UIViewController*)controller method, adopt that protocol in your app delegate (or whichever object manages the controllers), and have your controllers' delegate properties specify that protocol. That'll avoid any warnings about the delegate not implementing the method.