Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google Maps not loading on iOS (physical device) #1366

Open
judge opened this issue Jan 6, 2023 · 29 comments
Open

Google Maps not loading on iOS (physical device) #1366

judge opened this issue Jan 6, 2023 · 29 comments

Comments

@judge
Copy link

judge commented Jan 6, 2023

Bug Report

Plugin(s)

Google Maps

Capacitor Version

💊   Capacitor Doctor  💊 

Latest Dependencies:

  @capacitor/cli: 4.6.1
  @capacitor/core: 4.6.1
  @capacitor/android: 4.6.1
  @capacitor/ios: 4.6.1

Installed Dependencies:

  @capacitor/android: not installed
  @capacitor/cli: 4.6.1
  @capacitor/core: 4.6.1
  @capacitor/ios: 4.6.1

[success] iOS looking great! 👌

Platform(s)

iOS

Current Behavior

Empty box is appearing instead of Google Maps. Web works perfectly.

Expected Behavior

Show the map.

Code Reproduction

Sample webcomponent (apiKey deleted):

import { GoogleMap } from '@capacitor/google-maps';

const apiKey = '';

window.customElements.define('sample-app', class extends HTMLElement {
  async connectedCallback() {
    this.innerHTML = `
      <style>
        capacitor-google-map {
          display: inline-block;
          width: 200px;
          height: 400px;
        }
      </style>
      <capacitor-google-map id="main-map" style="border: 1px solid red;"></capacitor-google-map>
    `;

    const mapRef = this.querySelector('#main-map');

    const newMap = await GoogleMap.create({
      id: 'my-map',
      forceCreate: true,
      element: mapRef,
      apiKey: apiKey,
      config: {
        center: {
          lat: 33.6,
          lng: -117.9,
        },
        zoom: 8,
      },
    });
  }
});

Other Technical Details

When I load the page on web it works fine, on iOS (physical device) I can see an empty box (with the red border I added).

Additional Context

There is no error at all, I can see the following in XCode:

⚡️  [log] - [vite] connected.
⚡️  WebView loaded
⚡️  To Native ->  CapacitorGoogleMaps addListener 26518793
⚡️  To Native ->  CapacitorGoogleMaps create 26518794
⚡️  TO JS undefined
@Ionitron
Copy link
Collaborator

Ionitron commented Jan 9, 2023

This issue may need more information before it can be addressed. In particular, it will need a reliable Code Reproduction that demonstrates the issue.

Please see the Contributing Guide for how to create a Code Reproduction.

Thanks!
Ionitron 💙

@DwieDima
Copy link

DwieDima commented Jan 9, 2023

can you change the id to main-map and let me know if the problem still exists?

    const newMap = await GoogleMap.create({
      id: 'main-map', // <--
      forceCreate: true,
      element: mapRef,
      apiKey: apiKey,
      config: {
        center: {
          lat: 33.6,
          lng: -117.9,
        },
        zoom: 8,
      },
    });

I was able to render map on ios using this snippet

@judge
Copy link
Author

judge commented Jan 9, 2023

