From f5b220280ee18b85040fc36ea20d9454707f9e5b Mon Sep 17 00:00:00 2001 From: Jakob Johansson Date: Fri, 11 Sep 2026 10:30:56 +0200 Subject: [PATCH 1/4] Add flag to exclude Google Maps SDK in iOS --- LuggMaps.podspec | 8 +++++++- docs/content/docs/installation.mdx | 10 ++++++++++ ios/LuggMapView.mm | 12 ++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/LuggMaps.podspec b/LuggMaps.podspec index c4eeb42..5ffd74a 100644 --- a/LuggMaps.podspec +++ b/LuggMaps.podspec @@ -16,7 +16,13 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,swift,cpp}" s.private_header_files = "ios/**/*.h" - s.dependency "GoogleMaps" + # `$LuggMapsGoogleEnabled = false` in the Podfile drops the Google Maps SDK (Apple Maps only) + google_enabled = defined?($LuggMapsGoogleEnabled) ? $LuggMapsGoogleEnabled : true + if google_enabled + s.dependency "GoogleMaps" + else + s.exclude_files = "ios/core/Google*", "ios/core/GMS*" + end s.frameworks = "MapKit" install_modules_dependencies(s) diff --git a/docs/content/docs/installation.mdx b/docs/content/docs/installation.mdx index 82a012a..13f49a1 100644 --- a/docs/content/docs/installation.mdx +++ b/docs/content/docs/installation.mdx @@ -57,6 +57,16 @@ Apple Maps works out of the box on iOS. Google Maps requires an API key on every cd ios && pod install ``` + #### Apple Maps only + + If you only use `provider="apple"` on iOS, you can skip the Google Maps SDK entirely (it adds roughly 10 MB to the app). Set this at the top of your `Podfile` before running `pod install`: + + ```ruby title="Podfile" + $LuggMapsGoogleEnabled = false + ``` + + With the SDK excluded, a `MapView` requesting `provider="google"` falls back to Apple Maps and logs a warning. + ### Android Add your Google Maps API key to `AndroidManifest.xml`: diff --git a/ios/LuggMapView.mm b/ios/LuggMapView.mm index 93ebb84..16adcef 100644 --- a/ios/LuggMapView.mm +++ b/ios/LuggMapView.mm @@ -8,8 +8,11 @@ #import "LuggTileOverlayView.h" #import "core/AppleMapProvider.h" #import "core/AppleStaticMapProvider.h" +#if __has_include() +#define LUGG_GOOGLE_MAPS_AVAILABLE 1 #import "core/GoogleMapProvider.h" #import "core/GoogleStaticMapProvider.h" +#endif #import "core/MapProviderDelegate.h" #import "events/CameraIdleEvent.h" #import "events/CameraMoveEvent.h" @@ -329,6 +332,7 @@ - (void)initializeProviderWithCoordinate:(CLLocationCoordinate2D)coordinate if (_providerType == LuggMapViewProvider::Apple) { _provider = _staticMode ? [[AppleStaticMapProvider alloc] init] : [[AppleMapProvider alloc] init]; +#if LUGG_GOOGLE_MAPS_AVAILABLE } else if (_staticMode) { GoogleStaticMapProvider *google = [[GoogleStaticMapProvider alloc] init]; google.mapId = _mapId; @@ -338,6 +342,14 @@ - (void)initializeProviderWithCoordinate:(CLLocationCoordinate2D)coordinate google.mapId = _mapId; _provider = google; } +#else + } else { + NSLog(@"[LuggMaps] provider=\"google\" requested but the Google Maps SDK " + @"is excluded ($LuggMapsGoogleEnabled = false); using Apple Maps"); + _provider = _staticMode ? [[AppleStaticMapProvider alloc] init] + : [[AppleMapProvider alloc] init]; + } +#endif _provider.delegate = self; _provider.staticMode = _staticMode; From c44430e4def284b0d585d508b277b2ff21448daf Mon Sep 17 00:00:00 2001 From: Jakob Johansson Date: Fri, 11 Sep 2026 11:48:49 +0200 Subject: [PATCH 2/4] feat: add GMS exclusion option to Expo prebuilds --- docs/content/docs/installation.mdx | 16 ++++++++++++++++ plugin/src/index.ts | 19 ++++++++++++++++--- plugin/src/withLuggMapsIOS.ts | 15 ++++++++++++++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/installation.mdx b/docs/content/docs/installation.mdx index 13f49a1..5b32cfd 100644 --- a/docs/content/docs/installation.mdx +++ b/docs/content/docs/installation.mdx @@ -38,6 +38,22 @@ Apple Maps works out of the box on iOS. Google Maps requires an API key on every ```sh npx expo prebuild --clean ``` + + #### Apple Maps only + + If you only use `provider="apple"` on iOS, set `iosGoogleMapsEnabled` to `false` to skip the Google Maps SDK entirely (it adds roughly 10 MB to the app). The plugin writes `$LuggMapsGoogleEnabled = false` to your `Podfile` and `iosGoogleMapsApiKey` is ignored: + + ```json title="app.json" + [ + "@lugg/maps", + { + "iosGoogleMapsEnabled": false, + "androidGoogleMapsApiKey": "YOUR_ANDROID_API_KEY" + } + ] + ``` + + With the SDK excluded, a `MapView` requesting `provider="google"` falls back to Apple Maps and logs a warning. ### iOS diff --git a/plugin/src/index.ts b/plugin/src/index.ts index bcc2879..f121cb0 100644 --- a/plugin/src/index.ts +++ b/plugin/src/index.ts @@ -17,12 +17,25 @@ export interface MapsPluginProps { * Required for Android as it only supports Google Maps. */ androidGoogleMapsApiKey?: string; + + /** + * Whether to link the Google Maps SDK on iOS. Set to `false` when only + * Apple Maps is used to drop the SDK from the app. Defaults to `true`. + */ + iosGoogleMapsEnabled?: boolean; } const withMaps: ConfigPlugin = (config, props = {}) => { - const { iosGoogleMapsApiKey, androidGoogleMapsApiKey } = props ?? {}; - - config = withLuggMapsIOS(config, { apiKey: iosGoogleMapsApiKey }); + const { + iosGoogleMapsApiKey, + androidGoogleMapsApiKey, + iosGoogleMapsEnabled = true, + } = props ?? {}; + + config = withLuggMapsIOS(config, { + apiKey: iosGoogleMapsApiKey, + googleEnabled: iosGoogleMapsEnabled, + }); config = withLuggMapsAndroid(config, { apiKey: androidGoogleMapsApiKey }); return config; diff --git a/plugin/src/withLuggMapsIOS.ts b/plugin/src/withLuggMapsIOS.ts index 7b30701..b974c0b 100644 --- a/plugin/src/withLuggMapsIOS.ts +++ b/plugin/src/withLuggMapsIOS.ts @@ -2,16 +2,29 @@ import { type ConfigPlugin, withInfoPlist, withAppDelegate, + withPodfile, } from '@expo/config-plugins'; export interface MapsIOSPluginProps { apiKey?: string; + googleEnabled?: boolean; } +const GMS_EXCLUSION_PODFILE_FLAG = '$LuggMapsGoogleEnabled = false'; + export const withLuggMapsIOS: ConfigPlugin = ( config, - { apiKey } + { apiKey, googleEnabled = true } ) => { + if (!googleEnabled) { + return withPodfile(config, (c) => { + if (!c.modResults.contents.includes(GMS_EXCLUSION_PODFILE_FLAG)) { + c.modResults.contents = `${GMS_EXCLUSION_PODFILE_FLAG}\n${c.modResults.contents}`; + } + return c; + }); + } + if (!apiKey) { return config; } From c64061f1629f5e9156644c14585d6c88b577087e Mon Sep 17 00:00:00 2001 From: Jovanni Lo Date: Sat, 12 Sep 2026 07:33:18 +0800 Subject: [PATCH 3/4] fix: handle iOS Google Maps exclusion consistently --- LuggMaps.podspec | 3 + docs/content/docs/installation.mdx | 12 ++- ios/LuggMapView.mm | 7 +- plugin/__tests__/withLuggMapsIOS.test.ts | 128 +++++++++++++++++++++++ plugin/src/withLuggMapsIOS.ts | 35 +++++-- 5 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 plugin/__tests__/withLuggMapsIOS.test.ts diff --git a/LuggMaps.podspec b/LuggMaps.podspec index 5ffd74a..3214ade 100644 --- a/LuggMaps.podspec +++ b/LuggMaps.podspec @@ -18,6 +18,9 @@ Pod::Spec.new do |s| # `$LuggMapsGoogleEnabled = false` in the Podfile drops the Google Maps SDK (Apple Maps only) google_enabled = defined?($LuggMapsGoogleEnabled) ? $LuggMapsGoogleEnabled : true + s.pod_target_xcconfig = { + "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) LUGG_GOOGLE_MAPS_ENABLED=#{google_enabled ? 1 : 0}" + } if google_enabled s.dependency "GoogleMaps" else diff --git a/docs/content/docs/installation.mdx b/docs/content/docs/installation.mdx index 5b32cfd..dd605ab 100644 --- a/docs/content/docs/installation.mdx +++ b/docs/content/docs/installation.mdx @@ -41,7 +41,7 @@ Apple Maps works out of the box on iOS. Google Maps requires an API key on every #### Apple Maps only - If you only use `provider="apple"` on iOS, set `iosGoogleMapsEnabled` to `false` to skip the Google Maps SDK entirely (it adds roughly 10 MB to the app). The plugin writes `$LuggMapsGoogleEnabled = false` to your `Podfile` and `iosGoogleMapsApiKey` is ignored: + If you only use Apple Maps on iOS, set `iosGoogleMapsEnabled` to `false` to exclude the Google Maps SDK: ```json title="app.json" [ @@ -53,6 +53,10 @@ Apple Maps works out of the box on iOS. Google Maps requires an API key on every ] ``` + The plugin ignores `iosGoogleMapsApiKey` and removes the Google Maps initialization from `AppDelegate.swift`. + After changing this option, run `npx expo prebuild --platform ios` and rebuild your app. + To enable Google Maps again, set `iosGoogleMapsEnabled` to `true` and provide `iosGoogleMapsApiKey`. + With the SDK excluded, a `MapView` requesting `provider="google"` falls back to Apple Maps and logs a warning. @@ -75,12 +79,16 @@ Apple Maps works out of the box on iOS. Google Maps requires an API key on every #### Apple Maps only - If you only use `provider="apple"` on iOS, you can skip the Google Maps SDK entirely (it adds roughly 10 MB to the app). Set this at the top of your `Podfile` before running `pod install`: + If you only use Apple Maps on iOS, add this flag at the top of your `Podfile`: ```ruby title="Podfile" $LuggMapsGoogleEnabled = false ``` + Remove `import GoogleMaps` and `GMSServices.provideAPIKey(...)` from `AppDelegate.swift`. + Then run `pod install` and rebuild your app. + To enable Google Maps again, remove the flag and restore the API key initialization before running `pod install`. + With the SDK excluded, a `MapView` requesting `provider="google"` falls back to Apple Maps and logs a warning. ### Android diff --git a/ios/LuggMapView.mm b/ios/LuggMapView.mm index 16adcef..2147237 100644 --- a/ios/LuggMapView.mm +++ b/ios/LuggMapView.mm @@ -8,8 +8,7 @@ #import "LuggTileOverlayView.h" #import "core/AppleMapProvider.h" #import "core/AppleStaticMapProvider.h" -#if __has_include() -#define LUGG_GOOGLE_MAPS_AVAILABLE 1 +#if LUGG_GOOGLE_MAPS_ENABLED #import "core/GoogleMapProvider.h" #import "core/GoogleStaticMapProvider.h" #endif @@ -332,7 +331,7 @@ - (void)initializeProviderWithCoordinate:(CLLocationCoordinate2D)coordinate if (_providerType == LuggMapViewProvider::Apple) { _provider = _staticMode ? [[AppleStaticMapProvider alloc] init] : [[AppleMapProvider alloc] init]; -#if LUGG_GOOGLE_MAPS_AVAILABLE +#if LUGG_GOOGLE_MAPS_ENABLED } else if (_staticMode) { GoogleStaticMapProvider *google = [[GoogleStaticMapProvider alloc] init]; google.mapId = _mapId; @@ -629,7 +628,7 @@ - (void)reload { _provider = nil; _initialized = NO; [self initializeProviderWithCoordinate:coordinate zoom:zoom]; - if (_providerType == LuggMapViewProvider::Apple) { + if ([_provider isKindOfClass:[AppleMapProvider class]]) { // The captured camera already includes the previous inset offset. [_provider moveCamera:coordinate.latitude longitude:coordinate.longitude diff --git a/plugin/__tests__/withLuggMapsIOS.test.ts b/plugin/__tests__/withLuggMapsIOS.test.ts new file mode 100644 index 0000000..a3138bf --- /dev/null +++ b/plugin/__tests__/withLuggMapsIOS.test.ts @@ -0,0 +1,128 @@ +import type { + ExportedConfig, + InfoPlist, + IOSConfig, + Mod, +} from '@expo/config-plugins'; + +import { + type MapsIOSPluginProps, + withLuggMapsIOS, +} from '../src/withLuggMapsIOS'; + +const nativeFiles = { + podfile: "platform :ios, '15.1'\n", + appDelegate: `import Expo +class AppDelegate { + func application(_ application: UIApplication) -> Bool { + return true + } +} +`, + infoPlist: { CFBundleDisplayName: 'Maps' } as InfoPlist, +}; + +async function runMod(mod: Mod | undefined, modResults: T) { + if (!mod) { + return modResults; + } + + const config = { name: 'Maps', slug: 'maps' }; + const result = await mod({ + ...config, + modResults, + modRawConfig: config, + modRequest: { + projectRoot: '/tmp/maps', + platformProjectRoot: '/tmp/maps/ios', + platform: 'ios', + modName: 'test', + introspect: false, + }, + }); + return result.modResults; +} + +async function applyPlugin(props: MapsIOSPluginProps, files = nativeFiles) { + const config: ExportedConfig = withLuggMapsIOS( + { name: 'Maps', slug: 'maps' }, + props + ); + const mods = config.mods?.ios; + const podfileMod = ( + mods as { podfile?: Mod } + )?.podfile; + const podfile = await runMod(podfileMod, { + path: '/tmp/maps/ios/Podfile', + language: 'rb' as const, + contents: files.podfile, + }); + const appDelegate = await runMod(mods?.appDelegate, { + path: '/tmp/maps/ios/AppDelegate.swift', + language: 'swift' as const, + contents: files.appDelegate, + }); + const infoPlist = await runMod(mods?.infoPlist, { ...files.infoPlist }); + return { + podfile: podfile.contents, + appDelegate: appDelegate.contents, + infoPlist, + }; +} + +it('keeps Google Maps enabled by default', async () => { + const files = await applyPlugin({ apiKey: 'test-key' }); + expect(files.podfile).toBe(nativeFiles.podfile); + expect(files.appDelegate).toContain('import GoogleMaps'); + expect(files.appDelegate).toContain('GMSServices.provideAPIKey("test-key")'); + expect(files.infoPlist.GMSApiKey).toBe('test-key'); +}); + +it('excludes Google Maps on a fresh project even when an API key is provided', async () => { + const files = await applyPlugin({ googleEnabled: false, apiKey: 'test-key' }); + expect(files.podfile).toBe( + `$LuggMapsGoogleEnabled = false\n${nativeFiles.podfile}` + ); + expect(files.appDelegate).toBe(nativeFiles.appDelegate); + expect(files.infoPlist).toEqual(nativeFiles.infoPlist); +}); + +it('removes Google Maps initialization when disabling an existing project', async () => { + const enabled = await applyPlugin({ apiKey: 'test-key' }); + const disabled = await applyPlugin({ googleEnabled: false }, enabled); + expect(disabled.podfile).toContain('$LuggMapsGoogleEnabled = false'); + expect(disabled.appDelegate).toBe(nativeFiles.appDelegate); + expect(disabled.infoPlist).toEqual(nativeFiles.infoPlist); +}); + +it.each([true, undefined])( + 'restores Google Maps after disabling it (googleEnabled=%s)', + async (googleEnabled) => { + const disabled = await applyPlugin({ googleEnabled: false }); + const enabled = await applyPlugin( + { googleEnabled, apiKey: 'test-key' }, + disabled + ); + expect(enabled.podfile).toBe(nativeFiles.podfile); + expect(enabled.appDelegate).toContain('import GoogleMaps'); + expect(enabled.appDelegate).toContain( + 'GMSServices.provideAPIKey("test-key")' + ); + expect(enabled.infoPlist.GMSApiKey).toBe('test-key'); + } +); + +it('removes the exclusion flag without requiring an API key', async () => { + const disabled = await applyPlugin({ googleEnabled: false }); + const enabled = await applyPlugin({ googleEnabled: true }, disabled); + expect(enabled).toEqual(nativeFiles); +}); + +it.each([true, false])( + 'can run repeatedly without changing the result (googleEnabled=%s)', + async (googleEnabled) => { + const props = { googleEnabled, apiKey: 'test-key' }; + const files = await applyPlugin(props); + expect(await applyPlugin(props, files)).toEqual(files); + } +); diff --git a/plugin/src/withLuggMapsIOS.ts b/plugin/src/withLuggMapsIOS.ts index b974c0b..0da4bde 100644 --- a/plugin/src/withLuggMapsIOS.ts +++ b/plugin/src/withLuggMapsIOS.ts @@ -16,25 +16,38 @@ export const withLuggMapsIOS: ConfigPlugin = ( config, { apiKey, googleEnabled = true } ) => { - if (!googleEnabled) { - return withPodfile(config, (c) => { - if (!c.modResults.contents.includes(GMS_EXCLUSION_PODFILE_FLAG)) { - c.modResults.contents = `${GMS_EXCLUSION_PODFILE_FLAG}\n${c.modResults.contents}`; - } - return c; - }); - } + config = withPodfile(config, (c) => { + const contents = c.modResults.contents.replace( + /^\$LuggMapsGoogleEnabled = false\r?\n/gm, + '' + ); + c.modResults.contents = googleEnabled + ? contents + : `${GMS_EXCLUSION_PODFILE_FLAG}\n${contents}`; + return c; + }); - if (!apiKey) { + if (googleEnabled && !apiKey) { return config; } config = withInfoPlist(config, (c) => { - c.modResults.GMSApiKey = apiKey; + if (googleEnabled) { + c.modResults.GMSApiKey = apiKey; + } else { + delete c.modResults.GMSApiKey; + } return c; }); config = withAppDelegate(config, (c) => { + if (!googleEnabled) { + c.modResults.contents = c.modResults.contents + .replace(/^[ \t]*import GoogleMaps\r?\n/gm, '') + .replace(/^[ \t]*GMSServices\.provideAPIKey\("[^"\r\n]*"\)\r?\n/gm, ''); + return c; + } + const contents = c.modResults.contents; // Add import for GoogleMaps @@ -49,7 +62,7 @@ export const withLuggMapsIOS: ConfigPlugin = ( if (!c.modResults.contents.includes('GMSServices.provideAPIKey')) { c.modResults.contents = c.modResults.contents.replace( /(func application\([^)]+\)[^{]*\{)/, - `$1\n GMSServices.provideAPIKey("${apiKey}")\n` + `$1\n GMSServices.provideAPIKey("${apiKey}")` ); } From d27e5df4fdb4a78a8424acf60f5e6befec6fe610 Mon Sep 17 00:00:00 2001 From: Jovanni Lo Date: Sat, 12 Sep 2026 08:15:24 +0800 Subject: [PATCH 4/4] chore: log when iOS Google Maps SDK is disabled --- LuggMaps.podspec | 4 ++++ plugin/src/withLuggMapsIOS.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/LuggMaps.podspec b/LuggMaps.podspec index 3214ade..f7ddefb 100644 --- a/LuggMaps.podspec +++ b/LuggMaps.podspec @@ -24,6 +24,10 @@ Pod::Spec.new do |s| if google_enabled s.dependency "GoogleMaps" else + unless defined?($LuggMapsGoogleDisabledLogged) + Pod::UI.puts "[LuggMaps] Google Maps SDK is disabled, Apple Maps only.".yellow + $LuggMapsGoogleDisabledLogged = true + end s.exclude_files = "ios/core/Google*", "ios/core/GMS*" end s.frameworks = "MapKit" diff --git a/plugin/src/withLuggMapsIOS.ts b/plugin/src/withLuggMapsIOS.ts index 0da4bde..227eed7 100644 --- a/plugin/src/withLuggMapsIOS.ts +++ b/plugin/src/withLuggMapsIOS.ts @@ -17,6 +17,10 @@ export const withLuggMapsIOS: ConfigPlugin = ( { apiKey, googleEnabled = true } ) => { config = withPodfile(config, (c) => { + if (!googleEnabled) { + console.log('[LuggMaps] Google Maps SDK is disabled, Apple Maps only.'); + } + const contents = c.modResults.contents.replace( /^\$LuggMapsGoogleEnabled = false\r?\n/gm, ''