I have a page view controller in which i have four view controller.My question is when i go to second view controller i need to release memory of first,third and fourth view controller similarly for other three view controller.How to find whether it is allocating or deallocating memory for view controllers using instruments.I have tried using dealloc but the method is not getting called and i am using arc.I have my code as follows
-(NewsViewController *)news
{
if (!news) {
UIStoryboard *storyboard = self.storyboard;
news = [storyboard instantiateViewControllerWithIdentifier:#"News"];
}
return news;
}
-(PriceViewController *)price
{
if (!price) {
UIStoryboard *storyboard = self.storyboard;
price = [storyboard instantiateViewControllerWithIdentifier:#"price"];
}
return price;
}
- (CommentryViewController *)commentry {
if (!commentry) {
UIStoryboard *storyboard = self.storyboard;
commentry = [storyboard instantiateViewControllerWithIdentifier:#"commentry"];
}
return commentry;
}
-(WatchlistViewController *)watchlist {
if (!watchlist) {
UIStoryboard *storyboard = self.storyboard;
watchlist = [storyboard instantiateViewControllerWithIdentifier:#"watchlist"];
}
return watchlist;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
UIViewController *nextViewController = nil;
if (viewController == self.news) {
nextViewController = self.commentry;
}
else if(viewController == self.commentry) {
titleView.text = #"Commentry";
[titleView sizeToFit];
//self.title=#"Commentry";
nextViewController = self.price;
}
else if(viewController == self.price) {
titleView.text = #"Price";
[titleView sizeToFit];
nextViewController = self.watchlist;
}
else if(viewController == self.watchlist) {
titleView.text = #"WatchList";
[titleView sizeToFit];
}
return nextViewController;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
UIViewController *prevViewController = nil;
if (viewController==self.watchlist) {
titleView.text = #"WatchList";
prevViewController = self.price;
}
else if (viewController == self.price) {
titleView.text = #"Price";
prevViewController = self.commentry;
}
else if (viewController == self.commentry) {
titleView.text = #"Commentry";
prevViewController = self.news;
}
else if(viewController == self.news) {
titleView.text = #"News";
}
return prevViewController;
}
In a UIPageViewController if you call -viewControllers, you should see all the view controllers loaded in memory and if this array is filled with some you should not force a dealloc of them, because page view controller manages the memory pretty well removing unneeded view controllers from that array. That means that if no one else has ownership on them the will be dealloced and you should see a a decrease in the Memory Allocation tool.
View controllers has their own way to handle thier memory occupation(rules are always the same of the language, but there is an inner mechanism that helps to keep memory low) when in memory pressure, that is because view controllers do not occupy much memory by themselves, but the resources loaded with them do.
Memory management behavior in VC has changed a lot since iOS 6 (and I'm going to talk only about that), when the system is in need of memory, not visible VCs mark their views backing layer as dirty ready to be released, usually this frees up a lot of memory, but if in you view controller you create and take the ownership on a lot of objects, this memory can never be freed, unless you do in the method -didReceiveMemoryWarning, or you get rid of them when you have finished using them.
You should never call dealloc and in ARC is also forbidden, if you have a huge memory grow in memory, you need to check how you manage the memory of those objects.
I make an example to be clear, let's suppose that in you page VC you load other VCs with full screen images, if your load them using +imageNamed those images will finish in a cache and even if your VC is correctly dealloced the image occupation will remain.
Related
I am debugging legacy code which is always fun. The old code tried to mock the splitView delegate methods, causing all sorts of issues - mainly crashing: on a Plus device in Portrait, rotating to landscape caused the crash - if there was no detail view set, old code attempted to create one in a dodgy hack and it was just useless...
My app is UISplitViewController based, where I have a navigation stack in both master and detail sides of the splitView.
By reading though SO and using this example and was able to implement UISplitViewController delegate methods and everything is working correctly in regards to rotation, and showing the correct master/detail views when appropriate. Here is my implementation: (apologies for wall of code snippets)
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
if ([secondaryViewController isKindOfClass:[UINavigationController class]]
&& [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[AECourseHTMLTableViewController class]]
&& ([(AECourseHTMLTableViewController *)[(UINavigationController *)secondaryViewController topViewController] htmlContentEntry] == nil)) {
// If the detail controller doesn't have an item, display the primary view controller instead
return YES;
}
return NO;
}
And the other splitView delegate method - see comments in code for where I'm stuck.
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController {
// If detail view already exists
if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
if ([controller isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)controller visibleViewController] isKindOfClass:[AECourseHTMLTableViewController class]]) {
return controller;
}
}
}
// Create detail view
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"CourseHTMLNav"];
if ([navController.viewControllers.firstObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
AECourseHTMLTableViewController *courseViewController = navController.viewControllers.firstObject;
[self configureViewController:courseViewController entry:self.contentSection.sections[0] indexPath:courseViewController.currentIndexPath];
}
// Enable back button
UIViewController *controller = [navController visibleViewController];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
if (!self.splitViewController.isCollapsed) {
UINavigationController *navController = self.splitViewController.viewControllers.firstObject;
AEContentMenuTableViewController *contentMenuVC = navController.viewControllers.firstObject; // This controller needs to be master in Landscape
NSMutableArray<UIViewController *> *controllers = [navController.viewControllers mutableCopy]; // Contains 3 controllers, first needs removed
NSMutableArray *toDelete = [NSMutableArray new];
for (UIViewController *viewController in controllers)
if ([viewController isKindOfClass:[contentMenuVC class]] || [viewController isKindOfClass:[AECourseHTMLTableViewController class]]) {
[toDelete addObject:viewController]; // Remove first VC, so master should become AEContentMenuVC?
break;
}
// Remove the object
[controllers removeObjectsInArray:toDelete];
// Set viewControllers
navController.viewControllers = controllers;
}
return navController;
}
AECourseHTMLTableViewController has next/prev buttons to select the next row in the tableview of the tableview menu class class (AEContentMenuTableViewController). I have a delegate function which can tell me the current indexPath in which AECourseHTML... is using from AEContentMenu..., and when calling it, it selects the menu tableview row and instantiates a new AECourseHTML... and pushes it.
This is where I'm stuck. In Portrait, pressing next/prev is fine, it selects the correct row and works as expected. But once I rotate the device, both master and detail views show the detail view. I can press "Back" on the master view, and it takes me to the correct AEContentMenu... class. As noted in the code snippet comments, I need to remove a ViewController from the master stack (the first object actually), and AEContentMenu... should become the first object of that stack - so when rotating, that should be the master view.
Apologies for such a long post, I've been banging my head with this for weeks now and I want to include as much info as possible in this question. Thanks in advance.
I found a solution which works well for my use cases. It may not be the cleanest code, but I'm happy with what I've got.
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
remains unchanged. I have updated my splitViewController:separateSecondaryViewControllerFromPrimaryViewController: delegate method with the solution. Any feedback is welcome.
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController {
// If detail view already exists
if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
if ([controller isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)controller visibleViewController] isKindOfClass:[AECourseHTMLTableViewController class]]) {
return controller;
}
}
}
// Return CourseVC
UINavigationController *navController = splitViewController.viewControllers.firstObject;
UIViewController *viewController;
for (viewController in navController.viewControllers) {
if ([navController.viewControllers.lastObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
return viewController;
} else {
// Create detail view
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"CourseHTMLNav"];
if ([navController.viewControllers.firstObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
// Enable back button
UIViewController *controller = [navController visibleViewController];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
AECourseHTMLTableViewController *courseViewController = navController.viewControllers.firstObject;
// If next/prev has been tapped, configure current ContentHTML
if (self.currentContentHTML) {
[self configureViewController:courseViewController entry:self.currentContentHTML indexPath:courseViewController.currentIndexPath];
} else {
// Create new ContentHTML from first row of AEContentMenuVC
[self configureViewController:courseViewController entry:self.contentSection.sections[0] indexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}
return navController;
}
}
}
return navController;
}
Your top if statement should return nil. Since you were returning the nested navigation controller you were missing out on the default behaviour of popping the master navigation's top controller which is required so it can then be placed on the right.
The default behaviour will find that nested nav controller and pop it. However the reason you still need to search for it yourself is if it isn't there then you need to load the detail nav from the storyboard as you have done.
I have a tableviewcontroller that needs to drill down and show 3 layers of data. I have no problem drilling down but when I go back, the cells and table become empty. I'm currently using storyboard and I didn't have this problem when I was using nib. All I had to to was alloc and initWithNibName the same view and it would create another instance of the same TableView and I can go back and all the data would be there.
I've tried using segue but it's not working as I need the tableviewcontroller to segue back to itself if I'm drilling down. I've created my own method to push the view controller and pass data into the new instance of itself
- (void)drillDown{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
ExerciseTableViewController *tableView = [storyboard instantiateViewControllerWithIdentifier:#"ExerciseTableView"];
tableView.title = _detailTitle;
tableView.delegate = self;
tableView.level = _level;
tableView.currentSMID = _currentSMID;
tableView.currentMID = _currentMID;
tableView.muscleNameArray = _muscleNameArray;
if (_level == 2) {
tableView.submuscleNameArray = _submuscleNameArray;
}
[self.navigationController pushViewController:tableView animated:YES];
}
This is what I have in my viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *add = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addExercise:)];
self.navigationItem.rightBarButtonItem = add;
NSLog(#"level: %i, currentSMID: %i, currentMID: %i", _level, _currentSMID, _currentMID);
if (self.managedObjectContext_ == nil) {
self.managedObjectContext_ = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
if (_level == 1) {
_submuscleNameArray = [self submuscleGroup:_currentMID valueForKeyPath:#"submuscleGroup"];
_submuscleIDArray = [self submuscleGroup:_currentMID valueForKeyPath:#"submuscleID"];
} else if (_level == 2) {
//loading and sorting exercise list
_exerciseList = [self loadExercise:_currentMID sub:_currentSMID];
_exerciseListSorted = [self sortKeysByName];
} else {
[self loadMuscleGroup];
}
}
The table view is only populated when I put some of this code in the viewDidAppear but I can't differentiate between drilling down and going back up with the viewDidAppear.
Isn't the navigationController suppose to save my current view and push a new view and when I pop the new view, I go back to my current view?
It seems like everytime I pop the view, the previous view needs to load all over again and this is causing my previous views to become blank.
Any suggestions?
Storyboard attached
So I figured out what was the issue. I have a variable called _level and I use it to track which level of drill down I'm in. When passing this variable to the next level, I've replaced the current level with the next level number therefore when I come back to the previous view. The current level is the same as the next level which screwed things up.
I've solved it by creating a new variable called nextLevel and passing that down instead.
Very basic error on my part.
I have an app that is not working properly with state restoration. It previously did, but as I started moving away from the storyboard it stopped.
My app starts with a LoginViewController that is the starting view controller in my storyboard. If the login is successful, then it tries to add two FolderViewController to a navigation controller. This is so that the visible folder is one level deep already. This is done in the following code:
UINavigationController *foldersController = [[UINavigationController alloc] initWithNavigationBarClass:nil toolbarClass:nil];
foldersController.restorationIdentifier = #"FolderNavigationController";
FolderViewController *root = [storyboard instantiateViewControllerWithIdentifier:#"FolderView"];
root.folderId = 0;
FolderViewController *fvc = [storyboard instantiateViewControllerWithIdentifier:#"FolderView"];
fvc.folderId = 1;
[foldersController setViewControllers:#[root, fvc] animated:YES];
[self presentViewController:foldersController animated:YES completion:nil];
The FolderViewController has this awakeFromNib
- (void)awakeFromNib
{
[super awakeFromNib];
self.restorationClass = [self class]; // If we don't have this, then viewControllerWithRestorationIdentifierPath won't be called.
}
Within the storyboard the FolderViewController has a restorationIdentifier set. When I press the Home button, the app is suspended. My restoration calls in FolderViewController are being called:
// This is being called
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
[coder encodeInt64:self.folderId forKey:#"folderId"];
}
The problem now is when I try and restore. I stop the app in my debugger and then start it up again. This kicks off the restoration process.
First, my viewControllerWithRestorationIdentifierPath:coder: for my LoginViewController is called. This doesn't do much, and its use is optional. I've tried removing it and I don't have any ill effect.
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
LoginViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb)
{
vc = (LoginViewController *)[sb instantiateViewControllerWithIdentifier:#"LoginViewController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [LoginViewController class];
}
return vc;
}
Next, the viewControllerWithRestorationIdentifierPath:coder: for my FolderViewController is called:
// This is being called
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
FolderViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb)
{
vc = (FolderViewController *)[sb instantiateViewControllerWithIdentifier:#"FolderView"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [FolderViewController class];
vc.folderId = [coder decodeInt32ForKey:#"folderId"];
}
return vc;
}
I've previously had a decodeRestorableStateWithCoder: as well, and it did get called. However, since it's setup in the viewControllerWithRestorationIdentifierPath:coder:, it wasn't necessary to keep it around.
All of these things are being called the appropriate number of times. But in the end, the only view controller that is displayed in the LoginViewController. Why are my FolderViewControllers not being displayed. Is there a missing setup that I need to do in my LoginViewController to attach the view controllers that I manually added previously?
Edit
After reading http://aplus.rs/2013/state-restoration-for-modal-view-controllers/ which seemed relevant, I added the following code to the App delegate:
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
if ([identifierComponents.lastObject isEqualToString:#"FolderNavigationController"])
{
UINavigationController *nc = [[UINavigationController alloc] init];
nc.restorationIdentifier = #"FolderNavigationController";
return nc;
}
else
return nil;
}
I think the App is happier now, but it still isn't restoring properly. Now I get this error in my log:
Warning: Attempt to present <UINavigationController: 0xbaacf50> on <LoginViewController: 0xbaa1260> whose view is not in the window hierarchy!
It's something different.
I had similar issue. My stack of view controllers was pretty simple: root view with table view -> some details view -> some edit details view. No navigation views, views are modal. And it did not work.
Turned out, the issue was that, viewControllerWithRestorationIdentifierPath() in the root view controller should look like this:
+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
NSDictionary *myRestorableObj = [coder decodeObjectForKey:#"myRestorableObj"];
// #todo Add more sanity checks for internal structures of # myRestorableObj here
if (myRestorableObj == nil)
return nil;
return [[UIApplication sharedApplication] delegate].window.rootViewController;
}
Instantiating new root view controller is wrong. It creates new stack of view controllers that the stored children view controllers down the stack do not belong to.
All the other children view controller should be created as usually, with the following code:
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"MyChildViewController"];
Hope this helps.
You need to assign different restoration identifiers to different FolderViewController objects.
For example:
FolderViewController *folderViewController1 = // initialize object ;
FolderViewController *folderViewController2 = // initialize object ;
folderViewController1. restorationIdentifier = #"folderViewController1";
folderViewController2. restorationIdentifier = #"folderViewController2";
I tried the code above and it worked fine.
I have an app with a storyboard. I am using several View Controller with segues to choose from a table view depending on the item selected. I want to have a page view controller in which the pages will be one or more view controller from the storyboard, even repeating them. I am trying to init the Page View Controller like this:
....
self.dataSource=self;
UIViewController *initialViewController =[self viewControllerAtIndex:current];
NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
[self setViewControllers:viewControllers direction:UIPageViewControllerNavigationOrientationHorizontal animated:NO completion:nil];
....
- (UIViewController *)viewControllerAtIndex:(NSUInteger)index
{
if ((descriptions.count == 0) ||
(index >= descriptions.count)) {
return nil;
}
current=index;
// Create a new view controller and pass suitable data.
NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0];
Description *description=[DBCompany getDescriptionById:language descriptionId:[[descriptions objectAtIndex:index] integerValue]];
UIViewController *viewController=nil;
if(description.frame==100){
viewController=[[Company100ViewController alloc] init];
((Company100ViewController*)viewController).companyId = companyId;
}
return viewController;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
if ((descriptions.count == 0) ||
(current-1 < 0)) {
return nil;
}
return [self viewControllerAtIndex:current-1];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
if ((descriptions.count == 0) ||
(current >= descriptions.count)) {
return nil;
}
return [self viewControllerAtIndex:current+1];
}
However the viewcontroller appears in black. I think is because I am adding the UIviewcontroller class but not connecting it to any view, XIB, in my case with the storyboard.
How can I use the different view controller from the storyboard programmatically to use in a page view controller?
If you do viewController=[[Company100ViewController alloc] init] then yes this not a controller that is associated to a storyboard or an XIB. To load the controller with an XIB you have to do the following:
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
Company100ViewController * vc = (Company100ViewController *)[sb instantiateViewControllerWithIdentifier:#"vc-identifier"];
In the storyboard make sure you set the view controller's id to vc-identifier; whatever identifier you choose.
Swift 3
let sb = UIStoryboard(name: "MainStoryboard", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "vc-identifier")
Swift
In case you want to init from a UIViewController object.
guard let vc = storyboard?.instantiateViewController(withIdentifier: "nameVC") else {
return
}
// add to current view
view.addSubview(vc.view)
UPDATE: I made a mistake in my debugging - this question is not relavent - please see comment below.
Note: I am using Automated Reference Counting
When my app starts - I present a view controller inside a UINavigationController with presentViewController:animated:completion. That view controller loads a second view controller on to the navigation stack. The second view controller uses [self.presentingViewController dismissViewControllerAnimated:YES completion:nil] to dismiss itself. My issue, is that neither dealloc nor viewDidUnload are ever called in the first view controller. However, with instruments, I can see that the view controller is no longer allocated once the presented view controllers are dismissed. The code that presents the first view controller is
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// check if our context has any accounts
if( [self.accounts count] == 0 )
{
// Display the Add Account View Controller
MySettingsViewController *settingsVC = [[MySettingsViewController alloc] initWithNibName:#"MySettingsViewController" bundle:nil];
settingsVC.appContext = self.appContext;
UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:settingsVC];
navVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
// Display the Add Account View Controller
}
else
{
navVC.modalPresentationStyle = UIModalPresentationFormSheet;
}
[self presentViewController:navVC animated:YES completion:nil];
}
}
So, I do not have any references to settingsVC that should be sticking around but I do not know why my dealloc is not being called. Any help would be great.
They don't get called because you haven't correctly released your view controller.
You allocate both settingsVC and navVC with alloc and thus get owning references to both that you must later release which you didn't do.
You can do it like this:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// check if our context has any accounts
if( [self.accounts count] == 0 )
{
// Display the Add Account View Controller
MySettingsViewController *settingsVC = [[MySettingsViewController alloc] initWithNibName:#"MySettingsViewController" bundle:nil];
settingsVC.appContext = self.appContext;
UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:settingsVC];
navVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
// At this points, "settingsVC" is additionally retained by the navigation controller,
// so we can release it now.
[settingsVC release];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
// Display the Add Account View Controller
}
else
{
navVC.modalPresentationStyle = UIModalPresentationFormSheet;
}
[self presentViewController:navVC animated:YES completion:nil];
// At this point "navVC" is retained by UIKit, so we can release it as well.
[navVC release];
}
}
An alternative would have been to autorelease both right away.