Hi @DwieDima ,
I cannot see the map after the proposed change. :( I uploaded a sample application: https://github.com/judge/sample-app
Thanks!

@naqeeb-klabs
Copy link

Hi @judge ,

Did you found any work around for this issue?

@judge
Copy link
Author

judge commented Jan 18, 2023

Unfortunetaly not. I would add "needs reply" and remove "needs reproduction" labels to the issue but I cannot do that. :(

@akeeee
Copy link

akeeee commented Feb 6, 2023

@judge
Copy link
Author

judge commented Feb 7, 2023

Hi @akeeee ,

Yes it is enabled for both JavaScript and iOS. The JavaScript API works perfectly, it even shows request count but there is no request count for iOS.

@Simon54
Copy link

Simon54 commented Feb 13, 2023

It seems that the ScrollView the plugin is searching for is sometimes not existing yet when the map is initialized. I worked around this by changing Map.swift like this:

if let target = self.targetViewController {
    target.tag = 1
    target.removeAllSubview()
    self.mapViewController.view.frame = target.bounds
    target.addSubview(self.mapViewController.view)
    self.mapViewController.GMapView.delegate = self.delegate
} else { // add this else case
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
        self.render(callback)
    }
    return;
}

If you also need your callbacks to wait until the map is created you need to call the resolve at the point that onMapReady is signaled.

@yoaquim
Copy link

yoaquim commented Feb 22, 2023

I'm also facing this issue exactly.

On first use of the map, the "box" and the marker loads, but there's no actual map.

If I navigate to another screen — even if that screen is using maps — I get nothing.

It does work on web; I get the same logs @judge is getting from XCode.

Using:

💊 Capacitor Doctor 💊

Latest Dependencies:

@capacitor/cli: 4.6.3
@capacitor/core: 4.6.3
@capacitor/android: 4.6.3
@capacitor/ios: 4.6.3

Installed Dependencies:

@capacitor/android: not installed
@capacitor/cli: 4.6.3
@capacitor/core: 4.6.3
@capacitor/ios: 4.6.3

@yoaquim
Copy link

yoaquim commented Feb 22, 2023

@judge where you able to find a solution?

@avioli
Copy link

avioli commented May 18, 2023

@Simon54 thank you - that sorted it out for me. I'll now have to make sure this "patch" gets applied when this is deployed to CI.

@metinjakupi
Copy link

Bump!

@avioli
Copy link

avioli commented May 18, 2023

Ok... I think I found a better solution than patching Map.swift, which was a hit-or-miss solution anyway.

What I'm doing and is consistently working is:

  1. I wrap the capacitor-google-map element in a relative div:
<template>
  <div class="map-wrapper">
    <capacitor-google-map id="map"></capacitor-google-map>
  </div>
</template>
  1. Then I make the capacitor-google-map an absolutely positioned element:
<style scoped>
.map-wrapper {
  position: relative;
  flex: 1;
}

capacitor-google-map {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
</style>

(I use this component within another that uses display: flex; so I have flex: 1 to ensure the wrapper grows to maximum height on mobile)

  1. Then in my script I wait for the capacitor-google-map to be connected with a simple setTimeout... AND IT WORKS EVERY TIME:
<script setup lang="ts">
import { GoogleMap } from '@capacitor/google-maps';
import { onBeforeUnmount } from 'vue';
import { onMounted } from 'vue';

const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY;

let map: GoogleMap | null = null;

onMounted(async () => {
  await new Promise(res => setTimeout(res, 300));

  const mapRef = document.getElementById('map')!;

  const newMap = await GoogleMap.create({
    id: `my-map`, // Unique identifier for this map instance
    element: mapRef!, // reference to the capacitor-google-map element
    apiKey: apiKey, // Your Google Maps API Key
    config: {
      center: { lat: 33.6, lng: -117.9 },
      zoom: 8,
    },
  });
  map = newMap;
});

onBeforeUnmount(() => {
  if (map) {
    map.destroy();
    map = null;
  }
})

I did so many... many, many, many iterations with just a single change and this is the only solution without any changes to the plugin itself that worked!

Key findings:

  • The capacitor-google-map is a Custom Element, which takes a tiny bit of time to be hooked to its JS element after the <capacitor-google-map /> element is in the DOM. That JS connection adds an additional DIV element inside it that has 200% height, so this makes a ScollView in the WebKit browser.
  • If that ScrollView doesn't get created - this plugin cannot find and attach the GoogleMaps view controller and silently fails!!!
  • The Map.swift file makes a lookup for the said ScrollView by using some dodgy logic, but it works if everything is just right.
  • The container for the capacitor-google-map element must not be a Flex box and it must allow overflowing - otherwise the ScrollView won't be created by WebKit. Even if some parent container is a Flex box - the immediate parent should allow overflowing, so the 200% high DIV can ensure the ScrollView is created! So... your best bet is to use an absolutely positioned style for the bloody capacitor-google-map and wrap it in a relatively positioned DIV, which you can size as your heart desires.

That's it form me and good luck.

@avioli
Copy link

avioli commented May 18, 2023

@judge I know this finding is some five months late, but to make my solution work in your context, since I use Vue and you do not - add a timeout before calling GoogleMap.create to give a chance to capacitor-google-map to connect properly.

I personally wasn't able to make it work with the styles suggested in the official "documentation" - I had to use a wrapper DIV, so in your case I would suggest you do:

this.innerHTML = `
  <style>
    #map-wrapper {
      display: inline-block;
      width: 200px;
      height: 400px;
      position: relative;
    }
    capacitor-google-map {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
  </style>
  <div id="map-wrapper" style="border: 1px solid red;">
    <capacitor-google-map id="main-map"></capacitor-google-map>
  </div>
`;

I tested the above wrapper styles within my Vue app and it worked for me, but I had to move the red border to the wrapper - otherwise the dodgy logic in Maps.swift fails to identify the ScrollView :)

@metinjakupi
Copy link

It seems that the ScrollView the plugin is searching for is sometimes not existing yet when the map is initialized. I worked around this by changing Map.swift like this:

if let target = self.targetViewController {
    target.tag = 1
    target.removeAllSubview()
    self.mapViewController.view.frame = target.bounds
    target.addSubview(self.mapViewController.view)
    self.mapViewController.GMapView.delegate = self.delegate
} else { // add this else case
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
        self.render(callback)
    }
    return;
}

