From a23a7448ecf0e51e8264aab38a8fda27ba3347df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Thu, 4 Jun 2026 09:07:34 +0200 Subject: [PATCH 1/8] feat: introduce experimental KMP maps module wrapping Google Maps on Android and MapKit on iOS --- maps-compose-multiplatform/build.gradle.kts | 55 +++++++++++++++++++ .../compose/multiplatform/GoogleMap.kt | 40 ++++++++++++++ .../compose/multiplatform/GoogleMap.kt | 32 +++++++++++ .../compose/multiplatform/GoogleMap.kt | 51 +++++++++++++++++ settings.gradle.kts | 1 + 5 files changed, 179 insertions(+) create mode 100644 maps-compose-multiplatform/build.gradle.kts create mode 100644 maps-compose-multiplatform/src/androidMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt create mode 100644 maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt create mode 100644 maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt diff --git a/maps-compose-multiplatform/build.gradle.kts b/maps-compose-multiplatform/build.gradle.kts new file mode 100644 index 00000000..9eb0110d --- /dev/null +++ b/maps-compose-multiplatform/build.gradle.kts @@ -0,0 +1,55 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + kotlin("multiplatform") + id("com.android.kotlin.multiplatform.library") + alias(libs.plugins.compose.compiler) +} + +kotlin { + androidLibrary { + namespace = "com.google.maps.android.compose.multiplatform" + compileSdk = libs.versions.androidCompileSdk.get().toInt() + minSdk = libs.versions.androidMinSdk.get().toInt() + } + + // Enable iOS targets + iosX64() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain { + dependencies { + implementation("org.jetbrains.compose.runtime:runtime:1.7.3") + implementation("org.jetbrains.compose.foundation:foundation:1.7.3") + implementation("org.jetbrains.compose.ui:ui:1.7.3") + } + } + androidMain { + dependencies { + // Link the existing maps-compose module locally + api(project(":maps-compose")) + } + } + iosMain { + dependencies { + // Uses native MapKit via platform libraries + } + } + } +} diff --git a/maps-compose-multiplatform/src/androidMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt b/maps-compose-multiplatform/src/androidMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt new file mode 100644 index 00000000..d2696474 --- /dev/null +++ b/maps-compose-multiplatform/src/androidMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android.compose.multiplatform + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.compose.rememberCameraPositionState + +@Composable +public actual fun GoogleMap( + modifier: Modifier, + latitude: Double, + longitude: Double, + zoom: Float +) { + val cameraPositionState = rememberCameraPositionState { + position = CameraPosition.fromLatLngZoom(LatLng(latitude, longitude), zoom) + } + + com.google.maps.android.compose.GoogleMap( + modifier = modifier, + cameraPositionState = cameraPositionState + ) +} diff --git a/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt b/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt new file mode 100644 index 00000000..40043bcb --- /dev/null +++ b/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android.compose.multiplatform + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** + * A multiplatform Map Composable that renders Google Maps on Android + * and native Apple MapKit Map (MKMapView) on iOS. + */ +@Composable +public expect fun GoogleMap( + modifier: Modifier = Modifier, + latitude: Double, + longitude: Double, + zoom: Float = 10f +) diff --git a/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt b/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt new file mode 100644 index 00000000..5b578771 --- /dev/null +++ b/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android.compose.multiplatform + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.UIKitView +import kotlinx.cinterop.ExperimentalForeignApi +import kotlin.math.pow +import platform.CoreLocation.CLLocationCoordinate2DMake +import platform.MapKit.MKCoordinateRegionMake +import platform.MapKit.MKCoordinateSpanMake +import platform.MapKit.MKMapView + +@OptIn(ExperimentalForeignApi::class) +@Composable +public actual fun GoogleMap( + modifier: Modifier, + latitude: Double, + longitude: Double, + zoom: Float +) { + UIKitView( + factory = { + MKMapView() + }, + modifier = modifier, + update = { mapView -> + val center = CLLocationCoordinate2DMake(latitude, longitude) + // Approximate span calculation based on zoom level + val spanDelta = 360.0 / 2.0.pow(zoom.toDouble()) + val span = MKCoordinateSpanMake(spanDelta, spanDelta) + val region = MKCoordinateRegionMake(center, span) + mapView.setRegion(region, animated = true) + } + ) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 41cc0b7e..ed79228c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,4 +38,5 @@ include(":maps-app") include(":maps-compose") include(":maps-compose-widgets") include(":maps-compose-utils") +include(":maps-compose-multiplatform") include(":docs") \ No newline at end of file From c134512e72c24ba033ca3c2a27ccf2ad43101569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Thu, 4 Jun 2026 09:18:33 +0200 Subject: [PATCH 2/8] feat: migrate iOS multiplatform map implementation to native Google Maps iOS SDK via CocoaPods --- maps-compose-multiplatform/build.gradle.kts | 11 ++++++++++ .../compose/multiplatform/GoogleMap.kt | 20 ++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/maps-compose-multiplatform/build.gradle.kts b/maps-compose-multiplatform/build.gradle.kts index 9eb0110d..c721059e 100644 --- a/maps-compose-multiplatform/build.gradle.kts +++ b/maps-compose-multiplatform/build.gradle.kts @@ -16,6 +16,7 @@ plugins { kotlin("multiplatform") + kotlin("native.cocoapods") id("com.android.kotlin.multiplatform.library") alias(libs.plugins.compose.compiler) } @@ -32,6 +33,16 @@ kotlin { iosArm64() iosSimulatorArm64() + cocoapods { + summary = "Multiplatform Google Maps wrapper" + homepage = "https://github.com/googlemaps/android-maps-compose" + version = "1.0" + ios.deploymentTarget = "14.0" + pod("GoogleMaps") { + version = "9.0.0" + } + } + sourceSets { commonMain { dependencies { diff --git a/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt b/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt index 5b578771..1e8e2254 100644 --- a/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt +++ b/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt @@ -20,11 +20,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.UIKitView import kotlinx.cinterop.ExperimentalForeignApi -import kotlin.math.pow -import platform.CoreLocation.CLLocationCoordinate2DMake -import platform.MapKit.MKCoordinateRegionMake -import platform.MapKit.MKCoordinateSpanMake -import platform.MapKit.MKMapView +import kotlinx.cinterop.readValue +import platform.CoreGraphics.CGRectZero +import cocoapods.GoogleMaps.GMSMapView +import cocoapods.GoogleMaps.GMSCameraPosition @OptIn(ExperimentalForeignApi::class) @Composable @@ -36,16 +35,13 @@ public actual fun GoogleMap( ) { UIKitView( factory = { - MKMapView() + val camera = GMSCameraPosition.cameraWithLatitude(latitude, longitude, zoom) + GMSMapView.mapWithFrame(CGRectZero.readValue(), camera = camera) }, modifier = modifier, update = { mapView -> - val center = CLLocationCoordinate2DMake(latitude, longitude) - // Approximate span calculation based on zoom level - val spanDelta = 360.0 / 2.0.pow(zoom.toDouble()) - val span = MKCoordinateSpanMake(spanDelta, spanDelta) - val region = MKCoordinateRegionMake(center, span) - mapView.setRegion(region, animated = true) + val camera = GMSCameraPosition.cameraWithLatitude(latitude, longitude, zoom) + mapView.animateToCameraPosition(camera) } ) } From 8852240731ca865bfb715b23c0b0609311290c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Fri, 5 Jun 2026 17:35:55 +0200 Subject: [PATCH 3/8] feat: add interactive maps demos to iOS app & upgrade Google Maps SDK to 10.14.0 --- .gitignore | 1 + iosApp/.gitignore | 19 + iosApp/Podfile | 11 + iosApp/create_project.rb | 71 +++ iosApp/iosApp.xcodeproj/project.pbxproj | 418 ++++++++++++++++++ iosApp/iosApp/AppDelegate.swift | 25 ++ iosApp/iosApp/Info.plist | 34 ++ iosApp/iosApp/SampleListViewController.swift | 223 ++++++++++ maps-app/build.gradle.kts | 1 + maps-app/src/main/AndroidManifest.xml | 3 + .../com/google/maps/android/compose/Demo.kt | 5 + .../maps/android/compose/KmpMapActivity.kt | 49 ++ maps-app/src/main/res/values/strings.xml | 3 + maps-compose-multiplatform/build.gradle.kts | 23 +- .../maps_compose_multiplatform.podspec | 46 ++ .../compose/multiplatform/GoogleMap.kt | 46 +- .../compose/multiplatform/GoogleMap.kt | 26 +- .../compose/multiplatform/GoogleMap.kt | 120 ++++- 18 files changed, 1112 insertions(+), 12 deletions(-) create mode 100644 iosApp/.gitignore create mode 100644 iosApp/Podfile create mode 100644 iosApp/create_project.rb create mode 100644 iosApp/iosApp.xcodeproj/project.pbxproj create mode 100644 iosApp/iosApp/AppDelegate.swift create mode 100644 iosApp/iosApp/Info.plist create mode 100644 iosApp/iosApp/SampleListViewController.swift create mode 100644 maps-app/src/main/java/com/google/maps/android/compose/KmpMapActivity.kt create mode 100644 maps-compose-multiplatform/maps_compose_multiplatform.podspec diff --git a/.gitignore b/.gitignore index a77c626a..a7baf6c5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ secrets.properties # This covers new IDEs, like Antigravity .vscode/ +.antigravitycli/ build-logic/**/bin/ diff --git a/iosApp/.gitignore b/iosApp/.gitignore new file mode 100644 index 00000000..5d5a012c --- /dev/null +++ b/iosApp/.gitignore @@ -0,0 +1,19 @@ +# CocoaPods +Pods/ +Podfile.lock + +# Xcode +build/ +DerivedData/ +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 +xcuserdata/ +*.xcworkspace +!default.xcworkspace +*.xcuserstate + +# Secrets +iosApp/DeveloperSecrets.swift + diff --git a/iosApp/Podfile b/iosApp/Podfile new file mode 100644 index 00000000..058583d9 --- /dev/null +++ b/iosApp/Podfile @@ -0,0 +1,11 @@ +platform :ios, '16.0' + +# Ignore warnings from CocoaPods dependencies +inhibit_all_warnings! + +target 'iosApp' do + use_frameworks! + + # Reference our local KMP module via its podspec path + pod 'maps_compose_multiplatform', :path => '../maps-compose-multiplatform' +end diff --git a/iosApp/create_project.rb b/iosApp/create_project.rb new file mode 100644 index 00000000..f256eae3 --- /dev/null +++ b/iosApp/create_project.rb @@ -0,0 +1,71 @@ +require 'xcodeproj' + +# Initialize project +project_path = 'iosApp.xcodeproj' +project = Xcodeproj::Project.new(project_path) + +# Set deployment target to iOS 16.0 project-wide +project.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0' +end + +# Create group for source files (maps to the iosApp directory) +group = project.main_group.new_group('iosApp', 'iosApp') + +# Reference source files inside the group +app_delegate_ref = group.new_file('AppDelegate.swift') +sample_list_ref = group.new_file('SampleListViewController.swift') +secrets_ref = group.new_file('DeveloperSecrets.swift') +info_plist_ref = group.new_file('Info.plist') + +# Create target +target = project.new_target(:application, 'iosApp', :ios, '16.0') + +# Configure target settings +target.build_configurations.each do |config| + config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = 'com.google.maps.android.compose.iosApp' + config.build_settings['INFOPLIST_FILE'] = 'iosApp/Info.plist' + config.build_settings['SWIFT_VERSION'] = '5.0' + config.build_settings['SDKROOT'] = 'iphoneos' + config.build_settings['TARGETED_DEVICE_FAMILY'] = '1,2' + config.build_settings['CODE_SIGNING_REQUIRED'] = 'NO' + config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' + config.build_settings['AD_HOC_CODE_SIGNING_ALLOWED'] = 'YES' +end + +# Add a Build Phase Run Script to populate secrets before compilation +populate_secrets_phase = target.new_shell_script_build_phase('Populate Secrets') +populate_secrets_phase.shell_script = <<-SHELL +SECRETS_PATH="${PROJECT_DIR}/../secrets.properties" +OUTPUT_FILE="${SRCROOT}/iosApp/DeveloperSecrets.swift" + +API_KEY="YOUR_API_KEY" + +if [ -f "$SECRETS_PATH" ]; then + EXTRACTED_KEY=$(grep -E "^MAPS_API_KEY=" "$SECRETS_PATH" | cut -d'=' -f2 | tr -d '"' | tr -d "'") + if [ ! -z "$EXTRACTED_KEY" ]; then + API_KEY="$EXTRACTED_KEY" + fi +fi + +cat < "$OUTPUT_FILE" +// Generated file. Do not commit or modify. +struct DeveloperSecrets { + static let mapsApiKey = "$API_KEY" +} +EOF +SHELL + +# Move the secrets phase to the very beginning of target build phases +target.build_phases.delete(populate_secrets_phase) +target.build_phases.insert(0, populate_secrets_phase) + +# Add files to their respective build phases +source_build_phase = target.source_build_phase +source_build_phase.add_file_reference(app_delegate_ref) +source_build_phase.add_file_reference(sample_list_ref) +source_build_phase.add_file_reference(secrets_ref) + +project.save +puts "Xcode project created successfully!" + diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..e5873b99 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,418 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 09D299F7F5064B15DFF8647A /* SampleListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3C845D21837B44B6FED85F1 /* SampleListViewController.swift */; }; + 1937ECD4C944A623885CB9C3 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA2BC2B1BE1E2F248AA9D65E /* Pods_iosApp.framework */; }; + 7FE65D91E154BA74FB35FBAC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F1DCC9BB7B6BE074CB3D1D4F /* Foundation.framework */; }; + 81C277EB5057E02FF8B0CB5C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5293DC72FB4612B21EA96465 /* AppDelegate.swift */; }; + E997B8A8A2AFAD13A788A753 /* DeveloperSecrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EAB5F727C7CE0015606E231 /* DeveloperSecrets.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 18EAA545799221C4749B0BE6 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3EAB5F727C7CE0015606E231 /* DeveloperSecrets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DeveloperSecrets.swift; sourceTree = ""; }; + 5293DC72FB4612B21EA96465 /* AppDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + AFAE64BF1EC03DC7EE9905AF /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; + B78650FF6C736F3A8024670F /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; + E3C845D21837B44B6FED85F1 /* SampleListViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SampleListViewController.swift; sourceTree = ""; }; + EA2BC2B1BE1E2F248AA9D65E /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F1DCC9BB7B6BE074CB3D1D4F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + F37E4987EF58C0103DBE5753 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + ACD54AC92AF33CBA9038EF28 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7FE65D91E154BA74FB35FBAC /* Foundation.framework in Frameworks */, + 1937ECD4C944A623885CB9C3 /* Pods_iosApp.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 160662DC4B33BBB495406E79 /* Products */ = { + isa = PBXGroup; + children = ( + F37E4987EF58C0103DBE5753 /* iosApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 761B8220F805A3F9823B2474 /* iOS */ = { + isa = PBXGroup; + children = ( + F1DCC9BB7B6BE074CB3D1D4F /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; + 9A8CB19F0A8D45C35146683E = { + isa = PBXGroup; + children = ( + 160662DC4B33BBB495406E79 /* Products */, + AEC84CDA2E901AD18F65E5CF /* Frameworks */, + A8B082EA0D34BCDB9BC6A878 /* iosApp */, + B769C4D1E3A5E7DD227FDD0F /* Pods */, + ); + sourceTree = ""; + }; + A8B082EA0D34BCDB9BC6A878 /* iosApp */ = { + isa = PBXGroup; + children = ( + 5293DC72FB4612B21EA96465 /* AppDelegate.swift */, + E3C845D21837B44B6FED85F1 /* SampleListViewController.swift */, + 3EAB5F727C7CE0015606E231 /* DeveloperSecrets.swift */, + 18EAA545799221C4749B0BE6 /* Info.plist */, + ); + name = iosApp; + path = iosApp; + sourceTree = ""; + }; + AEC84CDA2E901AD18F65E5CF /* Frameworks */ = { + isa = PBXGroup; + children = ( + 761B8220F805A3F9823B2474 /* iOS */, + EA2BC2B1BE1E2F248AA9D65E /* Pods_iosApp.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + B769C4D1E3A5E7DD227FDD0F /* Pods */ = { + isa = PBXGroup; + children = ( + B78650FF6C736F3A8024670F /* Pods-iosApp.release.xcconfig */, + AFAE64BF1EC03DC7EE9905AF /* Pods-iosApp.debug.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5B40C8C8EA63318DDB4B739B /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0375DD885B6888B89AAE9BF9 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + A1495132AEB0B97D4FC21766 /* [CP] Check Pods Manifest.lock */, + 587CFD89D0C8586590AC06DD /* Populate Secrets */, + 854A57832A18FB33C8B65435 /* Sources */, + ACD54AC92AF33CBA9038EF28 /* Frameworks */, + 9FD86DF237A6365F05565BE6 /* Resources */, + 10D8D4B430DF1AE053CC752F /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + productName = iosApp; + productReference = F37E4987EF58C0103DBE5753 /* iosApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E8320DC81573CAF4F46DC0D7 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1600; + LastUpgradeCheck = 1600; + }; + buildConfigurationList = 12FCA89330D4CE38309E9B81 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 9A8CB19F0A8D45C35146683E; + minimizedProjectReferenceProxies = 0; + preferredProjectObjectVersion = 77; + productRefGroup = 160662DC4B33BBB495406E79 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5B40C8C8EA63318DDB4B739B /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 9FD86DF237A6365F05565BE6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 10D8D4B430DF1AE053CC752F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps/GoogleMapsResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 587CFD89D0C8586590AC06DD /* Populate Secrets */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Populate Secrets"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "SECRETS_PATH=\"${PROJECT_DIR}/../secrets.properties\"\nOUTPUT_FILE=\"${SRCROOT}/iosApp/DeveloperSecrets.swift\"\n\nAPI_KEY=\"YOUR_API_KEY\"\n\nif [ -f \"$SECRETS_PATH\" ]; then\n EXTRACTED_KEY=$(grep -E \"^MAPS_API_KEY=\" \"$SECRETS_PATH\" | cut -d'=' -f2 | tr -d '\"' | tr -d \"'\")\n if [ ! -z \"$EXTRACTED_KEY\" ]; then\n API_KEY=\"$EXTRACTED_KEY\"\n fi\nfi\n\ncat < \"$OUTPUT_FILE\"\n// Generated file. Do not commit or modify.\nstruct DeveloperSecrets {\n static let mapsApiKey = \"$API_KEY\"\n}\nEOF\n"; + }; + A1495132AEB0B97D4FC21766 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 854A57832A18FB33C8B65435 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81C277EB5057E02FF8B0CB5C /* AppDelegate.swift in Sources */, + 09D299F7F5064B15DFF8647A /* SampleListViewController.swift in Sources */, + E997B8A8A2AFAD13A788A753 /* DeveloperSecrets.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 368E878A47CC5CC47D624E79 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 79144F741B9018EB7ABCBD73 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AFAE64BF1EC03DC7EE9905AF /* Pods-iosApp.debug.xcconfig */; + buildSettings = { + AD_HOC_CODE_SIGNING_ALLOWED = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.maps.android.compose.iosApp; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A18BD2CB84E1A0278F561EB6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + AF9933E09D62868BBCC7C26A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B78650FF6C736F3A8024670F /* Pods-iosApp.release.xcconfig */; + buildSettings = { + AD_HOC_CODE_SIGNING_ALLOWED = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.maps.android.compose.iosApp; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0375DD885B6888B89AAE9BF9 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AF9933E09D62868BBCC7C26A /* Release */, + 79144F741B9018EB7ABCBD73 /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 12FCA89330D4CE38309E9B81 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 368E878A47CC5CC47D624E79 /* Debug */, + A18BD2CB84E1A0278F561EB6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = E8320DC81573CAF4F46DC0D7 /* Project object */; +} diff --git a/iosApp/iosApp/AppDelegate.swift b/iosApp/iosApp/AppDelegate.swift new file mode 100644 index 00000000..d206fe8f --- /dev/null +++ b/iosApp/iosApp/AppDelegate.swift @@ -0,0 +1,25 @@ +import UIKit +import GoogleMaps +import maps_compose_multiplatform + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + // Initialize Google Maps SDK dynamically. + GMSServices.provideAPIKey(DeveloperSecrets.mapsApiKey) + + window = UIWindow(frame: UIScreen.main.bounds) + let sampleListVC = SampleListViewController() + let navController = UINavigationController(rootViewController: sampleListVC) + + window?.rootViewController = navController + window?.makeKeyAndVisible() + + return true + } +} diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist new file mode 100644 index 00000000..37d0d4e6 --- /dev/null +++ b/iosApp/iosApp/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchScreen + + CADisableMinimumFrameDurationOnPhone + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/iosApp/iosApp/SampleListViewController.swift b/iosApp/iosApp/SampleListViewController.swift new file mode 100644 index 00000000..cc7a53a9 --- /dev/null +++ b/iosApp/iosApp/SampleListViewController.swift @@ -0,0 +1,223 @@ +import UIKit +import GoogleMaps +import maps_compose_multiplatform + +class SampleListViewController: UITableViewController { + + struct Sample { + let title: String + let description: String + let latitude: Double + let longitude: Double + let zoom: Float + let mapType: MapType + let myLocationEnabled: Bool + let scrollGesturesEnabled: Bool + let zoomGesturesEnabled: Bool + let markers: [MapMarker] + } + + struct Section { + let title: String + let samples: [Sample] + } + + private var sections: [Section] = [] + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "KMP Maps Demos" + + setupSections() + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") + } + + private func setupSections() { + sections = [ + Section( + title: "Map Types", + samples: [ + Sample( + title: "Normal Map", + description: "Standard road map of San Francisco", + latitude: 37.7749, + longitude: -122.4194, + zoom: 12.0, + mapType: .normal, + myLocationEnabled: false, + scrollGesturesEnabled: true, + zoomGesturesEnabled: true, + markers: [] + ), + Sample( + title: "Satellite Map", + description: "Satellite imagery of the Grand Canyon", + latitude: 36.0544, + longitude: -112.1401, + zoom: 10.0, + mapType: .satellite, + myLocationEnabled: false, + scrollGesturesEnabled: true, + zoomGesturesEnabled: true, + markers: [] + ), + Sample( + title: "Hybrid Map", + description: "Satellite with road names in New York City", + latitude: 40.7128, + longitude: -74.0060, + zoom: 11.0, + mapType: .hybrid, + myLocationEnabled: false, + scrollGesturesEnabled: true, + zoomGesturesEnabled: true, + markers: [] + ), + Sample( + title: "Terrain Map", + description: "Topographic map of Mount Everest", + latitude: 27.9881, + longitude: 86.9250, + zoom: 10.0, + mapType: .terrain, + myLocationEnabled: false, + scrollGesturesEnabled: true, + zoomGesturesEnabled: true, + markers: [] + ) + ] + ), + Section( + title: "Markers & Pins", + samples: [ + Sample( + title: "Single Marker", + description: "Marker at Golden Gate Bridge", + latitude: 37.8199, + longitude: -122.4783, + zoom: 13.0, + mapType: .normal, + myLocationEnabled: false, + scrollGesturesEnabled: true, + zoomGesturesEnabled: true, + markers: [ + MapMarker(latitude: 37.8199, longitude: -122.4783, title: "Golden Gate Bridge", snippet: "San Francisco, CA") + ] + ), + Sample( + title: "Multiple Markers", + description: "Demos with Big Ben, Tower Bridge, London Eye", + latitude: 51.5033, + longitude: -0.1195, + zoom: 13.0, + mapType: .normal, + myLocationEnabled: false, + scrollGesturesEnabled: true, + zoomGesturesEnabled: true, + markers: [ + MapMarker(latitude: 51.5007, longitude: -0.1246, title: "Big Ben", snippet: "Historic Clock Tower"), + MapMarker(latitude: 51.5055, longitude: -0.0754, title: "Tower Bridge", snippet: "Famous suspension bridge"), + MapMarker(latitude: 51.5033, longitude: -0.1195, title: "London Eye", snippet: "Giant Ferris Wheel") + ] + ), + Sample( + title: "Custom Snippet Marker", + description: "Tokyo Center with descriptive marker information", + latitude: 35.6762, + longitude: 139.6503, + zoom: 11.0, + mapType: .normal, + myLocationEnabled: false, + scrollGesturesEnabled: true, + zoomGesturesEnabled: true, + markers: [ + MapMarker(latitude: 35.6762, longitude: 139.6503, title: "Tokyo City", snippet: "Population: 14 million people") + ] + ) + ] + ), + Section( + title: "Map Gestures & Controls", + samples: [ + Sample( + title: "Gestures Enabled (Default)", + description: "Fully interactive map with zoom and scroll support", + latitude: 48.8566, + longitude: 2.3522, + zoom: 12.0, + mapType: .normal, + myLocationEnabled: false, + scrollGesturesEnabled: true, + zoomGesturesEnabled: true, + markers: [] + ), + Sample( + title: "Gestures Disabled", + description: "Static map centered on Rome, Italy (Scroll & zoom locked)", + latitude: 41.9028, + longitude: 12.4964, + zoom: 12.0, + mapType: .normal, + myLocationEnabled: false, + scrollGesturesEnabled: false, + zoomGesturesEnabled: false, + markers: [] + ), + Sample( + title: "Show My Location Button", + description: "Request location services & displays Location Button", + latitude: 48.8566, + longitude: 2.3522, + zoom: 12.0, + mapType: .normal, + myLocationEnabled: true, + scrollGesturesEnabled: true, + zoomGesturesEnabled: true, + markers: [] + ) + ] + ) + ] + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return sections.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return sections[section].samples.count + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return sections[section].title + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell") + let sample = sections[indexPath.section].samples[indexPath.row] + cell.textLabel?.text = sample.title + cell.detailTextLabel?.text = sample.description + cell.accessoryType = .disclosureIndicator + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let sample = sections[indexPath.section].samples[indexPath.row] + + let mapViewController = GoogleMapKt.GoogleMapViewController( + latitude: sample.latitude, + longitude: sample.longitude, + zoom: sample.zoom, + mapType: sample.mapType, + myLocationEnabled: sample.myLocationEnabled, + scrollGesturesEnabled: sample.scrollGesturesEnabled, + zoomGesturesEnabled: sample.zoomGesturesEnabled, + markers: sample.markers + ) + + mapViewController.title = sample.title + self.navigationController?.pushViewController(mapViewController, animated: true) + } +} diff --git a/maps-app/build.gradle.kts b/maps-app/build.gradle.kts index f76710f4..3a2555db 100644 --- a/maps-app/build.gradle.kts +++ b/maps-app/build.gradle.kts @@ -134,6 +134,7 @@ dependencies { implementation(project(":maps-compose")) implementation(project(":maps-compose-widgets")) implementation(project(":maps-compose-utils")) + implementation(project(":maps-compose-multiplatform")) } secrets { diff --git a/maps-app/src/main/AndroidManifest.xml b/maps-app/src/main/AndroidManifest.xml index d312b735..5d5663b0 100644 --- a/maps-app/src/main/AndroidManifest.xml +++ b/maps-app/src/main/AndroidManifest.xml @@ -115,6 +115,9 @@ + diff --git a/maps-app/src/main/java/com/google/maps/android/compose/Demo.kt b/maps-app/src/main/java/com/google/maps/android/compose/Demo.kt index 2931e60f..0c32a6d7 100644 --- a/maps-app/src/main/java/com/google/maps/android/compose/Demo.kt +++ b/maps-app/src/main/java/com/google/maps/android/compose/Demo.kt @@ -85,6 +85,11 @@ sealed class ActivityGroup( R.string.street_view_activity_description, StreetViewActivity::class ), + Activity( + R.string.kmp_map_activity, + R.string.kmp_map_activity_description, + KmpMapActivity::class + ), ) ) diff --git a/maps-app/src/main/java/com/google/maps/android/compose/KmpMapActivity.kt b/maps-app/src/main/java/com/google/maps/android/compose/KmpMapActivity.kt new file mode 100644 index 00000000..37c616e6 --- /dev/null +++ b/maps-app/src/main/java/com/google/maps/android/compose/KmpMapActivity.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import com.google.maps.android.compose.multiplatform.GoogleMap +import com.google.maps.android.compose.multiplatform.MapMarker + +class KmpMapActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + // Renders the multiplatform Map Composable + GoogleMap( + modifier = Modifier.fillMaxSize(), + latitude = 37.7749, // San Francisco + longitude = -122.4194, + zoom = 12f, + markers = listOf( + MapMarker( + latitude = 37.7749, + longitude = -122.4194, + title = "San Francisco", + snippet = "Welcome to SF!" + ) + ) + ) + } + } +} + diff --git a/maps-app/src/main/res/values/strings.xml b/maps-app/src/main/res/values/strings.xml index 1147de14..6e452257 100644 --- a/maps-app/src/main/res/values/strings.xml +++ b/maps-app/src/main/res/values/strings.xml @@ -81,6 +81,9 @@ Ground Overlay Adding a ground overlay to the map. + KMP Multiplatform Map + A map showcasing the KMP GoogleMap wrapper. + Map Types Map Features diff --git a/maps-compose-multiplatform/build.gradle.kts b/maps-compose-multiplatform/build.gradle.kts index c721059e..0a24de58 100644 --- a/maps-compose-multiplatform/build.gradle.kts +++ b/maps-compose-multiplatform/build.gradle.kts @@ -37,9 +37,28 @@ kotlin { summary = "Multiplatform Google Maps wrapper" homepage = "https://github.com/googlemaps/android-maps-compose" version = "1.0" - ios.deploymentTarget = "14.0" + ios.deploymentTarget = "16.0" pod("GoogleMaps") { - version = "9.0.0" + version = "10.14.0.0" + } + framework { + baseName = "maps_compose_multiplatform" + isStatic = true + linkerOpts("-lc++") + linkerOpts("-framework", "Accelerate") + linkerOpts("-framework", "CoreData") + linkerOpts("-framework", "CoreGraphics") + linkerOpts("-framework", "CoreImage") + linkerOpts("-framework", "CoreLocation") + linkerOpts("-framework", "CoreText") + linkerOpts("-framework", "GLKit") + linkerOpts("-framework", "ImageIO") + linkerOpts("-framework", "Metal") + linkerOpts("-framework", "OpenGLES") + linkerOpts("-framework", "QuartzCore") + linkerOpts("-framework", "Security") + linkerOpts("-framework", "SystemConfiguration") + linkerOpts("-framework", "UIKit") } } diff --git a/maps-compose-multiplatform/maps_compose_multiplatform.podspec b/maps-compose-multiplatform/maps_compose_multiplatform.podspec new file mode 100644 index 00000000..86cfb6f6 --- /dev/null +++ b/maps-compose-multiplatform/maps_compose_multiplatform.podspec @@ -0,0 +1,46 @@ +Pod::Spec.new do |spec| + spec.name = 'maps_compose_multiplatform' + spec.version = '1.0' + spec.homepage = 'https://github.com/googlemaps/android-maps-compose' + spec.source = { :http=> ''} + spec.authors = '' + spec.license = '' + spec.summary = 'Multiplatform Google Maps wrapper' + spec.vendored_frameworks = 'build/cocoapods/framework/maps_compose_multiplatform.framework' + spec.libraries = 'c++' + spec.ios.deployment_target = '15.0' + spec.dependency 'GoogleMaps', '10.14.0.0' + if !Dir.exist?('build/cocoapods/framework/maps_compose_multiplatform.framework') || Dir.empty?('build/cocoapods/framework/maps_compose_multiplatform.framework') + raise " + Kotlin framework 'maps_compose_multiplatform' doesn't exist yet, so a proper Xcode project can't be generated. + 'pod install' should be executed after running ':generateDummyFramework' Gradle task: + ./gradlew :maps-compose-multiplatform:generateDummyFramework + Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" + end + spec.xcconfig = { + 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO', + } + spec.pod_target_xcconfig = { + 'KOTLIN_PROJECT_PATH' => ':maps-compose-multiplatform', + 'PRODUCT_MODULE_NAME' => 'maps_compose_multiplatform', + } + spec.script_phases = [ + { + :name => 'Build maps_compose_multiplatform', + :execution_position => :before_compile, + :shell_path => '/bin/sh', + :script => <<-SCRIPT + if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then + echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" + exit 0 + fi + set -ev + REPO_ROOT="$PODS_TARGET_SRCROOT" + "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ + -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ + -Pkotlin.native.cocoapods.archs="$ARCHS" \ + -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" + SCRIPT + } + ] +end diff --git a/maps-compose-multiplatform/src/androidMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt b/maps-compose-multiplatform/src/androidMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt index d2696474..56460553 100644 --- a/maps-compose-multiplatform/src/androidMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt +++ b/maps-compose-multiplatform/src/androidMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt @@ -17,6 +17,7 @@ package com.google.maps.android.compose.multiplatform import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng @@ -27,14 +28,53 @@ public actual fun GoogleMap( modifier: Modifier, latitude: Double, longitude: Double, - zoom: Float + zoom: Float, + mapType: MapType, + myLocationEnabled: Boolean, + scrollGesturesEnabled: Boolean, + zoomGesturesEnabled: Boolean, + markers: List ) { val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(LatLng(latitude, longitude), zoom) } + val mapProperties = remember(mapType, myLocationEnabled) { + com.google.maps.android.compose.MapProperties( + mapType = when (mapType) { + MapType.NONE -> com.google.maps.android.compose.MapType.NONE + MapType.NORMAL -> com.google.maps.android.compose.MapType.NORMAL + MapType.SATELLITE -> com.google.maps.android.compose.MapType.SATELLITE + MapType.TERRAIN -> com.google.maps.android.compose.MapType.TERRAIN + MapType.HYBRID -> com.google.maps.android.compose.MapType.HYBRID + }, + isMyLocationEnabled = myLocationEnabled + ) + } + + val mapUiSettings = remember(scrollGesturesEnabled, zoomGesturesEnabled) { + com.google.maps.android.compose.MapUiSettings( + scrollGesturesEnabled = scrollGesturesEnabled, + zoomGesturesEnabled = zoomGesturesEnabled + ) + } + com.google.maps.android.compose.GoogleMap( modifier = modifier, - cameraPositionState = cameraPositionState - ) + cameraPositionState = cameraPositionState, + properties = mapProperties, + uiSettings = mapUiSettings + ) { + markers.forEach { markerData -> + val markerState = com.google.maps.android.compose.rememberMarkerState( + position = LatLng(markerData.latitude, markerData.longitude) + ) + com.google.maps.android.compose.Marker( + state = markerState, + title = markerData.title, + snippet = markerData.snippet + ) + } + } } + diff --git a/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt b/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt index 40043bcb..6be844a7 100644 --- a/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt +++ b/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt @@ -19,14 +19,34 @@ package com.google.maps.android.compose.multiplatform import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +public enum class MapType { + NONE, + NORMAL, + SATELLITE, + TERRAIN, + HYBRID +} + +public data class MapMarker( + val latitude: Double, + val longitude: Double, + val title: String? = null, + val snippet: String? = null +) + /** - * A multiplatform Map Composable that renders Google Maps on Android - * and native Apple MapKit Map (MKMapView) on iOS. + * A multiplatform Map Composable that renders Google Maps on Android and iOS. */ @Composable public expect fun GoogleMap( modifier: Modifier = Modifier, latitude: Double, longitude: Double, - zoom: Float = 10f + zoom: Float = 10f, + mapType: MapType = MapType.NORMAL, + myLocationEnabled: Boolean = false, + scrollGesturesEnabled: Boolean = true, + zoomGesturesEnabled: Boolean = true, + markers: List = emptyList() ) + diff --git a/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt b/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt index 1e8e2254..72f7365e 100644 --- a/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt +++ b/maps-compose-multiplatform/src/iosMain/kotlin/com/google/maps/android/compose/multiplatform/GoogleMap.kt @@ -22,16 +22,28 @@ import androidx.compose.ui.viewinterop.UIKitView import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.readValue import platform.CoreGraphics.CGRectZero -import cocoapods.GoogleMaps.GMSMapView -import cocoapods.GoogleMaps.GMSCameraPosition +import cocoapods.GoogleMaps.* -@OptIn(ExperimentalForeignApi::class) +import platform.UIKit.UIViewController +import androidx.compose.ui.window.ComposeUIViewController +import androidx.compose.foundation.layout.fillMaxSize + +import androidx.compose.ui.viewinterop.UIKitInteropProperties +import androidx.compose.ui.viewinterop.UIKitInteropInteractionMode +import platform.CoreLocation.CLLocationCoordinate2DMake + +@OptIn(ExperimentalForeignApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class) @Composable public actual fun GoogleMap( modifier: Modifier, latitude: Double, longitude: Double, - zoom: Float + zoom: Float, + mapType: MapType, + myLocationEnabled: Boolean, + scrollGesturesEnabled: Boolean, + zoomGesturesEnabled: Boolean, + markers: List ) { UIKitView( factory = { @@ -39,9 +51,109 @@ public actual fun GoogleMap( GMSMapView.mapWithFrame(CGRectZero.readValue(), camera = camera) }, modifier = modifier, + properties = UIKitInteropProperties( + interactionMode = UIKitInteropInteractionMode.NonCooperative + ), update = { mapView -> val camera = GMSCameraPosition.cameraWithLatitude(latitude, longitude, zoom) mapView.animateToCameraPosition(camera) + + // Update map type + mapView.mapType = when (mapType) { + MapType.NONE -> kGMSTypeNone + MapType.NORMAL -> kGMSTypeNormal + MapType.SATELLITE -> kGMSTypeSatellite + MapType.TERRAIN -> kGMSTypeTerrain + MapType.HYBRID -> kGMSTypeHybrid + } + + // Update my location + mapView.myLocationEnabled = myLocationEnabled + mapView.settings.myLocationButton = myLocationEnabled + + // Update gestures + mapView.settings.scrollGestures = scrollGesturesEnabled + mapView.settings.zoomGestures = zoomGesturesEnabled + + // Clear old and add new markers + mapView.clear() + markers.forEach { markerData -> + val marker = GMSMarker() + marker.position = CLLocationCoordinate2DMake(markerData.latitude, markerData.longitude) + marker.title = markerData.title + marker.snippet = markerData.snippet + marker.map = mapView + } } ) } + +public fun GoogleMapViewController( + latitude: Double, + longitude: Double, + zoom: Float +): UIViewController { + return GoogleMapViewController( + latitude = latitude, + longitude = longitude, + zoom = zoom, + mapType = MapType.NORMAL, + myLocationEnabled = false, + scrollGesturesEnabled = true, + zoomGesturesEnabled = true, + markers = emptyList() + ) +} + +public fun GoogleMapViewController( + latitude: Double, + longitude: Double, + zoom: Float, + markerLatitude: Double?, + markerLongitude: Double?, + markerTitle: String? +): UIViewController { + val markersList = if (markerLatitude != null && markerLongitude != null) { + listOf(MapMarker(latitude = markerLatitude, longitude = markerLongitude, title = markerTitle)) + } else { + emptyList() + } + return GoogleMapViewController( + latitude = latitude, + longitude = longitude, + zoom = zoom, + mapType = MapType.NORMAL, + myLocationEnabled = false, + scrollGesturesEnabled = true, + zoomGesturesEnabled = true, + markers = markersList + ) +} + +public fun GoogleMapViewController( + latitude: Double, + longitude: Double, + zoom: Float, + mapType: MapType, + myLocationEnabled: Boolean, + scrollGesturesEnabled: Boolean, + zoomGesturesEnabled: Boolean, + markers: List +): UIViewController { + return ComposeUIViewController(configure = { + enforceStrictPlistSanityCheck = false + }) { + GoogleMap( + modifier = Modifier.fillMaxSize(), + latitude = latitude, + longitude = longitude, + zoom = zoom, + mapType = mapType, + myLocationEnabled = myLocationEnabled, + scrollGesturesEnabled = scrollGesturesEnabled, + zoomGesturesEnabled = zoomGesturesEnabled, + markers = markers + ) + } +} + From f2b769f0be377822e704804c9590d4a45bd1f396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Fri, 5 Jun 2026 18:16:42 +0200 Subject: [PATCH 4/8] fix: resolve YAML syntax error in auto-fix.yml workflow --- .github/workflows/auto-fix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-fix.yml b/.github/workflows/auto-fix.yml index 6fe2c52c..c505fc70 100644 --- a/.github/workflows/auto-fix.yml +++ b/.github/workflows/auto-fix.yml @@ -31,7 +31,7 @@ on: jobs: auto-fix: - if: github.event.label.name == 'agent: create-pr' || github.event_name == 'workflow_dispatch' + if: "${{ github.event.label.name == 'agent: create-pr' || github.event_name == 'workflow_dispatch' }}" runs-on: ubuntu-latest permissions: contents: write From 73006ebf2d44b2043e7a3bbb368ca28ee23d5263 Mon Sep 17 00:00:00 2001 From: dkhawk <107309+dkhawk@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:19:48 -0600 Subject: [PATCH 5/8] fix(ios): declare DeveloperSecrets.swift output path & add BUILD_KMP.md --- BUILD_KMP.md | 83 +++++++++ iosApp/create_project.rb | 1 + iosApp/iosApp.xcodeproj/project.pbxproj | 237 ++++++++++++------------ 3 files changed, 203 insertions(+), 118 deletions(-) create mode 100644 BUILD_KMP.md diff --git a/BUILD_KMP.md b/BUILD_KMP.md new file mode 100644 index 00000000..a68effca --- /dev/null +++ b/BUILD_KMP.md @@ -0,0 +1,83 @@ +# Kotlin Multiplatform (KMP) Maps Demo — Build & Run Guide + +This guide details how to build and run the multiplatform Maps Compose library (`:maps-compose-multiplatform`), the Android demo app (`:maps-app`), and the iOS demo app (`iosApp`). + +--- + +## 1. Checkout the PR / Branch + +Fetch the remote branch and check it out (or add it as a Git worktree): + +```bash +git fetch origin feat/experimental-kmp-module +git checkout feat/experimental-kmp-module +``` + +*(Alternatively, to isolate your work in a worktree branch)*: +```bash +git worktree add -b feat/experimental-kmp-module feat-experimental-kmp-module origin/feat/experimental-kmp-module +``` + +--- + +## 2. Configure Local API Keys + +Create a file named `secrets.properties` in the **root** directory of the repository (if you don't already have one) and populate it with your Google Maps API keys: + +```properties +MAPS_API_KEY=YOUR_GOOGLE_MAPS_API_KEY +PLACES_API_KEY=YOUR_GOOGLE_PLACES_API_KEY +``` + +> [!IMPORTANT] +> Both `secrets.properties` and the generated iOS equivalent (`DeveloperSecrets.swift`) are ignored by Git to prevent accidentally leaking credentials. + +--- + +## 3. Building & Running for Android + +To compile and verify both the KMP library Android target and the Android sample application (`:maps-app`), run: + +```bash +./gradlew assembleDebug +``` + +--- + +## 4. Building & Running for iOS (Step-by-Step) + +If you are running the iOS project (`iosApp`) for the first time, follow these steps on a Mac with **Xcode** and **CocoaPods** installed: + +### Step 4.1: Generate the KMP Dummy Framework +Before CocoaPods can link the local `:maps-compose-multiplatform` podspec, the initial Kotlin framework must exist. From the repository root, execute: + +```bash +./gradlew :maps-compose-multiplatform:generateDummyFramework +``` + +### Step 4.2: Generate the Xcode Project +Navigate to the `iosApp` directory and run the project generation Ruby script (requires the `xcodeproj` gem): + +```bash +cd iosApp +ruby create_project.rb +``` + +> [!NOTE] +> **Build System Output Tracking (Xcode 16+)**: The `create_project.rb` script declares `$(SRCROOT)/iosApp/DeveloperSecrets.swift` as an output of the **Populate Secrets** script build phase. This prevents modern Xcode build systems from failing with `Build input file cannot be found: DeveloperSecrets.swift` during dependency graph analysis. + +### Step 4.3: Install Pod Dependencies +Install the required CocoaPods (`GoogleMaps` 10.14.0 and the local KMP library). If your local specs repository is out of date, use the `--repo-update` flag: + +```bash +pod install --repo-update +``` + +### Step 4.4: Open and Run in Xcode +1. Open the generated **Workspace** (do **not** open the `.xcodeproj` file directly): + ```bash + open iosApp.xcworkspace + ``` +2. In Xcode's top toolbar, select the active scheme dropdown and set it to **`iosApp`** (instead of `maps_compose_multiplatform`). +3. Choose an iOS Simulator (e.g., **iPhone 16** or **iPhone 17 Pro**) in the destination dropdown. +4. Click the **Play** button (or press **Cmd + R**) to compile, run the secrets population script, and launch the app! diff --git a/iosApp/create_project.rb b/iosApp/create_project.rb index f256eae3..cf90bfb1 100644 --- a/iosApp/create_project.rb +++ b/iosApp/create_project.rb @@ -35,6 +35,7 @@ # Add a Build Phase Run Script to populate secrets before compilation populate_secrets_phase = target.new_shell_script_build_phase('Populate Secrets') +populate_secrets_phase.output_paths = ['$(SRCROOT)/iosApp/DeveloperSecrets.swift'] populate_secrets_phase.shell_script = <<-SHELL SECRETS_PATH="${PROJECT_DIR}/../secrets.properties" OUTPUT_FILE="${SRCROOT}/iosApp/DeveloperSecrets.swift" diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index e5873b99..c8ac885e 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -7,108 +7,108 @@ objects = { /* Begin PBXBuildFile section */ - 09D299F7F5064B15DFF8647A /* SampleListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3C845D21837B44B6FED85F1 /* SampleListViewController.swift */; }; - 1937ECD4C944A623885CB9C3 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA2BC2B1BE1E2F248AA9D65E /* Pods_iosApp.framework */; }; - 7FE65D91E154BA74FB35FBAC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F1DCC9BB7B6BE074CB3D1D4F /* Foundation.framework */; }; - 81C277EB5057E02FF8B0CB5C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5293DC72FB4612B21EA96465 /* AppDelegate.swift */; }; - E997B8A8A2AFAD13A788A753 /* DeveloperSecrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EAB5F727C7CE0015606E231 /* DeveloperSecrets.swift */; }; + 0D653230C7B4B77DED78F795 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B55B65E85F954E4BF49E7CA /* AppDelegate.swift */; }; + 5520C2B8920F1DE6E2146F81 /* SampleListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DDC1C063EA32CE8824D07F /* SampleListViewController.swift */; }; + 7DE9F50AB020D738F9AC35AF /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AB792F01E14CD3EEC1AF5B8 /* Pods_iosApp.framework */; }; + 9B39CDB0583E7EFC46398A00 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D66C3B8A1626E64624FFC75 /* Foundation.framework */; }; + C048757D5C4B50BC1AB38AE6 /* DeveloperSecrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4DBA12C4C8AAC40F8EE45 /* DeveloperSecrets.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 18EAA545799221C4749B0BE6 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3EAB5F727C7CE0015606E231 /* DeveloperSecrets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DeveloperSecrets.swift; sourceTree = ""; }; - 5293DC72FB4612B21EA96465 /* AppDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - AFAE64BF1EC03DC7EE9905AF /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; - B78650FF6C736F3A8024670F /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; - E3C845D21837B44B6FED85F1 /* SampleListViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SampleListViewController.swift; sourceTree = ""; }; - EA2BC2B1BE1E2F248AA9D65E /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F1DCC9BB7B6BE074CB3D1D4F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - F37E4987EF58C0103DBE5753 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2E61E10ADF7CF13E32E000C0 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; + 4D66C3B8A1626E64624FFC75 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 51C4DBA12C4C8AAC40F8EE45 /* DeveloperSecrets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DeveloperSecrets.swift; sourceTree = ""; }; + 6B55B65E85F954E4BF49E7CA /* AppDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 711AFBB8CF132FD69AED5EAE /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 75DDC1C063EA32CE8824D07F /* SampleListViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SampleListViewController.swift; sourceTree = ""; }; + 7AB792F01E14CD3EEC1AF5B8 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7FA1089A5A50A88C1B0534CC /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; + B84637CCA3496DDB1F800E10 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - ACD54AC92AF33CBA9038EF28 /* Frameworks */ = { + 755A4F11212F18CBAE1A61A0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7FE65D91E154BA74FB35FBAC /* Foundation.framework in Frameworks */, - 1937ECD4C944A623885CB9C3 /* Pods_iosApp.framework in Frameworks */, + 9B39CDB0583E7EFC46398A00 /* Foundation.framework in Frameworks */, + 7DE9F50AB020D738F9AC35AF /* Pods_iosApp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 160662DC4B33BBB495406E79 /* Products */ = { + 6572DE9AD86B0CC67C7E963E /* Frameworks */ = { isa = PBXGroup; children = ( - F37E4987EF58C0103DBE5753 /* iosApp.app */, + C469B336BFDB55F88E4144D1 /* iOS */, + 7AB792F01E14CD3EEC1AF5B8 /* Pods_iosApp.framework */, ); - name = Products; + name = Frameworks; sourceTree = ""; }; - 761B8220F805A3F9823B2474 /* iOS */ = { + 8F4470E4F5DD69A5F2052318 /* iosApp */ = { isa = PBXGroup; children = ( - F1DCC9BB7B6BE074CB3D1D4F /* Foundation.framework */, + 6B55B65E85F954E4BF49E7CA /* AppDelegate.swift */, + 75DDC1C063EA32CE8824D07F /* SampleListViewController.swift */, + 51C4DBA12C4C8AAC40F8EE45 /* DeveloperSecrets.swift */, + 711AFBB8CF132FD69AED5EAE /* Info.plist */, ); - name = iOS; + name = iosApp; + path = iosApp; sourceTree = ""; }; - 9A8CB19F0A8D45C35146683E = { + 9C595BA209CE46C978747670 = { isa = PBXGroup; children = ( - 160662DC4B33BBB495406E79 /* Products */, - AEC84CDA2E901AD18F65E5CF /* Frameworks */, - A8B082EA0D34BCDB9BC6A878 /* iosApp */, - B769C4D1E3A5E7DD227FDD0F /* Pods */, + F5812B1B51C667A99E3473DB /* Products */, + 6572DE9AD86B0CC67C7E963E /* Frameworks */, + 8F4470E4F5DD69A5F2052318 /* iosApp */, + C21DC034EFC3BBCE3CB62ECF /* Pods */, ); sourceTree = ""; }; - A8B082EA0D34BCDB9BC6A878 /* iosApp */ = { + C21DC034EFC3BBCE3CB62ECF /* Pods */ = { isa = PBXGroup; children = ( - 5293DC72FB4612B21EA96465 /* AppDelegate.swift */, - E3C845D21837B44B6FED85F1 /* SampleListViewController.swift */, - 3EAB5F727C7CE0015606E231 /* DeveloperSecrets.swift */, - 18EAA545799221C4749B0BE6 /* Info.plist */, + 7FA1089A5A50A88C1B0534CC /* Pods-iosApp.release.xcconfig */, + 2E61E10ADF7CF13E32E000C0 /* Pods-iosApp.debug.xcconfig */, ); - name = iosApp; - path = iosApp; + name = Pods; + path = Pods; sourceTree = ""; }; - AEC84CDA2E901AD18F65E5CF /* Frameworks */ = { + C469B336BFDB55F88E4144D1 /* iOS */ = { isa = PBXGroup; children = ( - 761B8220F805A3F9823B2474 /* iOS */, - EA2BC2B1BE1E2F248AA9D65E /* Pods_iosApp.framework */, + 4D66C3B8A1626E64624FFC75 /* Foundation.framework */, ); - name = Frameworks; + name = iOS; sourceTree = ""; }; - B769C4D1E3A5E7DD227FDD0F /* Pods */ = { + F5812B1B51C667A99E3473DB /* Products */ = { isa = PBXGroup; children = ( - B78650FF6C736F3A8024670F /* Pods-iosApp.release.xcconfig */, - AFAE64BF1EC03DC7EE9905AF /* Pods-iosApp.debug.xcconfig */, + B84637CCA3496DDB1F800E10 /* iosApp.app */, ); - name = Pods; - path = Pods; + name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 5B40C8C8EA63318DDB4B739B /* iosApp */ = { + F5CA17578FAB66DC77D30B26 /* iosApp */ = { isa = PBXNativeTarget; - buildConfigurationList = 0375DD885B6888B89AAE9BF9 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildConfigurationList = CF3EFFA659DA87ADF491844D /* Build configuration list for PBXNativeTarget "iosApp" */; buildPhases = ( - A1495132AEB0B97D4FC21766 /* [CP] Check Pods Manifest.lock */, - 587CFD89D0C8586590AC06DD /* Populate Secrets */, - 854A57832A18FB33C8B65435 /* Sources */, - ACD54AC92AF33CBA9038EF28 /* Frameworks */, - 9FD86DF237A6365F05565BE6 /* Resources */, - 10D8D4B430DF1AE053CC752F /* [CP] Copy Pods Resources */, + ABA2E729135D4636D5EBF68A /* [CP] Check Pods Manifest.lock */, + EA513F9C2ABA8D7F7B488510 /* Populate Secrets */, + C8810296C81A419006D89E5E /* Sources */, + 755A4F11212F18CBAE1A61A0 /* Frameworks */, + C6F77023AAF8823528A70ED2 /* Resources */, + D63BC68F2C76415CBAB80FEA /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -116,19 +116,19 @@ ); name = iosApp; productName = iosApp; - productReference = F37E4987EF58C0103DBE5753 /* iosApp.app */; + productReference = B84637CCA3496DDB1F800E10 /* iosApp.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - E8320DC81573CAF4F46DC0D7 /* Project object */ = { + 0BAC57553145A9D99D214F34 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1600; }; - buildConfigurationList = 12FCA89330D4CE38309E9B81 /* Build configuration list for PBXProject "iosApp" */; + buildConfigurationList = 2F8B9FD9291D2E34BAB2808B /* Build configuration list for PBXProject "iosApp" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; @@ -136,20 +136,20 @@ en, Base, ); - mainGroup = 9A8CB19F0A8D45C35146683E; + mainGroup = 9C595BA209CE46C978747670; minimizedProjectReferenceProxies = 0; preferredProjectObjectVersion = 77; - productRefGroup = 160662DC4B33BBB495406E79 /* Products */; + productRefGroup = F5812B1B51C667A99E3473DB /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 5B40C8C8EA63318DDB4B739B /* iosApp */, + F5CA17578FAB66DC77D30B26 /* iosApp */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 9FD86DF237A6365F05565BE6 /* Resources */ = { + C6F77023AAF8823528A70ED2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -159,43 +159,47 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 10D8D4B430DF1AE053CC752F /* [CP] Copy Pods Resources */ = { + ABA2E729135D4636D5EBF68A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps/GoogleMapsResources.bundle", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsResources.bundle", + "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 587CFD89D0C8586590AC06DD /* Populate Secrets */ = { + D63BC68F2C76415CBAB80FEA /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps/GoogleMapsResources.bundle", ); - name = "Populate Secrets"; - outputFileListPaths = ( - ); + name = "[CP] Copy Pods Resources"; outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsResources.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "SECRETS_PATH=\"${PROJECT_DIR}/../secrets.properties\"\nOUTPUT_FILE=\"${SRCROOT}/iosApp/DeveloperSecrets.swift\"\n\nAPI_KEY=\"YOUR_API_KEY\"\n\nif [ -f \"$SECRETS_PATH\" ]; then\n EXTRACTED_KEY=$(grep -E \"^MAPS_API_KEY=\" \"$SECRETS_PATH\" | cut -d'=' -f2 | tr -d '\"' | tr -d \"'\")\n if [ ! -z \"$EXTRACTED_KEY\" ]; then\n API_KEY=\"$EXTRACTED_KEY\"\n fi\nfi\n\ncat < \"$OUTPUT_FILE\"\n// Generated file. Do not commit or modify.\nstruct DeveloperSecrets {\n static let mapsApiKey = \"$API_KEY\"\n}\nEOF\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; + showEnvVarsInLog = 0; }; - A1495132AEB0B97D4FC21766 /* [CP] Check Pods Manifest.lock */ = { + EA513F9C2ABA8D7F7B488510 /* Populate Secrets */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -203,37 +207,34 @@ inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "Populate Secrets"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", + "$(SRCROOT)/iosApp/DeveloperSecrets.swift", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "SECRETS_PATH=\"${PROJECT_DIR}/../secrets.properties\"\nOUTPUT_FILE=\"${SRCROOT}/iosApp/DeveloperSecrets.swift\"\n\nAPI_KEY=\"YOUR_API_KEY\"\n\nif [ -f \"$SECRETS_PATH\" ]; then\n EXTRACTED_KEY=$(grep -E \"^MAPS_API_KEY=\" \"$SECRETS_PATH\" | cut -d'=' -f2 | tr -d '\"' | tr -d \"'\")\n if [ ! -z \"$EXTRACTED_KEY\" ]; then\n API_KEY=\"$EXTRACTED_KEY\"\n fi\nfi\n\ncat < \"$OUTPUT_FILE\"\n// Generated file. Do not commit or modify.\nstruct DeveloperSecrets {\n static let mapsApiKey = \"$API_KEY\"\n}\nEOF\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 854A57832A18FB33C8B65435 /* Sources */ = { + C8810296C81A419006D89E5E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 81C277EB5057E02FF8B0CB5C /* AppDelegate.swift in Sources */, - 09D299F7F5064B15DFF8647A /* SampleListViewController.swift in Sources */, - E997B8A8A2AFAD13A788A753 /* DeveloperSecrets.swift in Sources */, + 0D653230C7B4B77DED78F795 /* AppDelegate.swift in Sources */, + 5520C2B8920F1DE6E2146F81 /* SampleListViewController.swift in Sources */, + C048757D5C4B50BC1AB38AE6 /* DeveloperSecrets.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - 368E878A47CC5CC47D624E79 /* Debug */ = { + 30D83033DD758DCAE2B65F37 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -267,17 +268,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -285,19 +280,18 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; - name = Debug; + name = Release; }; - 79144F741B9018EB7ABCBD73 /* Debug */ = { + 79F5FD2A2E53F2F285DB566F /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AFAE64BF1EC03DC7EE9905AF /* Pods-iosApp.debug.xcconfig */; + baseConfigurationReference = 7FA1089A5A50A88C1B0534CC /* Pods-iosApp.release.xcconfig */; buildSettings = { AD_HOC_CODE_SIGNING_ALLOWED = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -312,10 +306,11 @@ SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; }; - name = Debug; + name = Release; }; - A18BD2CB84E1A0278F561EB6 /* Release */ = { + 911DF9EA0DD218A18147B490 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -349,11 +344,17 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; + DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -361,18 +362,19 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; - MTL_ENABLE_DEBUG_INFO = NO; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; - name = Release; + name = Debug; }; - AF9933E09D62868BBCC7C26A /* Release */ = { + BE70C656E6CD2E4A26E99D95 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B78650FF6C736F3A8024670F /* Pods-iosApp.release.xcconfig */; + baseConfigurationReference = 2E61E10ADF7CF13E32E000C0 /* Pods-iosApp.debug.xcconfig */; buildSettings = { AD_HOC_CODE_SIGNING_ALLOWED = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -387,32 +389,31 @@ SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; }; - name = Release; + name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 0375DD885B6888B89AAE9BF9 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + 2F8B9FD9291D2E34BAB2808B /* Build configuration list for PBXProject "iosApp" */ = { isa = XCConfigurationList; buildConfigurations = ( - AF9933E09D62868BBCC7C26A /* Release */, - 79144F741B9018EB7ABCBD73 /* Debug */, + 911DF9EA0DD218A18147B490 /* Debug */, + 30D83033DD758DCAE2B65F37 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 12FCA89330D4CE38309E9B81 /* Build configuration list for PBXProject "iosApp" */ = { + CF3EFFA659DA87ADF491844D /* Build configuration list for PBXNativeTarget "iosApp" */ = { isa = XCConfigurationList; buildConfigurations = ( - 368E878A47CC5CC47D624E79 /* Debug */, - A18BD2CB84E1A0278F561EB6 /* Release */, + 79F5FD2A2E53F2F285DB566F /* Release */, + BE70C656E6CD2E4A26E99D95 /* Debug */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = E8320DC81573CAF4F46DC0D7 /* Project object */; + rootObject = 0BAC57553145A9D99D214F34 /* Project object */; } From 5d83ff72e1b2229eaf1978832e388bb01de97b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Tue, 8 Sep 2026 22:29:44 +0700 Subject: [PATCH 6/8] feat: consume multiplatform clustering from android-maps-utils KMP branch Adds common-code clustering to maps-compose-multiplatform via the experimental KMP modules of android-maps-utils (maps-model, clustering): clusterMarkers() runs NonHierarchicalDistanceBasedAlgorithm in commonMain and ClusteredGoogleMap renders the resulting clusters, so Android and iOS produce identical clusters from shared code. Consumption paths (see settings.gradle.kts): publishToMavenLocal from ../android-maps-utils plus -PuseMavenLocal=true (full build works, this is the default), or -PuseLocalMapsUtils=true for a composite build (platform compilations work; shared-metadata compilation is skipped by Kotlin's granular-metadata transform across included builds and fails). Note: linkPodDebugFrameworkIosArm64 fails on this branch with or without this change (Kotlin/Native cache failure on compose-ui 1.7.3 uikitarm64); simulator link and all target compilations pass. Claude-Session: https://claude.ai/code/session_01225X6MnAqkyCF7Xones6WY --- maps-compose-multiplatform/build.gradle.kts | 4 + .../compose/multiplatform/Clustering.kt | 106 ++++++++++++++++++ settings.gradle.kts | 24 ++++ 3 files changed, 134 insertions(+) create mode 100644 maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/Clustering.kt diff --git a/maps-compose-multiplatform/build.gradle.kts b/maps-compose-multiplatform/build.gradle.kts index 0a24de58..342706aa 100644 --- a/maps-compose-multiplatform/build.gradle.kts +++ b/maps-compose-multiplatform/build.gradle.kts @@ -68,6 +68,10 @@ kotlin { implementation("org.jetbrains.compose.runtime:runtime:1.7.3") implementation("org.jetbrains.compose.foundation:foundation:1.7.3") implementation("org.jetbrains.compose.ui:ui:1.7.3") + // Multiplatform clustering from android-maps-utils; substituted with the + // local ../android-maps-utils composite build (see settings.gradle.kts). + api("com.google.maps.android:maps-model:5.2.0") + api("com.google.maps.android:clustering:5.2.0") } } androidMain { diff --git a/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/Clustering.kt b/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/Clustering.kt new file mode 100644 index 00000000..2923541a --- /dev/null +++ b/maps-compose-multiplatform/src/commonMain/kotlin/com/google/maps/android/compose/multiplatform/Clustering.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android.compose.multiplatform + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.google.maps.android.clustering.ClusterItem +import com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm +import com.google.maps.android.model.LatLng +import com.google.maps.android.model.latitude +import com.google.maps.android.model.longitude + +/** + * Adapts a [MapMarker] to android-maps-utils' [ClusterItem] so it can be fed to the + * multiplatform clustering algorithms. + */ +private class MapMarkerClusterItem( + val marker: MapMarker, +) : ClusterItem { + override val position: LatLng = LatLng(marker.latitude, marker.longitude) + + override val title: String? = marker.title + + override val snippet: String? = marker.snippet + + override val zIndex: Float? = null +} + +/** + * Groups [markers] into clusters for the given [zoom] level using the multiplatform + * clustering algorithm from android-maps-utils. Runs entirely in common code, so Android + * and iOS produce identical clusters. + * + * Clusters of one item pass the original marker through; larger clusters are represented + * by a marker at the cluster position whose title carries the cluster size. + */ +public fun clusterMarkers( + markers: List, + zoom: Float, + maxDistanceBetweenClusteredItems: Int = 100, +): List { + val algorithm = NonHierarchicalDistanceBasedAlgorithm() + algorithm.maxDistanceBetweenClusteredItems = maxDistanceBetweenClusteredItems + algorithm.addItems(markers.map { MapMarkerClusterItem(it) }) + return algorithm.getClusters(zoom).map { cluster -> + val items = cluster.items + if (items.size == 1) { + items.first().marker + } else { + MapMarker( + latitude = cluster.position.latitude, + longitude = cluster.position.longitude, + title = "${items.size} items", + ) + } + } +} + +/** + * A [GoogleMap] that clusters [markers] with android-maps-utils' multiplatform clustering + * algorithm before rendering them. Clustering is recomputed when the markers or [zoom] + * change. + */ +@Composable +public fun ClusteredGoogleMap( + modifier: Modifier = Modifier, + latitude: Double, + longitude: Double, + zoom: Float = 10f, + mapType: MapType = MapType.NORMAL, + myLocationEnabled: Boolean = false, + scrollGesturesEnabled: Boolean = true, + zoomGesturesEnabled: Boolean = true, + markers: List = emptyList(), + maxDistanceBetweenClusteredItems: Int = 100, +) { + val clustered = remember(markers, zoom, maxDistanceBetweenClusteredItems) { + clusterMarkers(markers, zoom, maxDistanceBetweenClusteredItems) + } + GoogleMap( + modifier = modifier, + latitude = latitude, + longitude = longitude, + zoom = zoom, + mapType = mapType, + myLocationEnabled = myLocationEnabled, + scrollGesturesEnabled = scrollGesturesEnabled, + zoomGesturesEnabled = zoomGesturesEnabled, + markers = clustered, + ) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e6c77516..c0f3d7de 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,6 +38,30 @@ plugins { } rootProject.name = "android-maps-compose" +// The maps-compose-multiplatform module depends on the experimental KMP branch of +// android-maps-utils (feat/experimental-kmp-clustering), which provides multiplatform +// clustering algorithms and the common LatLng/CameraPosition model types. +// +// Default consumption path: publish those modules to mavenLocal and enable the +// mavenLocal repository (see dependencyResolutionManagement above): +// (in ../android-maps-utils) ./gradlew :maps-model:publishToMavenLocal :clustering:publishToMavenLocal +// (here) ./gradlew -PuseMavenLocal=true +// +// Alternative for tight iteration: -PuseLocalMapsUtils=true substitutes the two +// coordinates with a composite build of ../android-maps-utils. Note that with the +// composite, platform compilations (android/iOS) work but the shared-metadata +// compilation (compileCommonMainKotlinMetadata, and thus assemble) fails: Kotlin's +// granular metadata transformation currently skips project dependencies substituted +// across included builds, so common code cannot be analyzed against them. +if (providers.gradleProperty("useLocalMapsUtils").orNull == "true") { + includeBuild("../android-maps-utils") { + dependencySubstitution { + substitute(module("com.google.maps.android:clustering")).using(project(":clustering")) + substitute(module("com.google.maps.android:maps-model")).using(project(":maps-model")) + } + } +} + include(":maps-app") include(":maps-compose") include(":maps-compose-widgets") From b49e2d65df7dea95c18c744cdbb024af97e237e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Tue, 8 Sep 2026 22:38:38 +0700 Subject: [PATCH 7/8] ci: publish KMP maps-utils modules to mavenLocal before building The maps-compose-multiplatform module now depends on com.google.maps.android:maps-model and :clustering from the feat/experimental-kmp-clustering branch of android-maps-utils, which are not published to any remote repository. Check out that branch and publish the two modules to the runner's mavenLocal in the lint, test and instrumentation workflows, and set USE_MAVEN_LOCAL so the compose build resolves them from there. Claude-Session: https://claude.ai/code/session_01225X6MnAqkyCF7Xones6WY --- .github/workflows/instrumentation-test.yml | 15 +++++++++++++++ .github/workflows/lint-report.yml | 15 +++++++++++++++ .github/workflows/test.yml | 15 +++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/.github/workflows/instrumentation-test.yml b/.github/workflows/instrumentation-test.yml index 4e5301e5..49defa11 100644 --- a/.github/workflows/instrumentation-test.yml +++ b/.github/workflows/instrumentation-test.yml @@ -25,6 +25,8 @@ on: jobs: run-instrumentation-test: runs-on: macOS-latest-large # enables hardware acceleration in the virtual machine + env: + USE_MAVEN_LOCAL: "true" permissions: pull-requests: write timeout-minutes: 30 @@ -41,6 +43,19 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 + - name: Checkout android-maps-utils (experimental KMP branch) + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: googlemaps/android-maps-utils + ref: feat/experimental-kmp-clustering + path: maps-utils-kmp + + - name: Publish multiplatform maps-utils modules to mavenLocal + working-directory: maps-utils-kmp + run: | + printf 'MAPS_API_KEY=PLACEHOLDER\nPLACES_API_KEY=PLACEHOLDER\nMAP_ID=PLACEHOLDER\n' > secrets.properties + ./gradlew :maps-model:publishToMavenLocal :clustering:publishToMavenLocal + - name: Inject Maps API Key env: MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }} diff --git a/.github/workflows/lint-report.yml b/.github/workflows/lint-report.yml index 9fac6ae1..f207ff49 100644 --- a/.github/workflows/lint-report.yml +++ b/.github/workflows/lint-report.yml @@ -26,6 +26,8 @@ permissions: jobs: lint: runs-on: ubuntu-latest + env: + USE_MAVEN_LOCAL: "true" steps: - name: Checkout code @@ -40,6 +42,19 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 + - name: Checkout android-maps-utils (experimental KMP branch) + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: googlemaps/android-maps-utils + ref: feat/experimental-kmp-clustering + path: maps-utils-kmp + + - name: Publish multiplatform maps-utils modules to mavenLocal + working-directory: maps-utils-kmp + run: | + printf 'MAPS_API_KEY=PLACEHOLDER\nPLACES_API_KEY=PLACEHOLDER\nMAP_ID=PLACEHOLDER\n' > secrets.properties + ./gradlew :maps-model:publishToMavenLocal :clustering:publishToMavenLocal + - name: Run Android Lint run: ./gradlew lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dbf5b505..58e69c46 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,8 @@ permissions: jobs: test: runs-on: ubuntu-latest + env: + USE_MAVEN_LOCAL: "true" steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it @@ -44,6 +46,19 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 + - name: Checkout android-maps-utils (experimental KMP branch) + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: googlemaps/android-maps-utils + ref: feat/experimental-kmp-clustering + path: maps-utils-kmp + + - name: Publish multiplatform maps-utils modules to mavenLocal + working-directory: maps-utils-kmp + run: | + printf 'MAPS_API_KEY=PLACEHOLDER\nPLACES_API_KEY=PLACEHOLDER\nMAP_ID=PLACEHOLDER\n' > secrets.properties + ./gradlew :maps-model:publishToMavenLocal :clustering:publishToMavenLocal + - name: Build modules run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:generateReleaseScreenshotTestConfig --stacktrace From 169d8832cb8cd57e0d37ed44e325643b9461da54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Tue, 8 Sep 2026 22:48:15 +0700 Subject: [PATCH 8/8] fix: consume KMP maps-utils under its public artifactIds com.google.maps.android:clustering duplicated the classes of the android-maps-utils-clustering AAR that maps-compose-utils pulls from Maven Central, failing maps-app's checkDebugDuplicateClasses. Depend on android-maps-utils-clustering / android-maps-utils-maps-model instead (matching the renamed KMP publications) so Gradle resolves both paths to one module; mavenLocal shadows the Central AAR with the API-compatible KMP artifact. Claude-Session: https://claude.ai/code/session_01225X6MnAqkyCF7Xones6WY --- maps-compose-multiplatform/build.gradle.kts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/maps-compose-multiplatform/build.gradle.kts b/maps-compose-multiplatform/build.gradle.kts index 342706aa..06520606 100644 --- a/maps-compose-multiplatform/build.gradle.kts +++ b/maps-compose-multiplatform/build.gradle.kts @@ -68,10 +68,12 @@ kotlin { implementation("org.jetbrains.compose.runtime:runtime:1.7.3") implementation("org.jetbrains.compose.foundation:foundation:1.7.3") implementation("org.jetbrains.compose.ui:ui:1.7.3") - // Multiplatform clustering from android-maps-utils; substituted with the - // local ../android-maps-utils composite build (see settings.gradle.kts). - api("com.google.maps.android:maps-model:5.2.0") - api("com.google.maps.android:clustering:5.2.0") + // Multiplatform clustering from android-maps-utils (KMP branch); resolved from + // mavenLocal or the composite build (see settings.gradle.kts). Uses the repo's + // public artifactIds so it conflict-resolves as the same module as the AAR that + // maps-compose-utils pulls from Maven Central instead of duplicating its classes. + api("com.google.maps.android:android-maps-utils-maps-model:5.2.0") + api("com.google.maps.android:android-maps-utils-clustering:5.2.0") } } androidMain {