Search code examples
iphoneiosiadios-4.2opengl-es

iAd cannot be clicked in programmatically created window - bannerViewActionShouldBegin not called?


In an iOS 4.2 application, I've programmatically created a window, view controller, UIView and two subviews: an EAGLView and an ADBannerView. My code is a combination of a standard EAGLView and Apple's BasicAdBanner code.

I'm able to see iAd banners, but am not able to "click" on them - on device or in simulator. The view controller implements ADBannerDelegate, and receives bannerViewDidLoadAd etc messages, but does not ever receive a bannerViewActionShouldBegin message.

The behaviour is not changed by replacing the EAGL view with a UITextView or only having the ADBannerView, so I'm assuming that my problem lies with the programmatic creation of the window or view controller. Any help would be appreciated.

In addition, my views don't rotate/resize automatically like the ad banner sample does, but that's a less pressing issue.

Code Follows-

AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
    // Create the window programatically:
    window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

    // let's enable multitouch and user interaction
    window.userInteractionEnabled=YES;
    window.multipleTouchEnabled=YES;
    window.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    window.autoresizesSubviews = YES;

    controller = [GLViewController alloc];
    window.rootViewController = controller;
    [window addSubview:controller.view];
    glView = [controller.glView retain];
    [window makeKeyAndVisible];

    return YES;
}

ViewController:

- (void)loadView 
{
    self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
}

