user2191247
user2191247

Reputation:

Incorrect layout after rotation if banner ads enabled outside of viewDidLoad

I am having trouble with an iOS 8 app. My app needs to get information from a web server to determine if ads should be played or not. This can take a while, so the code in viewDidLoad doesn't wait for it and instead calls setCanDisplayBannerAds:NO. Then later, once the information comes in, setCanDisplayBannerAds:YES is called outside of viewDidLoad.

I have determined that I need to call setCanDisplayBannerAds:NO inside viewDidLoad otherwise setCanDisplayBannerAds:YES doesn't properly invoke the ads and generates lots of internal exceptions.

The issue is that once ads are enabled outside of viewdidLoad, the layout messes up. I've illustrated with two images below. The top one is "before", the bottom "after", as in after Show Ads is tapped.

Note in the "before" image that Show Ads and Just a Label both appear in their respective corners after rotation. In the "after" image, the landscape orientation is no longer correct. It maintains the portrait bounds with Just a Label and the banner ad cut off.

I have submitted a bug report to Apple and provided a sample project accessible on DropBox as ID19658866.zip. Feel free to download it and try it out.

If anyone can provide some insight, and maybe a solution where I don't have to change the logical sequence, i.e. not showing ads until after viewDidLoad is completed, that would be much appreciated.

before *Show Ads* is tapped after *Show Ads* is tapped

Upvotes: 3

Views: 361

Answers (4)

PMARINA
PMARINA

Reputation: 304

Frankly, Yimin Rong has the best solution out of all the available. However, if this is a premium v. standard account type of thing, wouldn't it be best if you were to keep a variable locally stored and have the user hit the "restore purchase" button. This way, even if the user were offline, you could display ads (if they were stored locally) and when they came online, you could collect your profits from the advertisers. Best of luck with your application!

Upvotes: 0

cjwirth
cjwirth

Reputation: 2013

To be honest, Yimin Rong's answer will probably work, at least with the current SDK. But it feels a little strange. It doesn't really explain why it would work.

Looking at the documentation for setCanDisplayBannerAds: we see:

It's important to note that this will modify the view hierarchy of the view controller by inserting a new container view above the view controller's view. The impact is that the view controller's view property will no longer return the originally provided view, it will return the new container. To access the original view, use the originalContentView property.

So it actually replaces your UIViewController's .view property. Keep that in mind.

When a UIViewController is displayed onscreen, it is resized to fit the whole screen (or at least the container it is taking up). Because of this, the UIViewController's .view property gets an autoresizing mask of UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth, so its height and width are flexible, and it can stretch to fit the container.

When you call [self setCanDisplayBannerAds:YES]; in viewDidLoad, your UIViewController's .view property gets replaced with a new UIView, and since it is in viewDidLoad, its autoresizingMask gets set appropriately. Conversely, calling [self setCanDisplayBannerAds:NO]; in viewDidLoad effectively does nothing.

In your sample app, when you call [self setCanDisplayBannerAds:YES]; outside of viewDidLoad, we can see that the autoresizingMask does not in fact get set to stretch. This is what is causing the problem:

- (IBAction)TouchUpInside:(id)sender {
    NSLog(@"Touch Up Inside");
    NSLog(@"Original View: %@", self.view);
    [self setCanDisplayBannerAds:YES];
    NSLog(@"New View: %@", self.view);
    NSLog(@"Content View: %@", self.originalContentView);
}

Output: 
2015-02-05 11:32:19.654 ID19658866[12404:3600041] Touch Up Inside
2015-02-05 11:32:19.655 ID19658866[12404:3600041] Original View: <UIView: 0x7f9c42717440; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f9c42717710>>
2015-02-05 11:32:19.666 ID19658866[12404:3600041] New View: <UIView: 0x7f9c42523fb0; frame = (0 0; 375 667); layer = <CALayer: 0x7f9c42524080>>
2015-02-05 11:32:19.666 ID19658866[12404:3600041] Content View: <UIView: 0x7f9c42717440; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f9c42717710>>

The key point here being in the 3rd line of output, where "New View" does not have autoresize = W+H; included in the output. We have also confirmed that self.view gets replaced by a new view in between the 2nd and 3rd lines of output.

To remedy this, we can make sure our new self.view property gets the required autoresizingMask by adding this line after we call [self setCanDisplayBannerAds:YES];:

self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;

And it will work, and we will know why.

Or we could, you know... just replace our self.view property by calling [self setCanDisplayBannerAds:YES]; within viewDidLoad, so it automatically gets the autoresizingMask set correctly.

Upvotes: 2

Yimin Rong
Yimin Rong

Reputation: 2027

Put these two lines in viewdidLoad:

[self setCanDisplayBannerAds:YES];
[self setCanDisplayBannerAds:NO];

The first call sets up the ads correctly, the second right away makes sure they don't show. Later you can call [self setCanDisplayBannerAds:YES]; and it will handle orientation changes without problem.

Upvotes: 3

BonanzaDriver
BonanzaDriver

Reputation: 6452

Total shot in the dark, but what happens if you call setNeedsLayout or setneedsupdatecontraint (provided you're using auto layout, which I would assume you are) once the ads come in? Also, make sure your constraints are properly setup between the ad view and the others. These probably are not the correct answer but I'm hoping it will provide some additional insight as to the real problem/solution.

Upvotes: 1

Related Questions