Skip to content

Commit f7c1b1b

Browse files
committed
feat(ios)!: integrate native dependencies via Swift Package Manager
GoogleMaps, GoogleMapsUtils and SVGKit are now resolved via SPM through react-native's spm_dependency helper instead of CocoaPods, which makes the svgkit patch script obsolete. The pods have to be installed as frameworks, the Expo config plugin sets `ios.useFrameworks` to `dynamic` and removes the svgkit block generated by previous plugin versions from the Podfile. BREAKING CHANGE: Requires react-native >= 0.87.0. Bare RN iOS apps must install pods as frameworks (`use_frameworks!`, either `:dynamic` or `:static`) and remove the svgkit patch from their Podfile (`require_relative '.../scripts/svgkit_patch'` and `apply_svgkit_patch(installer)`). Expo apps are migrated by the config plugin.
1 parent 20c3a43 commit f7c1b1b

26 files changed

Lines changed: 1475 additions & 1407 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![Dev Release](https://img.shields.io/npm/v/react-native-google-maps-plus/dev.svg?label=dev%20release&color=orange)](https://www.npmjs.com/package/react-native-google-maps-plus)
55
[![Build](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml/badge.svg)](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml)
66
[![API Docs](https://img.shields.io/static/v1?label=typedoc&message=docs&color=informational)](https://pinpong.github.io/react-native-google-maps-plus)
7-
![React Native](https://img.shields.io/badge/react--native-%3E%3D0.82.0-61dafb.svg?logo=react)
7+
![React Native](https://img.shields.io/badge/react--native-%3E%3D0.87.0-61dafb.svg?logo=react)
88

99
React Native wrapper for Android & iOS Google Maps SDK with Street View support
1010

RNGoogleMapsPlus.podspec

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,21 @@ Pod::Spec.new do |s|
2424
s.dependency 'React-jsi'
2525
s.dependency 'React-callinvoker'
2626

27-
s.dependency 'GoogleMaps', '10.15.0'
28-
s.dependency 'Google-Maps-iOS-Utils', '7.0.0'
29-
s.dependency 'SVGKit', '3.0.0'
27+
spm_dependency(s,
28+
url: 'https://github.com/googlemaps/ios-maps-sdk',
29+
requirement: { kind: 'exactVersion', version: '10.15.0' },
30+
products: ['GoogleMaps']
31+
)
32+
spm_dependency(s,
33+
url: 'https://github.com/googlemaps/google-maps-ios-utils',
34+
requirement: { kind: 'exactVersion', version: '7.0.0' },
35+
products: ['GoogleMapsUtils']
36+
)
37+
spm_dependency(s,
38+
url: 'https://github.com/SVGKit/SVGKit',
39+
requirement: { kind: 'exactVersion', version: '3.0.0' },
40+
products: ['SVGKit']
41+
)
3042

3143
load 'nitrogen/generated/ios/RNGoogleMapsPlus+autolinking.rb'
3244
add_nitrogen_files(s)

__tests__/expoConfig/__snapshots__/withIosGoogleMapsPlus.test.ts.snap

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,6 @@ target 'HelloWorld' do
145145
)
146146
147147
post_install do |installer|
148-
# @generated begin react-native-google-maps-svgkit-patch - expo prebuild (DO NOT MODIFY) sync-ed16ec228929e9f6bcce5f8654ab58fb1bdadeea
149-
require File.join(File.dirname(\`node --print "require.resolve('react-native-google-maps-plus/package.json')"\`), 'scripts', 'svgkit_patch')
150-
apply_svgkit_patch(installer)
151-
# @generated end react-native-google-maps-svgkit-patch
152148
react_native_post_install(
153149
installer,
154150
config[:reactNativePath],

__tests__/expoConfig/withIosGoogleMapsPlus.test.ts

Lines changed: 86 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jest.mock('@expo/config-plugins', () => {
2121
...actual,
2222
withInfoPlist: applyMod('infoPlist'),
2323
withPodfile: applyMod('podfile'),
24+
withPodfileProperties: applyMod('podfileProperties'),
2425
withAppDelegate: applyMod('appDelegate'),
2526
};
2627
});
@@ -30,6 +31,30 @@ const PROPS: RNGoogleMapsPlusExpoPluginProps = {
3031
googleMapsIosApiKey: 'ios-key',
3132
};
3233

34+
const LEGACY_PODFILE_BLOCK = `# @generated begin react-native-google-maps-svgkit-patch - expo prebuild (DO NOT MODIFY) sync-ed16ec228929e9f6bcce5f8654ab58fb1bdadeea
35+
require File.join(File.dirname(\`node --print "require.resolve('react-native-google-maps-plus/package.json')"\`), 'scripts', 'svgkit_patch')
36+
apply_svgkit_patch(installer)
37+
# @generated end react-native-google-maps-svgkit-patch
38+
`;
39+
40+
const LEGACY_MODULAR_HEADERS_BLOCK = `# @generated begin react-native-google-maps-modular-headers - expo prebuild (DO NOT MODIFY) sync-b7c1d0c0f4d0a3e1e0f2a0d6f1c4c1b0d1a2e3f4
41+
use_modular_headers!
42+
# @generated end react-native-google-maps-modular-headers
43+
`;
44+
45+
const LEGACY_APP_DELEGATE_IMPORT = `// @generated begin react-native-google-maps-import - expo prebuild (DO NOT MODIFY) sync-3b9e722debd3c073b9705963208f8121ec9f576c
46+
import GoogleMaps
47+
// @generated end react-native-google-maps-import
48+
`;
49+
50+
const LEGACY_APP_DELEGATE_INIT = `// @generated begin react-native-google-maps-init - expo prebuild (DO NOT MODIFY) sync-ed7def55dfd1b52fb685aa33a269aeb34fc6ab13
51+
52+
if let apiKey = Bundle.main.object(forInfoDictionaryKey: "MAPS_API_KEY") as? String {
53+
GMSServices.provideAPIKey(apiKey)
54+
}
55+
// @generated end react-native-google-maps-init
56+
`;
57+
3358
const LEGACY_APP_DELEGATE = `import UIKit
3459
import React
3560
@@ -45,13 +70,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
4570

4671
type PluginMods = {
4772
infoPlist: Record<string, unknown>;
73+
podfileProperties: Record<string, string>;
4874
podfile: { path: string; language: string; contents: string };
4975
appDelegate: { path: string; language: string; contents: string };
5076
};
5177

5278
type ApplyOptions = {
5379
props?: Partial<RNGoogleMapsPlusExpoPluginProps>;
5480
infoPlist?: Record<string, unknown>;
81+
podfileProperties?: Record<string, string>;
5582
podfile?: string;
5683
appDelegate?: string;
5784
appDelegateLanguage?: string;
@@ -64,6 +91,7 @@ function readFixture(name: string): string {
6491
function applyPlugin({
6592
props,
6693
infoPlist = {},
94+
podfileProperties = {},
6795
podfile = readFixture('Podfile'),
6896
appDelegate = readFixture('AppDelegate.swift'),
6997
appDelegateLanguage = 'swift',
@@ -73,6 +101,7 @@ function applyPlugin({
73101
slug: 'example',
74102
__mods: {
75103
infoPlist,
104+
podfileProperties,
76105
podfile: { path: 'ios/Podfile', language: 'rb', contents: podfile },
77106
appDelegate: {
78107
path: 'ios/AppDelegate.swift',
@@ -92,9 +121,9 @@ function reapply(mods: PluginMods, props?: ApplyOptions['props']): PluginMods {
92121
return applyPlugin({
93122
props,
94123
infoPlist: mods.infoPlist,
124+
podfileProperties: mods.podfileProperties,
95125
podfile: mods.podfile.contents,
96126
appDelegate: mods.appDelegate.contents,
97-
appDelegateLanguage: mods.appDelegate.language,
98127
});
99128
}
100129

@@ -137,6 +166,23 @@ describe('withIosGoogleMapsPlus', () => {
137166
});
138167
});
139168

169+
describe('Podfile.properties.json', () => {
170+
it('opts into dynamically linked frameworks', () => {
171+
const { podfileProperties } = applyPlugin({ props: PROPS });
172+
173+
expect(podfileProperties['ios.useFrameworks']).toBe('dynamic');
174+
});
175+
176+
it('keeps a linkage the app already chose', () => {
177+
const { podfileProperties } = applyPlugin({
178+
props: PROPS,
179+
podfileProperties: { 'ios.useFrameworks': 'static' },
180+
});
181+
182+
expect(podfileProperties['ios.useFrameworks']).toBe('static');
183+
});
184+
});
185+
140186
describe('Podfile', () => {
141187
it('adds use_modular_headers! after the platform line', () => {
142188
const { contents } = applyPlugin({ props: PROPS }).podfile;
@@ -147,54 +193,18 @@ describe('withIosGoogleMapsPlus', () => {
147193
);
148194
});
149195

150-
it('injects the svgkit patch into the existing post_install block', () => {
151-
const { contents } = applyPlugin({ props: PROPS }).podfile;
152-
153-
expect(contents).toContain(
154-
`require File.join(File.dirname(\`node --print "require.resolve('react-native-google-maps-plus/package.json')"\`), 'scripts', 'svgkit_patch')`
155-
);
156-
expect(count(contents, 'post_install do |installer|')).toBe(1);
157-
expect(contents.indexOf('apply_svgkit_patch(installer)')).toBeGreaterThan(
158-
contents.indexOf('post_install do |installer|')
196+
it('keeps a single use_modular_headers! when the app already has one', () => {
197+
const legacy = readFixture('Podfile').replace(
198+
/^(platform\s+:ios.*\n)/m,
199+
`$1${LEGACY_MODULAR_HEADERS_BLOCK}`
159200
);
160-
});
161201

162-
it('places the patch outside a nested if/end block from another plugin', () => {
163-
const nestedEndPodfile = `platform :ios, '16.0'
164-
165-
target 'Example' do
166-
post_install do |installer|
167-
if ENV['CI'] == 'true'
168-
puts 'ci run'
169-
end
170-
react_native_post_install(installer)
171-
end
172-
end
173-
`;
174202
const { contents } = applyPlugin({
175203
props: PROPS,
176-
podfile: nestedEndPodfile,
204+
podfile: legacy,
177205
}).podfile;
178206

179-
expect(count(contents, 'apply_svgkit_patch(installer)')).toBe(1);
180-
expect(contents.indexOf('apply_svgkit_patch(installer)')).toBeLessThan(
181-
contents.indexOf("if ENV['CI']")
182-
);
183-
});
184-
185-
it('appends a post_install block when the Podfile has none', () => {
186-
const minimalPodfile = `platform :ios, '16.0'
187-
188-
target 'Example' do
189-
end
190-
`;
191-
const { contents } = applyPlugin({
192-
props: PROPS,
193-
podfile: minimalPodfile,
194-
}).podfile;
195-
196-
expect(count(contents, 'post_install do |installer|')).toBe(1);
197-
expect(contents).toContain('apply_svgkit_patch(installer)');
207+
expect(contents.match(/use_modular_headers!/g)).toHaveLength(1);
198208
});
199209

200210
it('is idempotent when applied twice', () => {
@@ -204,19 +214,20 @@ end
204214
expect(second.podfile.contents).toBe(first.podfile.contents);
205215
});
206216

207-
it('stays idempotent when the first run had to append the post_install block', () => {
208-
const minimalPodfile = `platform :ios, '16.0'
217+
it('removes the svgkit patch block generated by previous versions', () => {
218+
const legacy = readFixture('Podfile').replace(
219+
'post_install do |installer|\n',
220+
`post_install do |installer|\n${LEGACY_PODFILE_BLOCK}`
221+
);
222+
expect(legacy).toContain('apply_svgkit_patch(installer)');
209223

210-
target 'Example' do
211-
end
212-
`;
213-
const first = applyPlugin({ props: PROPS, podfile: minimalPodfile });
214-
const second = reapply(first, PROPS);
224+
const { contents } = applyPlugin({
225+
props: PROPS,
226+
podfile: legacy,
227+
}).podfile;
215228

216-
expect(second.podfile.contents).toBe(first.podfile.contents);
217-
expect(
218-
count(first.podfile.contents, 'apply_svgkit_patch(installer)')
219-
).toBe(1);
229+
expect(contents).not.toContain('svgkit_patch');
230+
expect(contents).not.toContain('react-native-google-maps-svgkit-patch');
220231
});
221232

222233
it('matches the snapshot for the current Expo template', () => {
@@ -283,6 +294,27 @@ end
283294
);
284295
});
285296

297+
it('does not duplicate the blocks generated by previous versions', () => {
298+
const legacy = readFixture('AppDelegate.swift')
299+
.replace(
300+
'import React\n',
301+
`import React\n${LEGACY_APP_DELEGATE_IMPORT}`
302+
)
303+
.replace(
304+
/^(\s*return super\.application\()/m,
305+
`${LEGACY_APP_DELEGATE_INIT}$1`
306+
);
307+
308+
const { contents } = applyPlugin({
309+
props: PROPS,
310+
appDelegate: legacy,
311+
}).appDelegate;
312+
313+
expect(count(contents, 'import GoogleMaps')).toBe(1);
314+
expect(count(contents, 'GMSServices.provideAPIKey')).toBe(1);
315+
expect(contents).toBe(applyPlugin({ props: PROPS }).appDelegate.contents);
316+
});
317+
286318
it('matches the snapshot for the current Expo template', () => {
287319
const { contents } = applyPlugin({ props: PROPS }).appDelegate;
288320
expect(contents).toMatchSnapshot();

android/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def getExtOrIntegerDefault(name) {
3737

3838
android {
3939
namespace "com.rngooglemapsplus"
40+
ndkVersion getExtOrDefault("ndkVersion")
4041

4142
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
4243

android/gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
RNGoogleMapsPlus_kotlinVersion=2.3.0
22
RNGoogleMapsPlus_minSdkVersion=26
3-
RNGoogleMapsPlus_targetSdkVersion=36
4-
RNGoogleMapsPlus_compileSdkVersion=36
3+
RNGoogleMapsPlus_targetSdkVersion=37
4+
RNGoogleMapsPlus_compileSdkVersion=37
55
RNGoogleMapsPlus_ndkVersion=27.1.12297006
66
RNGoogleMapsPlus_googlePlayServicesBaseVersion=18.10.0
77
RNGoogleMapsPlus_googlePlayServicesMapsVersion=20.0.0

example/Gemfile.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ GEM
22
remote: https://rubygems.org/
33
specs:
44
CFPropertyList (3.0.8)
5-
activesupport (7.2.3.1)
5+
activesupport (7.2.3.2)
66
base64
77
benchmark (>= 0.3)
88
bigdecimal
@@ -22,7 +22,7 @@ GEM
2222
atomos (0.1.3)
2323
base64 (0.3.0)
2424
benchmark (0.5.0)
25-
bigdecimal (4.1.1)
25+
bigdecimal (4.1.2)
2626
claide (1.1.0)
2727
cocoapods (1.15.2)
2828
addressable (~> 2.8)
@@ -75,9 +75,9 @@ GEM
7575
gh_inspector (1.1.3)
7676
httpclient (2.9.0)
7777
mutex_m
78-
i18n (1.14.8)
78+
i18n (1.15.2)
7979
concurrent-ruby (~> 1.0)
80-
json (2.19.3)
80+
json (2.21.2)
8181
logger (1.7.0)
8282
minitest (5.27.0)
8383
molinillo (0.8.0)
@@ -120,13 +120,13 @@ DEPENDENCIES
120120

121121
CHECKSUMS
122122
CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261
123-
activesupport (7.2.3.1) sha256=11ebed516a43a0bb47346227a35ebae4d9427465a7c9eb197a03d5c8d283cb34
123+
activesupport (7.2.3.2) sha256=ddc90d11dd88086d78ace03407fe3c2a3f5c8853c3c3046313db5ed823ef3312
124124
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
125125
algoliasearch (1.27.5) sha256=26c1cddf3c2ec4bd60c148389e42702c98fdac862881dc6b07a4c0b89ffec853
126126
atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f
127127
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
128128
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
129-
bigdecimal (4.1.1) sha256=1c09efab961da45203c8316b0cdaec0ff391dfadb952dd459584b63ebf8054ca
129+
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
130130
claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e
131131
cocoapods (1.15.2) sha256=f0f5153de8d028d133b96f423e04f37fb97a1da0d11dda581a9f46c0cba4090a
132132
cocoapods-core (1.15.2) sha256=322650d97fe1ad4c0831a09669764b888bd91c6d79d0f6bb07281a17667a2136
@@ -147,8 +147,8 @@ CHECKSUMS
147147
fuzzy_match (2.0.4) sha256=b5de4f95816589c5b5c3ad13770c0af539b75131c158135b3f3bbba75d0cfca5
148148
gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939
149149
httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8
150-
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
151-
json (2.19.3) sha256=289b0bb53052a1fa8c34ab33cc750b659ba14a5c45f3fcf4b18762dc67c78646
150+
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
151+
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
152152
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
153153
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
154154
molinillo (0.8.0) sha256=efbff2716324e2a30bccd3eba1ff3a735f4d5d53ffddbc6a2f32c0ca9433045d

example/android/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ android {
104104
// see https://reactnative.dev/docs/signed-apk-android.
105105
signingConfig signingConfigs.debug
106106
minifyEnabled enableProguardInReleaseBuilds
107-
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
107+
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
108108
}
109109
}
110110
secrets {

example/android/build.gradle

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
buildscript {
22
ext {
3-
buildToolsVersion = "36.0.0"
4-
minSdkVersion = 24
5-
compileSdkVersion = 36
6-
targetSdkVersion = 36
3+
buildToolsVersion = "37.0.0"
4+
minSdkVersion = 26
5+
compileSdkVersion = 37
6+
targetSdkVersion = 37
77
ndkVersion = "27.1.12297006"
8-
kotlinVersion = "2.1.20"
8+
kotlinVersion = "2.2.0"
99
}
1010
repositories {
1111
google()

example/android/gradle.properties

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,9 @@ hermesEnabled=true
4141
# Use this property to enable edge-to-edge display support.
4242
# This allows your app to draw behind system bars for an immersive UI.
4343
# Note: Only works with ReactActivity and should not be used with custom Activity.
44-
edgeToEdgeEnabled=false
44+
edgeToEdgeEnabled=true
45+
46+
# Opt out of built-in kotlin and new DSL behavior that ships with AGP 9.
47+
# Starting from AGP 10.x these opt outs will be removed.
48+
android.builtInKotlin=false
49+
android.newDsl=false

0 commit comments

Comments
 (0)