If you also need your callbacks to wait until the map is created you need to call the resolve at the point that onMapReady is signaled.

Can you give full example please?

@avioli
Copy link

avioli commented Jun 5, 2023

@metinjakupi The <capacitor-google-map> element is a custom element, which requires an event-loop cycle for the browser to connect it to JavaScript once the DOM is updated. Once that's connected and if the *ScrollView exists the lookup for the targetViewController will succeed. Otherwise it may need a bit more time.

Unfortunately if the *ScrollView is never created by Webkit - that loop will go forever.

I did patch the swift file in the hope that would be enough of a fix, but it wasn't and I felt it is pretty bad - patching and all.

@gm112
Copy link

gm112 commented Jul 2, 2023

#1366 (comment)

I think is actually along the correct lines of what the fix should be. The issue is that the web view is loaded before the map view component is even available, which confuses the JS code that executes after. Any subsequent calls into the Google Maps component will 100% yield a null reference exception because the map controller is simply not initialized. This completely screams lifecycle issue, to me, honestly.

Currently the code just invokes the resolve callback without any concern for if the map view controller is active or not. It just assumes so. The reason why the fix I linked above works is because it creates effectively a spinlock which ensures that the map view is always initialized before proceeding with invoking the resolve callback.

I'm honestly not sure how the Google Maps plugin shipped in 5.0 considering how easy it is to reproduce this bug.

@IbrahimElkhatib
Copy link

any solution for this?

@avioli
Copy link

avioli commented Nov 19, 2023

I believe the latest alpha version of the plugin does address the delay requirement, but I haven't tested it:

MR:1638

To be honest - I had to switch to a JS version for mapping, since this plugin proved to be less useful in several cases that we needed - like annotations (aka popups), marker SVG images and marker animation. I had to use Leaflet (+MapBox for tiles), since GoogleMaps JS requires a realtime download to import their JS libraries, which is not something we wanted. It was also such a nuisance to setup and use - no Vue (or any JS framework) support at all - just bare-bone procedural JS - a loaded foot-gun on every corner.

@greg-md
Copy link

greg-md commented Dec 11, 2023

Still not a fix? I am tired of trying everyhting and couldn't make it load. Only the google logo is loading inside a gray area. :(

@gm112
Copy link

gm112 commented Apr 21, 2024

I believe the latest alpha version of the plugin does address the delay requirement, but I haven't tested it:

MR:1638

To be honest - I had to switch to a JS version for mapping, since this plugin proved to be less useful in several cases that we needed - like annotations (aka popups), marker SVG images and marker animation. I had to use Leaflet (+MapBox for tiles), since GoogleMaps JS requires a realtime download to import their JS libraries, which is not something we wanted. It was also such a nuisance to setup and use - no Vue (or any JS framework) support at all - just bare-bone procedural JS - a loaded foot-gun on every corner.

This doesn't fix the issue - because the issue is that the native code seems to fire off before the page is fully ready to have the map container injected/overlayed, hence why the fix suggested by #1366 (comment) works so well.

Particularly this bit of code:

 else { // add this else case
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
        self.render(callback)
    }
    return;
}

Though, I do not believe this to be the actual fix as I believe this is merely pushing the chance of this bug happening to a condition that's pretty much never going to happen. I think the issue is that some of the code for the Map Controller just simply executes too early.

@ngmiduc
Copy link

ngmiduc commented May 27, 2024

@judge I know this finding is some five months late, but to make my solution work in your context, since I use Vue and you do not - add a timeout before calling GoogleMap.create to give a chance to capacitor-google-map to connect properly.

I personally wasn't able to make it work with the styles suggested in the official "documentation" - I had to use a wrapper DIV, so in your case I would suggest you do:

