Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion LuggMaps.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,20 @@ 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
s.pod_target_xcconfig = {
"GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) LUGG_GOOGLE_MAPS_ENABLED=#{google_enabled ? 1 : 0}"
}
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"

install_modules_dependencies(s)
Expand Down
34 changes: 34 additions & 0 deletions docs/content/docs/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ 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 Apple Maps on iOS, set `iosGoogleMapsEnabled` to `false` to exclude the Google Maps SDK:

```json title="app.json"
[
"@lugg/maps",
{
"iosGoogleMapsEnabled": false,
"androidGoogleMapsApiKey": "YOUR_ANDROID_API_KEY"
}
]
```

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.
</Tab>
<Tab value="Bare React Native">
### iOS
Expand All @@ -57,6 +77,20 @@ 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 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

Add your Google Maps API key to `AndroidManifest.xml`:
Expand Down
13 changes: 12 additions & 1 deletion ios/LuggMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
#import "LuggTileOverlayView.h"
#import "core/AppleMapProvider.h"
#import "core/AppleStaticMapProvider.h"
#if LUGG_GOOGLE_MAPS_ENABLED
#import "core/GoogleMapProvider.h"
#import "core/GoogleStaticMapProvider.h"
#endif
#import "core/MapProviderDelegate.h"
#import "events/CameraIdleEvent.h"
#import "events/CameraMoveEvent.h"
Expand Down Expand Up @@ -329,6 +331,7 @@ - (void)initializeProviderWithCoordinate:(CLLocationCoordinate2D)coordinate
if (_providerType == LuggMapViewProvider::Apple) {
_provider = _staticMode ? [[AppleStaticMapProvider alloc] init]
: [[AppleMapProvider alloc] init];
#if LUGG_GOOGLE_MAPS_ENABLED
} else if (_staticMode) {
GoogleStaticMapProvider *google = [[GoogleStaticMapProvider alloc] init];
google.mapId = _mapId;
Expand All @@ -338,6 +341,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;
Expand Down Expand Up @@ -617,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
Expand Down
128 changes: 128 additions & 0 deletions plugin/__tests__/withLuggMapsIOS.test.ts
Original file line number Diff line number Diff line change
@@ -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<T>(mod: Mod<T> | 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<IOSConfig.Paths.PodfileProjectFile> }
)?.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);
}
);
19 changes: 16 additions & 3 deletions plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MapsPluginProps | void> = (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;
Expand Down
38 changes: 34 additions & 4 deletions plugin/src/withLuggMapsIOS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,56 @@ 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<MapsIOSPluginProps> = (
config,
{ apiKey }
{ apiKey, googleEnabled = true }
) => {
if (!apiKey) {
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,
''
);
c.modResults.contents = googleEnabled
? contents
: `${GMS_EXCLUSION_PODFILE_FLAG}\n${contents}`;
return c;
});

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
Expand All @@ -36,7 +66,7 @@ export const withLuggMapsIOS: ConfigPlugin<MapsIOSPluginProps> = (
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}")`
);
}

Expand Down
Loading