Support multiple windows and foldables

Applications that support multiple windows on iPad or run on foldable devices, such as the iPhone Duo, can display UI across dynamic window sizes and screen configurations. On iPad, users can interact with multiple concurrent copies of an app's UI and resize windows. On foldable devices, the device's fold state determines the active screen. When unfolded, the device uses the larger inner display. When folded, the device uses the smaller outer display.

This guide covers loading and rendering ads across multi-window environments and foldable displays.

Prerequisites

Before you begin, complete the following:

Set the window scene in an ad request

To receive an ad that fits a specific window or active display, assign the active UIWindowScene object to the scene property on the GAMRequest object. Google Mobile Ads SDK returns an ad sized for that scene. In SwiftUI, query connected scenes for the active foreground window scene.

The following example configures the window scene, loads an interstitial ad on success, and logs an error message on failure:

Swift

func loadAdManagerInterstitial() async {
  let request = AdManagerRequest()
  request.scene = view.window?.windowScene

  do {
    adManagerInterstitial = try await AdManagerInterstitialAd.load(
      with: "/21775744923/example/interstitial", request: request)
  } catch {
    print("Failed to load interstitial ad with error: \(error.localizedDescription)")
  }
}

SwiftUI

func loadAdManagerInterstitial() async {
  let request = AdManagerRequest()
  request.scene =
    UIApplication.shared.connectedScenes
    .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene

  do {
    let _ = try await AdManagerInterstitialAd.load(
      with: "/21775744923/example/interstitial", request: request)
  } catch {
    print("Failed to load interstitial ad with error: \(error.localizedDescription)")
  }
}

Objective-C

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

  [GAMInterstitialAd loadWithAdManagerAdUnitID:@"/21775744923/example/interstitial"
                                       request:request
                             completionHandler:^(GAMInterstitialAd *ad, NSError *error) {
                               if (error) {
                                 NSLog(@"Failed to load interstitial ad with error: %@",
                                       [error localizedDescription]);
                                 return;
                               }
                               self.adManagerInterstitial = ad;
                             }];
}

In test mode, ad requests fail with the following error if your multi-scene 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 fails if the ad is presented on a window smaller than the ad content size:

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

Build the ad request

When sending ad requests, multi-scene applications require a valid window scene. Where the window scene is populated, build ad requests using the viewDidAppear method. We recommend this process because a view is not yet added to a window in the viewDidLoad method.

While you can call the viewDidAppear method more than once in an app's lifecycle, you must wrap the ad request in a boolean flag to run only once.

The following example initializes the ad request in the viewDidAppear method:

Swift

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

Objective-C

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

Handle window resizing

Window dimensions change when users resize scenes on iPad or fold and unfold a foldable device such as the iPhone Duo. When the window size changes, verify that loaded ads fit the updated dimensions and reload the ads if necessary.

To verify that loaded ads fit the updated dimensions, use either of the following methods:

Full-screen ads

To verify whether a loaded full-screen ad remains valid for the updated window size or fold state, use the canPresentFromRootViewController:error: method. If the check fails, load an ad sized for the updated scene.

The following example checks whether an interstitial ad can show after a size transition and reloads the ad if necessary:

Swift

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

  coordinator.animate(alongsideTransition: nil) { [weak self] _ in
    guard let self else { return }
    do {
      try self.interstitial?.canPresent(from: self)
    } catch {
      Task {
        await self.loadInterstitial()
      }
    }
  }
}

Objective-C

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

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

Handle window resizing and fold transitions similar to device rotation. Limit banner views to the safeAreaLayoutGuide property. This process lets ads remain clear of device hinges, cutouts, and system bars. This process also recalculates the anchored adaptive banner size using the updated safe area width.

The following example loads an anchored adaptive banner with the updated window width during a size transition:

Swift

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

  coordinator.animate(alongsideTransition: nil) { [weak self] _ in
    self?.loadBannerAd()
  }
}

@MainActor
func loadBannerAd() {
  let frame = view.frame.inset(by: view.safeAreaInsets)
  let viewWidth = frame.size.width

  bannerView.adSize = largeAnchoredAdaptiveBanner(width: viewWidth)

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

SwiftUI

struct MultiSceneAdManagerBannerView: View {
  var body: some View {
    GeometryReader { geometry in
      let adSize = largeAnchoredAdaptiveBanner(width: geometry.size.width)
      MultiSceneAdManagerBannerViewContainer(adSize: adSize)
        .frame(width: adSize.size.width, height: adSize.size.height)
    }
  }
}

private struct MultiSceneAdManagerBannerViewContainer: UIViewRepresentable {
  let adSize: AdSize

  func makeUIView(context: Context) -> AdManagerBannerView {
    let bannerView = AdManagerBannerView(adSize: adSize)
    let request = AdManagerRequest()
    request.scene =
      UIApplication.shared.connectedScenes
      .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene
    bannerView.load(request)
    return bannerView
  }

  func updateUIView(_ uiView: AdManagerBannerView, context: Context) {
    if uiView.adSize.size != adSize.size {
      uiView.adSize = adSize
      let request = AdManagerRequest()
      request.scene = uiView.window?.windowScene
      uiView.load(request)
    }
  }
}

Objective-C

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

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

- (void)loadBannerAd {
  dispatch_async(dispatch_get_main_queue(), ^{
    CGRect frame = UIEdgeInsetsInsetRect(self.view.frame, self.view.safeAreaInsets);
    CGFloat viewWidth = frame.size.width;

    self.bannerView.adSize = GADLargeAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth);

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

Native ads

You control the layout of native ads. Unlike banners or full-screen ads, you don't need to reload native ads when the window resizes. Use standard Auto Layout constraints or SwiftUI layouts for the native ad view to adapt to size changes.