this.innerHTML = `
  <style>
    #map-wrapper {
      display: inline-block;
      width: 200px;
      height: 400px;
      position: relative;
    }
    capacitor-google-map {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
  </style>
  <div id="map-wrapper" style="border: 1px solid red;">
    <capacitor-google-map id="main-map"></capacitor-google-map>
  </div>
`;

I tested the above wrapper styles within my Vue app and it worked for me, but I had to move the red border to the wrapper - otherwise the dodgy logic in Maps.swift fails to identify the ScrollView :)

I think this was a good hint. I spent some time now debugging some issues of this plugin but I can also now create the map with this help. I used this styling for my map which did the fix for me. It worked without a wrapper. I noticed that height: 99.9vh did not work on some devices. I used e.g. an iPhone SE (1gen) with IOS 14, or IOS 15. Only if I change the height with a difference of 10vh of the fullheight, it worked. When I use height: 99vh it didn't work. No idea why... - maybe it depends on the device height.

capacitor-google-map {
  display: inline-block;
  width: 100vw;
  height: 115vh;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  position: fixed !important;
  z-index: 1;
}

A workaround might be to render the map with a styling of 200vh and when the map is initialised to reduce it again to 100vh.

EDIT:

I noticed that after page transitions, the map doesn't reappear anymore... (with the fix with the styling)

@alexp25
Copy link

alexp25 commented Jun 11, 2024

@ngmiduc does it work with the fix with the styling, or with @gm112 suggestion using the spinlock, or both of them are needed?

@ngmiduc
Copy link

ngmiduc commented Jun 11, 2024

@ngmiduc does it work with the fix with the styling, or with @gm112 suggestion using the spinlock, or both of them are needed?

I have tested the solution of @gm112 but I didn't resolve my issue. I added some extra logs and it seems to be an infinite loop where the reference to the google maps was lost by the ViewControler.

What works for me now is to set the width and height to 100vh and 100vw. I used to have it at 99.9vh, but this worked on some devices but not on other devices. There is also no way to change the height dynamically. There are apparently some specific styling dimensions for some specific devices that will make the app disappear and page transitions will retrigger the native map function onDisplay which could make the app disappear when the map has some bad heights or widths. That was at least my experience with the map.

@alexp25
Copy link

alexp25 commented Jun 11, 2024

@ngmiduc It seems that using this component is required for iOS: <capacitor-google-map id="map"></capacitor-google-map>
Before I was using a div, which worked fine on Android, and before that for some reason I tried using capacitor-google-maps instead of capacitor-google-map which didn't work. (Note I'm using Angular)

"The Google Maps Capacitor plugin ships with a web component that must be used to render the map in your application as it enables us to embed the native view more effectively on iOS" (from the readme) It looks like the component implements the style hack with the height.

I currently managed to get it shown, but only for a few seconds, until I'm starting to fill up the map, and for some reason it disappears (that might be related to something else in my project though)

@alexp25
Copy link

alexp25 commented Jun 12, 2024

I found out why the map disappeared. It seems to be related to dynamically adding a new element to the page where the map is created. Could someone explain why would that happen? Would the map binding break or how can this be avoided?

@bravesoul349
Copy link

@yoaquim, I am also facing the exact same problem. Did you find any solution?

@gm112
Copy link

gm112 commented Nov 2, 2024

@ngmiduc does it work with the fix with the styling, or with @gm112 suggestion using the spinlock, or both of them are needed?

The spinlock issue I noted will propagate as null exceptions coming from the native plugin, which you would visually see as the Google Maps component not rendering at all. So regarding the styling fix, I believe people are having another issue that is unrelated. I hope that clears things up.

@singatias
Copy link

singatias commented Dec 6, 2024

For the angular gang, I had the same issue on the iphone 16 but not the iphone 16 pro (was working properly for w.e. reason). I found a simple solution, forcing the angular lifecycle to detect changes seems to fix my issue:

private readonly changeDetectorRef = inject(ChangeDetectorRef);
...

async initMap() {
  await GoogleMap.create(...);
  ...
  this.changeDetectorRef.detectChanges();
}

Let me know if this fix yours also

EDIT: Making the map too high is apparently recreating the same issue -.- this is so frustrating to work with and I assume it will also be clunky from device to device so more testing is needed. 40vh-60vh is fine 70vh map refuse to appear...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests