Supporting Multiple Windows on iPad

Beginning with iOS 13, applications can support multiple windows on iPad, meaning users can interact with multiple concurrent copies of an app's UI. Each window can be created in different sizes and can be resized at anytime, which has implications for how ads are loaded and presented.

This guide is intended to show you the best practices for rendering ads correctly in an iPad multi-window scenario.

Prerequisites

Set the scene in an ad request

In order to receive an ad that fits a specific window, you pass the view's windowScene to the ad request. The Google Mobile Ads SDK returns an ad with valid size for that scene.

Swift

func loadInterstitial() {
  let request = GADRequest()
  request.scene = view.window?.windowScene

  GADInterstitialAd.load(withAdUnitID: "[AD_UNIT_ID]",
      request: request) { ad, error in }
}

Objective-C

- (void)loadInterstitial {
  GADRequest *request = [GADRequest request];
  request.scene = self.view.window.windowScene;

  [GADInterstitialAd loadWithAdUnitID:@"[AD_UNIT_ID]"
      request:request
      completionHandler:^(GADInterstitialAd *ad, NSError *error) {}];
}

In test mode, ad requests will fail with the following error if your multiscene app requests an ad without passing a scene:

<Google> Invalid Request. The GADRequest scene property should be set for
applications that support multi-scene. Treating the unset property as an error
while in test mode.

In production mode, the ad request fills, but presenting the ad will fail if the ad is to be presented on a non full screen window. The error message in this case is:

<Google> Ad cannot be presented. The full screen ad content size exceeds the current window size.

Build the ad request in viewDidAppear:

The case of multi-window introduces a requirement of having a window scene for sending ad requests. Since a view has not yet been added to a window in viewDidLoad:, you should instead build ad requests in viewDidAppear: where the window scene is set by that point.

Note that viewDidAppear: can get called more than once during an app's lifecycle. We recommend that you wrap the ad request initialization code in a flag that indicates whether it has been done already.

Swift

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  if !requestInitialized {
    loadInterstitial()
    requestInitialized = true
  }
}

Objective-C

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  if (!_requestInitialized) {
    [self loadInterstitial];
    _requestInitialized = YES;
  }
}

Handle resizing

Users can drag scenes around at anytime, changing window sizes after an ad request has been made. It's up to you to request a new ad when resizing happens. The sample code below uses viewWillTransitionToSize:withTransitionCoordinator: to get notified when the root view controller's window rotates or resizes, but you can also listen to windowScene:didUpdateCoordinateSpace:interfaceOrientation:traitCollection: for window scene specific changes.

Interstitial and Rewarded Ad

The Google Mobile Ads SDK provides the method canPresentFromViewController:error: to determine if an interstitial or a rewarded ad is valid or not, giving you the ability to check if any fullscreen ad needs to be refreshed whenever window size changes.

Swift

override func viewWillTransition(to size: CGSize,
    with coordinator: UIViewControllerTransitionCoordinator) {
  super.viewWillTransition(to: size, with: coordinator)

  coordinator.animate(alongsideTransition: nil) { [self] context in
    do {
      try interstitial?.canPresent(fromRootViewController: self)
    } catch {
      loadInterstitial()
    }
  }
}

Objective-C

- (void)viewWillTransitionToSize:(CGSize)size
    withTransitionCoordinator:(id)coordinator {
  [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

  [coordinator animateAlongsideTransition:nil
      completion:^(id _Nonnull context) {
    if (![self.interstitial canPresentFromRootViewController:self error:nil]) {
      [self loadInterstitial];
    }
  }];
}

You can handle window resizing in the same way you do for window rotation. Your app is responsible for ensuring the banner ad fits the new window size.

The example below creates a new adaptive banner with the new window width:

Swift

override func viewWillTransition(to size: CGSize,
    with coordinator: UIViewControllerTransitionCoordinator) {
  super.viewWillTransition(to: size, with: coordinator)

  coordinator.animate(alongsideTransition: nil) { [self] context in
    loadBanner()
  }
}

func loadBanner() {
  let bannerWidth = view.frame.size.width

  bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(bannerWidth)

  let request = GADRequest()
  request.scene = view.window?.windowScene
  bannerView.load(request)
}

Objective-C

- (void)viewWillTransitionToSize:(CGSize)size
    withTransitionCoordinator:(id)coordinator {
  [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

  [coordinator animateAlongsideTransition:nil
      completion:^(id _Nonnull context) {
    [self loadBannerAd];
  }];
}

- (void)loadBannerAd {
  CGFloat bannerWidth = self.view.frame.size.width;

  self.bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(bannerWidth);

  GADRequest *request = [GADRequest request];
  request.scene = self.view.window.windowScene;
  [self.bannerView loadRequest:request];
}

Native ad

You are in control of rendering native ads and are responsible for ensuring the native ad is rendered inside a resized view, similar to the rest of your app content.

Known issues

Currently multi-window and split-screen ads are supported only in portrait mode. You will get the following log message when requesting an ad in landscape mode.

<Google> Ad cannot be presented. The full screen ad content size exceeds the
current window size.