Dev Diary: Native Google Maps
Native Google Maps has been a much-anticipated addition to Capacitor’s core lineup. This new plugin comes with powerful features such as the creation and installation of multiple maps, adding one or more markers, marker clustering, map type support (including indoors and traffic layers), and an advanced scrolling experience—one of the most popular features.
The scrolling implementation of Capacitor Google Maps took a few iterations. Let’s step through the process of creating this complex feature.
One of the challenges we faced while working on our version of the Google Maps Capacitor Plugin was to figure out some way to improve the “scrolling” experience with real-world content along with a native google map.
Most attempts at this involve simply creating the Google Map view, and placing it on the screen over the web view that contains the running application. While this works for fullscreen map implementations, this presents several problems for other scenarios:
- The map will not scroll inline with, nor force content that sits inline with the map, to wrap around it.
- The map lives outside of the DOM of the web application, meaning it will not respect CSS z-indexing. This could be a problem in situations where other web elements need to appear above the map, like menus, dialogs, and other advanced design techniques.
HTML Element Approach
Our first naive attempt was simple: require the API consumer to create an HTML element that will “contain” the map (similar to how it works with the JS Maps API), and send a reference to that element along with the rest of their map settings through our API. We then take the bounds of the container element and use those values to position the native google map over the web view.
From here, to implement scrolling we listen to scroll events from all parent elements of the map container and send the updated map container bounds to the native layer, which is used to update the position of the native view.
The result is a native Google Map that appears to scroll inline with the content, with Inline content that wraps around the map thanks to the map container element. This solves problem #1, but since the map view is still over the web view, we still need a way to deal with #2.
Inline Native Elements
Normally, there is no way to integrate native elements inline with web content. However, due to an interesting quirk of iOS WebView, there is.
If a block-level element inside an iOS WebView is given a forced overflow scroll, that scrolling element is represented on the native side as a UIScrollView – and a child of the WKWebView control. This UIScrollView is something we can add arbitrary UIViews into, and when we inject the Google Maps native view into the scroll view, something interesting happens.
The injected native view is inserted into the web view and rendered inline with the rest of the content as if it were any piece of HTML content. Scrolling and touch events all are handled appropriately, as well as proper z-indexing with other HTML elements on the page. This solves problem #2 in the cleanest way possible – we essentially have a native element functioning as a run-of-the-mill HTML element.
One problem with this technique is that it is currently not possible to accomplish a similar technique on Android. So, in order to solve problem #2 on Android, we kept our initial technique, but instead of having the native Google Map view above the web view, we moved it behind the web view. This allows HTML elements (ALL HTML elements, in fact) to layer above the map. The only downside of this is that the web view (and the entire DOM stack all the way down to HTML and body) must have a transparent background for the map view to be visible through the web view control. While not the cleanest resolution, it solves the two main issues with our native maps solution.
The new Capacitor Google Maps plugin brings some powerful new features to the community. We hope that this look behind the curtain was insightful and got your minds thinking about how else inline native elements could be used!