ADMagazine/Classes/MagazineViewController.m

/*
    File: MagazineViewController.m
Abstract: Main view controller
 Version: 1.2
 
Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms.  If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
 
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple.  Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
 
The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
 
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
 
Copyright (C) 2012 Apple Inc. All Rights Reserved.
 
*/
 
#import "MagazineViewController.h"
#import "MagazinePage.h"
 
@interface MagazineViewController()
 
// Interstitial Management
- (void)cycleInterstitial;
- (void)insertInterstitialAtIndex:(NSInteger)indx;
- (void)removeInterstitial;
 
// View Layout
- (void)layout;
- (void)preparePages;
 
@end
 
#pragma mark -
@implementation MagazineViewController
 
@synthesize scrollView;
 
#pragma mark -
#pragma mark Lifetime Management
 
// Load the data needed to create our magazine pages
// We load the pages once, but each page manages memory usage
// with assistance from this view controller.
- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self != nil) {
        pages = [[NSMutableArray alloc] init];
        NSArray *paths = [[NSBundle mainBundle] pathsForResourcesOfType:@"jpg" inDirectory:@"bunnies"];
        [paths enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
            // Use imageWithContentsOfFile to avoid placing the image in the image cache
            MagazinePage *page = [[MagazinePage alloc] initWithContentsOfFile:obj];
            [pages addObject:page];
            [page release];
        }];
        // Setup the interstitial. Set the interstitialIndex to -1 to indicate that we haven't placed it yet.
        [self cycleInterstitial];
        interstitialIndex = -1;
        
        // Setup the pageIndex. Since its used often, keep the pageCount around too.
        pageIndex = 0;
        pageCount = [pages count];
    }
    return self;
}
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Run layout for the magazine pages and prepare those pages that will be needed soon.
    [self layout];
    [self preparePages];
}
 
- (void)didReceiveMemoryWarning
{
    // Memory warning code here happens in two steps.
    // The first step is here, where we call purge on all pages
    // This will reduce memory usage on each page to its current minimum.
    // (continued in -viewDidUnload).
    for (MagazinePage *page in pages) {
        [page purge];
    }
    [super didReceiveMemoryWarning];
}
 
- (void)viewDidUnload
{
    // Clear the IBOutlet to the scroll view to release it.
    self.scrollView = nil;
    // (continued from -didReceiveMemoryWarning)
    // Here we go on to unprepare each page in the magazine.
    // Since we aren't displaying them to the user, they can easy go away right now.
    // If we get another memory warning afterwards, then the pages that we've unprepared
    // will be further reduced in memory usage, but if we reload instead then
    // we'll have the images already available for use.
    for (MagazinePage *page in pages) {
        [page unprepare];
    }
}
 
- (void)dealloc
{
    interstitial.delegate = nil;
    [scrollView release];
    [pages release];
    [interstitial release];
    [super dealloc];
}
 
#pragma mark -
#pragma mark Interstitial Management
 
- (void)cycleInterstitial
{
    // Release the old interstial and create a new one.
    interstitial.delegate = nil;
    [interstitial release];
    interstitial = [[ADInterstitialAd alloc] init];
    interstitial.delegate = self;
}
 
- (void)insertInterstitialAtIndex:(NSInteger)indx
{
    // When we insert the interstitial, we also need to relayout and reprepare the relevant magazine pages.
    
    // First try to generate the interstitial page.
    // If we are able to successfully insert the interstitial, then we
    // can do layout and add it into the pages array.
    interstitialIndex = indx;
    CGRect interstitialFrame = scrollView.bounds;
    interstitialFrame.origin = CGPointMake(interstitialFrame.size.width * indx, 0);
    MagazinePage *page = [[MagazinePage alloc] initWithInterstitialFrame:interstitialFrame];
    UIView *view = page.pageView;
    [scrollView addSubview:view]; // the view passed to -presentInView must already be in a view controller owned view hierarchy, so we place it in the scroll view now.
    if ([interstitial presentInView:view]) {
        // Success, insert the page and do layout.
        [pages insertObject:page atIndex:interstitialIndex];
        pageCount = [pages count];
        [self layout];
        [self preparePages];
    } else {
        // Failure, rip it all out cycle the interstitial.
        NSLog(@"failed to present interstitial in container %@", view);
        [view removeFromSuperview];
        [self cycleInterstitial]; // TODO: Reconsider this... esp since it probably won't make a difference.
    }
    [page release];
}
 
- (void)removeInterstitial
{
    if (interstitialIndex != -1) {
        if (interstitialIndex == pageIndex) {
            // the user is looking at the interstitial now.
            if (pageIndex == pageCount - 1) {
                // the interstitial ended up as the last page in the view.
                // In this case, we need to slip the pageIndex back by one.
                --pageIndex;
            }
            [pages removeObjectAtIndex:interstitialIndex];
            pageCount = [pages count];
            [UIView animateWithDuration:0.2 animations:^{
                [self layout];
                [self preparePages];
            }];
        } else {
            // The user isn't looking, so we can just quietly remove the interstitial
            [pages removeObjectAtIndex:interstitialIndex];
            pageCount = [pages count];
            [self layout];
            [self preparePages];
        }
        // If we're going to animate away, then we want to nil the delegate now.
        interstitial.delegate = nil;
        [self cycleInterstitial];
        interstitialIndex = -1;
    }
}
 
