Releasing unused pages in UIPageViewController

I am using a separate .h, .m, and .xib files for each UIViewController based page of a UIPageViewController based picture book. Each page is loaded with animations, music, etc. and takes about 4MB of memory. In Instruments, the free memory drops down about 4MB as each page is loaded. This memory is never released as the pages are turned. It eventually gives memory warnings. UIPageViewController seems to keep each page it instantiates in memory and won’t unload it. So when pages are turned fast, the app crashes.

I would like to be able to unload all pages except the 3 needed by UIPageViewController – the previous, current, and next pages. How can I unload undesired pages since they were instantiated by UIPageViewController.

  • Objective-C: NSLinguisticTagger “new york” vs “New York”
  • iOS 8: UINavigationController hide back button
  • IOS - best way to queue requests to be sent when connection is reestablished
  • iOS White to Transparent Gradient Layer is Gray
  • IBoutlets nil in swift when using storyboards
  • UIWebView not loading JavaScript properly - Embedded Facebook post
  • Below is the Array of the pages that UIPageViewController pulls from. All of the pages (Page1, Page2, etc.) basically just load image files, provide basic animation, and have music.

        //ARRAY OF PAGES    
    pageArray = [[NSArray alloc] initWithObjects:
            (id)[[Page1 alloc] initWithNibName:nil bundle:nil],
                [[Page2 alloc] initWithNibName:nil bundle:nil],    
                [[Page3 alloc] initWithNibName:nil bundle:nil],    
                [[Page4 alloc] initWithNibName:nil bundle:nil],    
                [[Page5 alloc] initWithNibName:nil bundle:nil],    
                [[Page6 alloc] initWithNibName:nil bundle:nil], 
                [[Page7 alloc] initWithNibName:nil bundle:nil],
                [[Page8 alloc] initWithNibName:nil bundle:nil],
                 // continues all the way up to page 47
                 [[Page47 alloc] initWithNibName:nil bundle:nil],
                 nil];
    

    I’ve left out the standard initialization for UIPageViewController. It uses “nextPageNumber” to pull the right page from the pageArray above to create a new page object.

    -(void)turnPageForward{
    
    [pageController setViewControllers:[NSArray arrayWithObject:[pageArray objectAtIndex:nextPageNumber]]
                             direction:UIPageViewControllerNavigationDirectionForward
                              animated:YES completion:^(BOOL finished){
                              }];
    

    }

    I have tried creating an object “pageIndex” (see below) that is set to nil after providing it to the pageController. It didn’t work. The page still took up memory well after the pages had advanced.

    //PROGRAM PAGE FORWARD
    

    -(void)turnPageForward{

    UIViewController * pageIndex =[pageArray objectAtIndex:nextPageNumber];  //nextPageNumber is the next page to load
    
    [pageController setViewControllers:[NSArray arrayWithObject:pageIndex]
                             direction:UIPageViewControllerNavigationDirectionForward 
                              animated:YES completion:^(BOOL finished){
                              }];                                  
    pageIndex = nil;                            
    

    }

    I’ve looked through stackoverflow for posts using the same way of supplying pages to UIPageViewController, but haven’t found anything close. The closest was “ARC not releasing memory when going “back” in navigation controller” but doesn’t set the view controllers the same way.

    I’ve tried to set the undesired pages to nil so ARC can remove them with no luck. Any suggestions or alternate paths I should try? I like the page curl effect and have not been able to find a good one elsewhere that does horizontal page curls.

    Thanks! Eric

    3 Solutions Collect From Internet About “Releasing unused pages in UIPageViewController”

    “UIPageViewController seems to keep each page it instantiates in memory”

    No, you’re doing that by instantiating all those “pages” (controllers), and putting them into an array (the memory jumps as you turn the page because the controller’s view is not actually loaded until you display it, even though the controller has been instantiated. But once you’ve done that, your controller retains its view and the array retains the controller). You just need to keep some kind of count of which page you’re on, and in the implementation of viewControllerBeforeViewController: and viewControllerAfterViewController:, instantiate a page there. When you go away from that page, its controller will be dealloc’d. If the loading is slow, you might need to keep an array that has the current page and the ones before and after — UIPageViewController does do that if you have it set to scroll instead of page curl for the transition.

    I made a simple test app like this:

    @implementation ViewController {
        int count;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        count = 1;
        self.pager = [[UIPageViewController alloc]initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationDirectionForward options:nil];
        self.pager.dataSource = self;
        self.pager.delegate = self;
        Page1 *first = [[Page1 alloc] initWithNibName:@"Page1" bundle:nil];
        [self.pager setViewControllers:@[first] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
        [self addChildViewController:self.pager];
        [self.view addSubview:self.pager.view];
        [self.pager didMoveToParentViewController:self];
    }
    
    
    -(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
        if (count > 1) {
            NSString *nibName = [@"Page" stringByAppendingFormat:@"%d",count-1];
            UIViewController *prev = [[NSClassFromString(nibName) alloc] initWithNibName:nibName bundle:nil];
            count -= 1;
            return prev;
        }
        return nil;
    }
    
    -(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
        if (count < 3) {
            NSString *nibName = [@"Page" stringByAppendingFormat:@"%d",count+1];
            UIViewController *next = [[NSClassFromString(nibName) alloc] initWithNibName:nibName bundle:nil];
            count += 1;
            return next;
        }
        return nil;
    }
    

    My example only has 3 pages, but it illustrates one way to do this. I put logs in the dealloc methods of the 3 controllers, and they were called when I navigated away from them.

    I know this question is a bit older, but maybe my approach can help some people facing the same issue when not using ARC.

    I think the best way to release unused pages is in the didFinishAnimation method of the UIPageViewController. You get the 1 or 2 previous view controllers there and you can easily release them. I do it like this:

    -(void)pageViewController:(UIPageViewController *)thePageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
    
        if(completed) {
            for (UIViewController *viewController in previousViewControllers) {
                 [viewController release];
            }
         }
    }
    

    It is important that you only release them if the action was completed because otherwise with the next page change you would try to access released pages.

    Hope this helps!

    I encounter this problem this time.
    And after an hour thinking,I had find a solution.

    This is my .h file

    #import <UIKit/UIKit.h>
    
    @interface BKContentViewController : UIPageViewController
    
    @end
    

    And my .m file and viewDidload method

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view from its nib.
    
        self.delegate = self;
        self.dataSource = self;
    
        [self setViewControllers:[NSArray arrayWithObject:detailContent]
                       direction:UIPageViewControllerNavigationDirectionForward
                        animated:NO
                      completion:NULL];
    }
    

    And the most important.

    #pragma mark - UIPageViewControllerDataSource UIPageViewControllerDelegate
    
    - (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
        if (completed) {
            [self setViewControllers:[NSArray arrayWithObject:detailContent]
                           direction:UIPageViewControllerNavigationDirectionForward
                            animated:NO
                          completion:NULL];
        }
    
    }
    
    - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
    viewControllerAfterViewController:(UIViewController *)viewController{
    
    
        if (!detailRight) {
            detailRight = [[DetailContent alloc] init];
            [detailRight setShouldReturn:YES];
            [detailRight setPath:fPath withFileName:fName];
        }
        return detailRight;
    }
    
    - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
    viewControllerBeforeViewController:(UIViewController *)viewController{
    
        if (!detailLeft) {
            detailLeft = [[DetailContent alloc] init];
            [detailLeft setShouldReturn:YES];
            [detailLeft setPath:fPath withFileName:fName];
        }
    
        return detailLeft;
    }
    
    - (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
    spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation{
        UIViewController *currentViewController = [self.viewControllers objectAtIndex:0];
        NSArray *viewControllers = [NSArray arrayWithObject:currentViewController];
        [self setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
    
        self.doubleSided = NO;
        return UIPageViewControllerSpineLocationMin;
    }
    

    After this, I have only three viewContrller and the memory will not increase by scroll again and again.
    Hope this will help you.