Поддержка нескольких окон и складных устройств

Приложения, поддерживающие несколько окон на iPad или работающие на складных устройствах, таких как iPhone Duo, могут отображать пользовательский интерфейс в окнах динамического размера и на экранах разной конфигурации. На iPad пользователи могут взаимодействовать с несколькими копиями интерфейса приложения одновременно и изменять размер окон. На складных устройствах активный экран определяется состоянием складывания. В разложенном виде используется внутренний экран большего размера. В сложенном виде устройство использует меньший внешний экран.

В этом руководстве рассказывается, как загружать и показывать объявления в многооконном режиме и на складных экранах.

Требования

Прежде чем начать, выполните следующие действия:

Как задать сцену окна в запросе объявления

Чтобы получить объявление, которое подходит для определенного окна или активного экрана, назначьте активный объект UIWindowScene свойству scene объекта GADRequest. Google Mobile Ads SDK возвращает объявление, размер которого соответствует этой сцене. В SwiftUI запросите подключенные сцены для активной сцены окна переднего плана.

В следующем примере настраивается сцена окна, загружается межстраничное объявление в случае успеха и регистрируется сообщение об ошибке в случае неудачи:

Swift

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

  do {
    interstitial = try await InterstitialAd.load(
      with: "ca-app-pub-3940256099942544/4411468910", request: request)
  } catch {
    print("Failed to load interstitial ad with error: \(error.localizedDescription)")
  }
}

SwiftUI

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

  do {
    let _ = try await InterstitialAd.load(
      with: "ca-app-pub-3940256099942544/4411468910", request: request)
  } catch {
    print("Failed to load interstitial ad with error: \(error.localizedDescription)")
  }
}

Objective-C

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

  [GADInterstitialAd
       loadWithAdUnitID:@"ca-app-pub-3940256099942544/4411468910"
                request:request
      completionHandler:^(GADInterstitialAd *ad, NSError *error) {
        if (error) {
          NSLog(@"Failed to load interstitial ad with error: %@", [error localizedDescription]);
          return;
        }
        self.interstitial = ad;
      }];
}

В тестовом режиме запросы объявлений завершаются ошибкой, если приложение с несколькими сценами запрашивает объявление без передачи сцены:

<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.

В производственном режиме запрос объявления выполняется, но показ объявления не происходит, если размер окна меньше размера содержания объявления.

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

Создание запроса объявления

При отправке запросов объявлений приложениям с несколькими сценами требуется действительная сцена окна. В месте, где заполняется сцена окна, создайте запросы объявлений с помощью метода viewDidAppear. Мы рекомендуем этот способ, поскольку в методе viewDidLoad представление ещё не добавлено в окно.

Хотя метод viewDidAppear можно вызывать несколько раз за жизненный цикл приложения, запрос объявления необходимо обернуть в логический флаг, чтобы он выполнялся только один раз.

В примере ниже запрос объявления инициализируется в методе viewDidAppear:

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;
  }
}

Как обрабатывать изменение размера окна

Размеры окна меняются, когда пользователи изменяют размер сцен на iPad или складывают и раскладывают складное устройство, например iPhone Duo. При изменении размера окна убедитесь, что загруженные объявления соответствуют новым размерам, и при необходимости перезагрузите их.

Чтобы убедиться, что загруженные объявления соответствуют новым размерам, воспользуйтесь одним из следующих способов:

Полноэкранные объявления

Чтобы проверить, подходит ли загруженное полноэкранное объявление для нового размера окна или состояния сгиба, используйте метод canPresentFromRootViewController:error:. Если проверка не пройдена, загрузите объявление, размер которого соответствует обновленной сцене.

В примере ниже проверяется, может ли межстраничное объявление показываться после изменения размера, и при необходимости оно загружается повторно.

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];
                        }
                      }];
}

Обрабатывайте изменение размера окна и складывание устройства так же, как поворот экрана. Ограничить показы баннеров ресурсом safeAreaLayoutGuide. Это позволяет избежать наложения объявлений на петли, вырезы и системные панели. При этом также будет пересчитан размер фиксированного адаптивного баннера с учетом новой ширины безопасной зоны.

В примере ниже показано, как загрузить фиксированный адаптивный баннер с обновленной шириной окна при изменении размера.

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 = Request()
  request.scene = view.window?.windowScene
  bannerView.load(request)
}

SwiftUI

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

private struct MultiSceneBannerViewContainer: UIViewRepresentable {
  let adSize: AdSize

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

  func updateUIView(_ uiView: BannerView, context: Context) {
    if uiView.adSize.size != adSize.size {
      uiView.adSize = adSize
      let request = Request()
      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);

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

Нативные объявления

Вы можете управлять макетом нативных объявлений. В отличие от баннеров и полноэкранных объявлений, нативные объявления не нужно перезагружать при изменении размера окна. Чтобы представление нативного объявления адаптировалось к изменениям размера, используйте стандартные ограничения Auto Layout или макеты SwiftUI.