#pragma mark ADInterstitialViewDelegate methods
 
// The application should implement this method so that when the user dismisses the interstitial via
// the top left corner dismiss button (which will hide the content of the interstitial) the
// application can then move the view offscreen.
- (void)interstitialAdDidUnload:(ADInterstitialAd *)interstitialAd
{
    [self removeInterstitial];
}
 
// This method is invoked each time a interstitial loads a new advertisement. 
// The delegate should implement this method so that it knows when the interstitial is ready to be displayed.
- (void)interstitialAdDidLoad:(ADInterstitialAd *)interstitialAd
{
    [self removeInterstitial];
    if (interstitialIndex == -1) {
        [self insertInterstitialAtIndex:pageIndex+1];
    }
}
 
// This method will be invoked when an error has occurred attempting to get advertisement content. 
// The ADError enum lists the possible error codes.
- (void)interstitialAd:(ADInterstitialAd *)interstitialAd didFailWithError:(NSError *)error
{
    NSLog(@"interstitialAd <%@> recieved error <%@>", interstitialAd, error);
}
 
// This message will be sent when the user taps on the interstitial and some action is to be taken.
// The delegate may return NO to block the action from taking place, but this
// should be avoided if possible because most advertisements pay significantly more when 
// the action takes place and, over the longer term, repeatedly blocking actions will 
// decrease the ad inventory available to the application. Applications should reduce
// their own activity while the advertisement's action executes.
- (BOOL)interstitialAdActionShouldBegin:(ADInterstitialAd *)interstitialAd willLeaveApplication:(BOOL)willLeave
{
    return YES;
}
 
// This message is sent when a modal action has completed and control is returned to the application. 
// Games, media playback, and other activities that were paused in response to the beginning
// of the action should resume at this point.
- (void)interstitialAdActionDidFinish:(ADInterstitialAd *)interstitialAd
{
}
 
#pragma mark -
#pragma mark View Layout
 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}
 
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    pendingOrientationChange = YES;
}
 
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [self layout];
}
 
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    pendingOrientationChange = NO;
}
 
- (void)viewDidLayoutSubviews
{
    [self layout];
}
 
// Layout is relatively simple, just iterate through the magazine pages and place each one back to back in the scroll view.
// At the same time, setup the scroll view's contentSize and contentOffset to display the current page.
- (void)layout
{
    CGRect placementRect = scrollView.bounds;
    scrollView.contentSize = CGSizeMake(placementRect.size.width * pageCount, placementRect.size.height);
    scrollView.contentOffset = CGPointMake(placementRect.size.width * pageIndex, 0.0);
    for (NSInteger i = 0; i < pageCount; ++i) {
        MagazinePage *page = [pages objectAtIndex:i];
        UIView *pageView = page.pageView;
        placementRect.origin.x = placementRect.size.width * i;
        pageView.frame = placementRect;
        [scrollView addSubview:pageView];
    }
}
 
// Preparation ensures that if the user pages to the previous/next page that the content for that page is ready to go.
// This method is called seperately of layout because there are times when the layout needs to change but available pages does not
// and vice versa.
// This version only prepares the page that the user sees and the page to the immediate left & right of that page.
- (void)preparePages
{
    NSInteger i = 0;
    for (; i < pageIndex - 1; ++i) {
        [[pages objectAtIndex:i] unprepare];
    }
    for (; (i <= pageIndex + 1) && (i < pageCount); ++i) {
        [[pages objectAtIndex:i] prepare];
    }
    for (; i < pageCount; ++i) {
        [[pages objectAtIndex:i] unprepare];
    }
}
 
#pragma mark -
#pragma mark Scrolling Support
 
- (void)scrollViewDidScroll:(UIScrollView *)sv
{
    // Because the orientation change may shrink the scroll view, which may send this message.
    // Basically ignore the message until the orientation change completes, and trust -layout
    // to place us correctly.
    if (pendingOrientationChange) {
        return;
    }
    
    // Infer the desired page from the new contentOffset.
    CGFloat offsetX = scrollView.contentOffset.x;
    CGFloat width = scrollView.bounds.size.width;
    NSInteger tmpIndex = trunc(offsetX / width);
    if (tmpIndex != pageIndex) {
        pageIndex = tmpIndex;
        [self preparePages];
    }
}
 
- (IBAction)nextImage
{
    if (pageIndex < pageCount - 1) {
        ++pageIndex;
    }
    [scrollView setContentOffset:CGPointMake(scrollView.bounds.size.width * pageIndex, 0.0) animated:YES];
    [self preparePages];
}
 
- (IBAction)prevImage
{
    if (pageIndex > 0) {
        --pageIndex;
    }
    [scrollView setContentOffset:CGPointMake(scrollView.bounds.size.width * pageIndex, 0.0) animated:YES];
    [self preparePages];
}
 
@end