Как узнать нужное смещение сверху для показа баннера firebase_admob в iOS?

Я использую плагин firebase_admob для flutter. Неприятная особенность состоит в том, что баннер нельзя встроить в дерево виджетов как обычный виджет, баннер накладывается поверх всего приложения. Но можно задать привязку к верху и смещение по оси Y.

Но тут я столкнулся с проблемой, не могу найти нужное смещение для этого. В андроиде вроде бы всё нормально, я просто получаю позицию по Y самого первого виджета в body (в Scaffold) и это и есть смещение. А вот в iOS к этой величине нужно ещё прибавить отступ сверху, там где показываются часы.

Неправильное отображение на iPhone 8Plus

Как видно на скрине баннер немного налазит на AppBar, а должен быть строго под ним.

Сокращённый код виджета такой:

  GlobalKey _widgetForAdsStickingKey = GlobalKey();

  @override
  void initState() {
    super.initState();
    _bannerAd = buildBannerAd()..load();
  }

  _getTopOffset() {
    final rBox =
        _widgetForAdsStickingKey.currentContext.findRenderObject() as RenderBox;
    final pos = rBox.localToGlobal(Offset.zero).dy;
    var offset = pos;
    if (Platform.isIOS) {
      offset = pos + MediaQuery.of(context).padding.top;
    }
    return offset;
  }

  BannerAd buildBannerAd() {
    return BannerAd(
        adUnitId: BannerAd.testAdUnitId,
        size: AdSize.banner,
        listener: (MobileAdEvent event) {
          if (event == MobileAdEvent.loaded) {
            setState(() {
              _add_is_loaded = true;
            });
            _bannerAd
              ..show(anchorType: AnchorType.top, anchorOffset: _getTopOffset());
          }
        });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: Text('Zeit verfolgen'),
      ),
      backgroundColor: backgroundColor,
      body: Center(
          key: _widgetForAdsStickingKey,
          child: SingleChildScrollView(
            child: Padding(
              ...

Таким образом в методе _getTopOffset() я получаю Y позицию виджета Center, который идёт корневым в body и прибавляю к этой величине отступ сверху до аппбара. Но баннер на iPhone немного накладывается на аппбар.

Может быть я неправильно считаю расстояние от начала экрана до начала области приложения при помощи MediaQuery.of(context).padding.top? Или я не учёл какой-то ещё параметр влияющий на высоту? Помогите как правильно вычислить смещение для баннера, чтобы баннер не накладывался на AppBar.


Ответы (2 шт):

Автор решения: MiT

А если так попробовать (MediaQuery.of(context).size.height):

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    double screenHeight = MediaQuery.of(context).size.height;
    double statusBarHeight = MediaQuery.of(context).padding.top;
    double appBarHeight = 50.0;

    double ad = screenHeight - (statusBarHeight + appBarHeight);

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: PreferredSize(
          preferredSize: Size.fromHeight(appBarHeight),
          child: AppBar(
            title: Text("Test"),
          ),
        ),
      ),
    );
  }
}
→ Ссылка
Автор решения: cheerful_weasel

В общем поиски не привели ни к чему положительному.

Во-первых, как я узнал MediaQuery.of(context).padding.top на iOS ниже 11 версии вообще может не срабатывать и выдавать 0 (это причина, по которой на моём реальном устройстве реклама приклеивается прямо поверх статус бара).

Во-вторых, как я выяснил опытным путём, оффсет 0 для баннера в данном плагине не означает реальный верх экрана! Это очень странно, но чтобы реклама показывалась сверху впритык к краю экрана (вверху) надо установить офсет 30. Возможно на это есть какие-то причины, но мне кажется это как-то не адекватно (однако может на iOS 13 таких проблем нет я пока не проверял, у меня эмулятор 12 ios).

Поэтому я решил пойти другим путём, раз этим способом задачу решить не удалось. Я просто поменял привязку рекламы с AnchorType.top на AnchorType.bottom. Однако, я решил, раз на андроиде старый способ работает, то для этой платформы я оставил старый вариант привязки.

А при подсчёте высоты смещения снизу я просто беру высоту экрана и отнимаю от неё положение виджета (на высоте которого должна быть реклама), а также высоту баннера (благо она фиксированная и равна 50).

Таким образом код остался старый лишь с двумя изменениями:

  1. В методе buildBannerAd() я поменял AnchorType.top на Platform.isIOS ? AnchorType.bottom : AnchorType.top

  2. В методе _getTopOffset() я просто заменил строку подсчёта офсета для iOS платформы offset = MediaQuery.of(context).size.height - 50 - pos;

GlobalKey _widgetForAdsStickingKey = GlobalKey();

  @override
  void initState() {
    super.initState();
    _bannerAd = buildBannerAd()..load();
  }

  _getTopOffset() {
    final rBox =
        _widgetForAdsStickingKey.currentContext.findRenderObject() as RenderBox;
    final pos = rBox.localToGlobal(Offset.zero).dy;
    var offset = pos;
    if (Platform.isIOS) {
      offset = MediaQuery.of(context).size.height - 50 - pos;
    }
    return offset;
  }

  BannerAd buildBannerAd() {
    return BannerAd(
        adUnitId: BannerAd.testAdUnitId,
        size: AdSize.banner,
        listener: (MobileAdEvent event) {
          if (event == MobileAdEvent.loaded) {
            setState(() {
              _add_is_loaded = true;
            });
            _bannerAd
              ..show(anchorType: Platform.isIOS ? AnchorType.bottom : AnchorType.top, anchorOffset: _getTopOffset());
          }
        });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: Text('Zeit verfolgen'),
      ),
      backgroundColor: backgroundColor,
      body: Center(
          key: _widgetForAdsStickingKey,
          child: SingleChildScrollView(
            child: Padding(
              ...
→ Ссылка