-(void)viewDidLoad
{
    [super viewDidLoad];

    // I'd like to get device orientation notifications.
    //[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 
    //[[NSNotificationCenter defaultCenter] addObserver:self                                        
    //                                       selector:@selector(didRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];

    // the GL VIEW I want.
    glView = [[EAGLView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
    glView.userInteractionEnabled=NO;
    [self.view addSubview:glView];

    // testing with a textview
    //content = [[UITextView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    //content.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
    //content.userInteractionEnabled=NO;
    //[self.view addSubview:content];

    if (self.banner==NULL)
        [self createADBannerView];

    [self layoutForCurrentOrientation:NO];
}

-(void)createADBannerView
{

    // --- WARNING ---
    // If you are planning on creating banner views at runtime in order to support iOS targets that don't support the iAd framework
    // then you will need to modify this method to do runtime checks for the symbols provided by the iAd framework
    // and you will need to weaklink iAd.framework in your project's target settings.
    // See the iPad Programming Guide, Creating a Universal Application for more information.
    // http://developer.apple.com/iphone/library/documentation/general/conceptual/iPadProgrammingGuide/Introduction/Introduction.html
    // --- WARNING ---

    // Depending on our orientation when this method is called, we set our initial content size.
    // If you only support portrait or landscape orientations, then you can remove this check and
    // select either ADBannerContentSizeIdentifierPortrait (if portrait only) or ADBannerContentSizeIdentifierLandscape (if landscape only).
    NSString *contentSize;
    if (&ADBannerContentSizeIdentifierPortrait != nil)
    {
        contentSize = UIInterfaceOrientationIsPortrait(self.interfaceOrientation) 
        ? ADBannerContentSizeIdentifierPortrait : ADBannerContentSizeIdentifierLandscape;
    }
    else
    {
        // user the older sizes 
        contentSize = UIInterfaceOrientationIsPortrait(self.interfaceOrientation) 
        ? ADBannerContentSizeIdentifier320x50 : ADBannerContentSizeIdentifier480x32;
    }

    // Calculate the intial location for the banner.
    // We want this banner to be at the bottom of the view controller, but placed
    // offscreen to ensure that the user won't see the banner until its ready.
    // We'll be informed when we have an ad to show because -bannerViewDidLoadAd: will be called.
    CGRect frame;
    frame.size = [ADBannerView sizeFromBannerContentSizeIdentifier:contentSize];
    frame.origin = CGPointMake(0.0f, 0.0f);//CGRectGetMaxY(self.view.bounds)); // this can't be called until the view exists!

    // Now to create and configure the banner view
    ADBannerView *bannerView = [[ADBannerView alloc] initWithFrame:frame];
    // Set the delegate to self, so that we are notified of ad responses.
    bannerView.delegate = self;
    bannerView.hidden = YES;
    // Set the autoresizing mask so that the banner is pinned to the bottom
    bannerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
    // Since we support all orientations in this view controller, support portrait and landscape content sizes.
    // If you only supported landscape or portrait, you could remove the other from this set.

    bannerView.requiredContentSizeIdentifiers = [NSSet setWithObjects:ADBannerContentSizeIdentifierPortrait, ADBannerContentSizeIdentifierLandscape, nil];
    //support for 4.0 and 4.1: //(&ADBannerContentSizeIdentifierPortrait != nil) ?[NSSet setWithObjects:ADBannerContentSizeIdentifierPortrait, ADBannerContentSizeIdentifierLandscape, nil]:[NSSet setWithObjects:ADBannerContentSizeIdentifier320x50, ADBannerContentSizeIdentifier480x32, nil];

    // At this point the ad banner is now be visible and looking for an ad.
    self.banner = bannerView;
    [self.view addSubview:bannerView];
    [bannerView release];
}


-(void)layoutForCurrentOrientation:(BOOL)animated
{

    //CGFloat animationDuration = animated ? 0.2f : 0.0f;
    // by default content consumes the entire view area
    CGRect contentFrame = self.view.bounds;
    // the banner still needs to be adjusted further, but this is a reasonable starting point
    // the y value will need to be adjusted by the banner height to get the final position
    CGPoint bannerOrigin = CGPointMake(CGRectGetMinX(contentFrame), CGRectGetMaxY(contentFrame));
    CGFloat bannerHeight = 0.0f;

    // First, setup the banner's content size and adjustment based on the current orientation
    if(UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
//    if(UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation]))
        banner.currentContentSizeIdentifier = ADBannerContentSizeIdentifierLandscape;//(&ADBannerContentSizeIdentifierLandscape != nil) ? ADBannerContentSizeIdentifierLandscape : ADBannerContentSizeIdentifier480x32;
    else
        banner.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;//(&ADBannerContentSizeIdentifierPortrait != nil) ? ADBannerContentSizeIdentifierPortrait : ADBannerContentSizeIdentifier320x50; 
    bannerHeight = banner.bounds.size.height; 

    // Depending on if the banner has been loaded, we adjust the content frame and banner location
    // to accomodate the ad being on or off screen.
    // This layout is for an ad at the bottom of the view.
    if(banner.bannerLoaded)
    {
        // HMM this is going to be a problem for a GLView..
        banner.hidden = NO;
        contentFrame.size.height -= bannerHeight;
        bannerOrigin.y -= bannerHeight;
        NSLog(@"Banner will be %f x %f, content resized to %f x %f\n", banner.frame.size.width,bannerHeight,contentFrame.size.width,contentFrame.size.height);
    }
    else
    {
        banner.hidden = YES;
        //bannerOrigin.y += bannerHeight;
        NSLog(@"Banner will be offscreen, content resized to %f x %f\n", contentFrame.size.width,contentFrame.size.height);

    }

    //EDIT: contrary to my OP, this UIViewAnimateWithDuration code was not commented out!
    // And finally animate the changes, running layout for the content view if required.
    [UIView animateWithDuration:animationDuration
                     animations:^{
                         glView.frame = contentFrame;
                         [glView layoutIfNeeded];
                         banner.frame = CGRectMake(bannerOrigin.x, bannerOrigin.y, banner.frame.size.width, banner.frame.size.height);
                     }];
}

ADBannerViewDelegate:

-(void)bannerViewDidLoadAd:(ADBannerView *)banner
{
    [self layoutForCurrentOrientation:YES];
}

-(void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
    NSLog(@"Failed to receive and ad: %s %s %s\n",error.localizedFailureReason.UTF8String, error.localizedDescription.UTF8String, error.localizedRecoverySuggestion.UTF8String);

    [self layoutForCurrentOrientation:YES];
}

-(BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
{
    // this indicates that the banner has been clicked.. confirm?
    NSLog(@"banner clicked: will %s application\n",willLeave?"leave":"cover");

    return YES;
}

Solution

  • Clarification: It seems that problem is related to the code attempting to set or animate the content frame changes at the end of -layoutForCorrentOrientation(BOOL). if I only set the banner frame's location, I'm able to click the banner, but if I also attempt to set the glView's frame and recreate the surface, the banner does not respond to input.

    //EDIT: contrary to my OP, this UIViewAnimateWithDuration code was not commented out!
    // And finally animate the changes, running layout for the content view if required.
    //[UIView animateWithDuration:animationDuration
    //                 animations:^{
    //                     glView.frame = contentFrame;
    //                     [glView layoutIfNeeded];
                         banner.frame = CGRectMake(bannerOrigin.x, bannerOrigin.y, banner.frame.size.width, banner.frame.size.height);
    //                 }];
    

    APENDED: In the end, the problem was that I have a top level UIView (controller.view) which has the glView and the banner as sub-views. Turns out I needed to call

    [self.view setNeedsLayout]
    

    to get this top level view to update its bounds so that it allowed button presses down to the banner! (credit goes to Ephraim's answer to a post on UIButtons not responding after rotations