How to create a mobile app (PWA) with MapTiler SDK JS
This example shows how to use MapTiler SDK to create a native mobile app that can be pushed to the Apple App Store and Google Play.
With this project as a starter (or any Capacitor app) and with a single codebase, you can create:
- A web app hosted online, which people visit with a regular web browser.
- A PWA that people can turn into a semi-app (under certain conditions: Android, EU-iPhone, etc.).
- A Capacitor-powered native web app wrapped to be distributed on Apple AppStore and Google Play.
Get started
For this project, we started from the ViteJS Vanilla TypeScript sample project and then followed the Ionic Capacitor instructions to give our project mobile superpowers.
To make things easier for you, we have created the maptiler-mini-mobile-app repository where we have already configured all the dependencies, configuration files, styles, etc. Here’s how to set it up:
- Clone the maptiler-mini-mobile-app repo.
- To install project dependencies, run
npx npm install
. - Rename
.env.sample
to.env
and replace the value ofYOUR_MAPTILER_CLOUD_API_KEY
with your actual MapTiler API key. - Modify the file
capacitor.config.ts
: Update the app name, logo, bundleID, etc. For more info, refer to the Capacitor configuration docs. - In terminal, run
npx npm run build
&&npx cap sync
.
Modify the source
The project itself reuses the structure of the ViteJS boilerplate, meaning the project source is located in the src/
directory and can be ran in a regular web browser:
- In dev/watch mode:
npm run dev
- To build a production bundle:
npm run build
After building the project for production, you can run it locally with npm run preview
.
Update the mobile app
Both mobile projects (XCode workspace and Android Studio Workspace) need to be updated after the web project has been built for production. To do so, run: npm run sync
Back in XCode or Android Studio, you may see a popup asking if you want to refresh the project based on the updates. Select “yes” (or “Read from disk” in XCode).
Then build the projects in XCode or Android Studio again and you should see your latest changes.
Customize the iOS project
First, run your project directly on iOS with the command npx cap run ios
or open the iOS project with npm run open-xcode
. You can then tune a few things.
Select the target iOS version:
If you plan to distribute your app, set up signing by associating it to a Team:
To change the icon of your app, go to menu App > App > Assets and drag and drop a non-transparent image in the icon frame:
What’s in this project?
Mobile-friendly geolocation
The geolocation control originally available in MapTiler SDK is web-specific, and even though it will work when encapsulated into a mobile app, using it will trigger two user-agreement panel: one at the web-view level, and the next at system level. A more elegant way is to use @capacitor/geolocation
, an official plugin that uses geolocation directly from the system. We had to modify the settings of the native projects (both Android and iOS) to grant the app the permission to use geolocation. You can read more about this on the plugins’s page.
To make it simpler to integrate, we have created a SDK-friendly control that you can find in src/universalgeolocatecontrol.ts
. Note that this also works in a regular web page as the plugin provides a complete fallback.
Safe insets
The safe insets are the margins that need to be put so that the content you display on screen does not step on the system display, such as the status bar on top or the bottom menu on iOS. They are defined as CSS environment variables that you can directly leverage in your styling.
For instance, MapTiler SDK and MapLibre stylesheet define some classes that apply to the controls called .maplibregl-ctrl-yyy-xxx
. Our mobile app can define more properties to these classes to prevent the visual elements to clash with the system display. You can see the classes with the insets in the file src/style.css
.
Example of a class using safe insets for controls positioned in the top-right corner of the map:
.maplibregl-ctrl-top-right {
top: env(safe-area-inset-top);
}
The app’s viewport must also have the following attributes in the index.html
:
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
Here is the result:
❌ Without safe insets | ✅ With safe insets |
---|---|
![]() |
![]() |