Crea tu primer mapa 3D con SwiftUI

1. Antes de comenzar

En este codelab, aprenderás a crear una app de mapas en 3D en SwiftUI con el SDK de Maps 3D para iOS.

App que muestra un mapa 3D de San Francisco

Aprenderás a hacer lo siguiente:

  • Cómo controlar la cámara para ver ubicaciones y volar por el mapa
  • Cómo agregar marcadores y modelos
  • Cómo dibujar líneas y polígonos
  • Cómo controlar los clics de los usuarios en los marcadores de Place

Requisitos previos

  • Un proyecto de Google Play Console con la facturación habilitada
  • Una clave de API, que se puede restringir de forma opcional al SDK de Mapas 3D para iOS
  • Conocimientos básicos sobre el desarrollo de iOS con SwiftUI

Actividades

  • Configura Xcode y agrega el SDK con Swift Package Manager
  • Configura tu app para usar una clave de API
  • Agrega un mapa 3D básico a tu app
  • Controla la cámara para volar a ubicaciones específicas y alrededor de ellas
  • Agrega marcadores, líneas, polígonos y modelos a tu mapa

Requisitos

  • Xcode 15 o una versión posterior

2. Prepárate

Para el siguiente paso, debes habilitar el SDK de Maps 3D para iOS.

Configura Google Maps Platform

Si todavía no tienes una cuenta de Google Cloud Platform y un proyecto con la facturación habilitada, consulta la guía Cómo comenzar a utilizar Google Maps Platform para crear una cuenta de facturación y un proyecto.

  1. En Cloud Console, haz clic en el menú desplegable del proyecto y selecciona el proyecto que deseas usar para este codelab.

  1. Habilita las API y los SDK de Google Maps Platform necesarios para este codelab en Google Cloud Marketplace. Para hacerlo, sigue los pasos que se indican en este video o esta documentación.
  2. Genera una clave de API en la página Credenciales de Cloud Console. Puedes seguir los pasos que se indican en este video o esta documentación. Todas las solicitudes a Google Maps Platform requieren una clave de API.

Habilita el SDK de Mapas 3D para iOS

Puedes encontrar el SDK de Maps 3D para iOS en el vínculo del menú Google Maps Platform > APIs y servicios de la consola.

Haz clic en Habilita para habilitar la API en el proyecto seleccionado.

Habilita el SDK de Maps 3D en la Consola de Google

3. Crea una app básica de SwiftUI

Nota: Puedes encontrar el código de la solución para cada paso en el repositorio de la app de ejemplo del codelab en GitHub .

Crea una app nueva en Xcode.

El código de este paso se puede encontrar en la carpeta GoogleMaps3DDemo en GitHub.

Abre Xcode y crea una app nueva. Especifica SwiftUI.

Llama a tu app GoogleMaps3DDemo, con un nombre de paquete com.example.GoogleMaps3DDemo.

Importa la biblioteca de GoogleMaps3D a tu proyecto

Agrega el SDK a tu proyecto con Swift Package Manager.

En tu proyecto o lugar de trabajo de Xcode, ve a File > Add Package Dependencies. Ingresa https://github.com/googlemaps/ios-maps-3d-sdk como la URL, presiona Intro para extraer el paquete y haz clic en "Agregar paquete".

En la ventana Choose Package Products, verifica que se agregará GoogleMaps3D a tu destino principal designado. Cuando termines, haz clic en Agregar paquete.

Para verificar la instalación, navega al panel General de tu segmentación. En Frameworks, bibliotecas y contenido incorporado, deberías ver los paquetes instalados. También puedes ver la sección Dependencias de paquetes del Navegador de proyectos para verificar el paquete y su versión.

Agrega tu clave de API

Puedes codificar tu clave de API en la app, pero no se recomienda. Agregar un archivo de configuración te permite mantener la clave de API en secreto y evitar que se registre en el control de código fuente.

Crea un nuevo archivo de configuración en la carpeta raíz del proyecto

En Xcode, asegúrate de que estás viendo la ventana del explorador de proyectos. Haz clic con el botón derecho en la raíz del proyecto y selecciona "New File from Template". Desplázate hasta que veas "Archivo de configuración". Selecciona esta opción y haz clic en "Siguiente". Asigna el nombre Config.xcconfig al archivo y asegúrate de que esté seleccionada la carpeta raíz del proyecto. Haz clic en "Crear" para crear el archivo.

En el editor, agrega una línea al archivo de configuración de la siguiente manera: MAPS_API_KEY = YOUR_API_KEY

Reemplaza YOUR_API_KEY por tu clave de API.

Agrega este parámetro de configuración a Info.plist.

Para ello, selecciona la raíz del proyecto y haz clic en la pestaña “Información”.

Agrega una propiedad nueva llamada MAPS_API_KEY con un valor de $(MAPS_API_KEY).

El código de la app de ejemplo tiene un archivo Info.plist que especifica esta propiedad.

Agrega un mapa

Abre el archivo llamado GoogleMaps3DDemoApp.swift. Este es el punto de entrada y la navegación principal de tu app.

Llama a ContentView(), que muestra un mensaje de Hello World.

Abre ContentView.swift en el editor.

Agrega una sentencia import para GoogleMaps3D.

Borra el código dentro del bloque de código var body: some View {}. Declara un nuevo Map() dentro de body.

La configuración mínima que necesitas para inicializar un Map es un MapMode. Tiene dos valores posibles:

  • .hybrid: Imágenes satelitales con rutas y etiquetas
  • .satellite: Solo imágenes satelitales.

Elija .hybrid.

Tu archivo ContentView.swift debería tener el siguiente aspecto:

import GoogleMaps3D
import SwiftUI

@main
struct ContentView: View {
    var body: some View {
      Map(mode: .hybrid)
    }
}

Establece tu clave de API.

La clave de API se debe establecer antes de que se inicialice el mapa.

Para ello, establece Map.apiKey en el controlador de eventos init() de cualquier View que contenga un mapa. También puedes configurarlo en GoogleMaps3DDemoApp.swift antes de que llame a ContentView().

En GoogleMaps3DDemoApp.swift, establece Map.apiKey en el controlador de eventos onAppear de WindowGroup.

Recupera tu clave de API del archivo de configuración

Usa Bundle.main.infoDictionary para acceder a la configuración de MAPS_API_KEY que creaste en tu archivo de configuración.

import GoogleMaps3D
import SwiftUI

@main
struct GoogleMaps3DDemoApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .onAppear {
      guard let infoDictionary: [String: Any] = Bundle.main.infoDictionary else {
        fatalError("Info.plist not found")
      }
      guard let apiKey: String = infoDictionary["MAPS_API_KEY"] as? String else {
        fatalError("MAPS_API_KEY not set in Info.plist")
      }
      Map.apiKey = apiKey
    }
  }
}

Compila y ejecuta tu app para verificar que se cargue correctamente. Deberías ver un mapa del mundo.

Mapa 3D que muestra la Tierra

4. Cómo usar una cámara para controlar la vista de mapa

Crea un objeto de estado de la cámara

Las vistas de mapas en 3D se controlan con la clase Camera. En este paso, aprenderás a especificar la ubicación, la altitud, el rumbo, la inclinación, el balanceo y el rango para personalizar la vista del mapa.

Vista del mapa 3D de San Francisco

Crea una clase Helpers para almacenar la configuración de la cámara

Agrega un nuevo archivo vacío llamado MapHelpers.swift. En tu archivo nuevo, importa GoogleMaps3D y agrega una extensión a la clase Camera. Agrega una variable llamada sanFrancisco. Inicializa esta variable como un nuevo objeto Camera. Busca la cámara en latitude: 37.39, longitude: -122.08.

import GoogleMaps3D

extension Camera {
 public static var sanFrancisco: Camera = .init(latitude: 37.39, longitude: -122.08)
}

Agrega una nueva vista a tu app

Crea un archivo nuevo llamado CameraDemo.swift de la siguiente manera: Agrega el esquema básico de una nueva vista de SwiftUI al archivo.

Agrega una variable @State llamada camera de tipo Camera. Inicialízala en la cámara sanFrancisco que acabas de definir.

El uso de @State te permite vincular el mapa al estado de la cámara y usarlo como fuente de confianza.

@State var camera: Camera = .sanFrancisco

Cambia la llamada a función Map() para incluir una propiedad camera. Usa la vinculación de estado de la cámara $camera para inicializar la propiedad camera en tu objeto @State de la cámara (.sanFrancisco).

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
    }
  }
}

Agrega una IU de navegación básica a tu app

Agrega un NavigationView al punto de entrada principal de la app, GoogleMaps3DDemoApp.swift.

Esto permitirá que los usuarios vean una lista de demos y hagan clic en cada una para abrirlas.

Edita GoogleMaps3DDemoApp.swift para agregar un NavigationView nuevo.

Agrega un List que contenga dos declaraciones NavigationLink.

El primer NavigationLink debe abrir ContentView() con una descripción Text Basic Map.

El segundo NavigationLink debería abrir CameraDemo().

...
      NavigationView {
        List {
          NavigationLink(destination: ContentView()) {
            Text("Basic Map")
          }
          NavigationLink(destination: CameraDemo()) {
            Text("Camera Demo")
          }
        }
      }
...

Cómo agregar una vista previa de Xcode

Las vistas previas son una función potente de Xcode que te permite ver tu app y, además, interactuar con ella a medida que realizas cambios.

Para agregar una vista previa, abre CameraDemo.swift. Agrega un bloque de código #Preview {} fuera de struct.

#Preview {
 CameraDemo()
}

Abre o actualiza el panel de vista previa en Xcode. El mapa debería mostrar San Francisco.

Configura vistas 3D personalizadas

Puedes especificar parámetros adicionales para controlar la cámara:

  • heading: Es el rumbo en grados desde el norte hacia el que se debe apuntar la cámara.
  • tilt: Es el ángulo de inclinación en grados, donde 0 es directamente hacia arriba y 90 es horizontal.
  • roll: Es el ángulo de balanceo alrededor del plano vertical de la cámara, en grados.
  • range: Es la distancia en metros de la cámara desde la ubicación de latitud y longitud.
  • altitude: Es la altura de la cámara sobre el nivel del mar.

Si no proporcionas ninguno de estos parámetros adicionales, se usarán los valores predeterminados.

Para que la vista de la cámara muestre más datos en 3D, establece los parámetros iniciales para mostrar una vista más cercana y inclinada.

Edita el Camera que definiste en MapHelpers.swift para incluir valores para altitude, heading, tilt, roll y range.

public static var sanFrancisco: Camera = .init(
  latitude: 37.7845812,
  longitude: -122.3660241,
  altitude: 585,
  heading: 288.0,
  tilt: 75.0,
  roll: 0.0,
  range: 100)

Compila y ejecuta la app para ver y explorar la nueva vista en 3D.

5. Animaciones básicas de la cámara

Hasta ahora, usaste la cámara para especificar una sola ubicación con una inclinación, altitud, rumbo y rango. En este paso, aprenderás a mover la vista de la cámara animando estas propiedades de un estado inicial a uno nuevo.

Mapa en 3D de Seattle

Cómo volar a una ubicación

Usarás el método Map.flyCameraTo() para animar la cámara de la ubicación inicial a una nueva.

El método flyCameraTo() toma una serie de parámetros:

  • un Camera que representa la ubicación de destino.
  • duration: Es la duración de la animación, en segundos.
  • trigger: Es un objeto observable que activará la animación cuando cambie su estado.
  • completion: Es el código que se ejecutará cuando se complete la animación.

Define una ubicación a la que volar

Abre el archivo MapHelpers.swift.

Define un nuevo objeto de cámara para mostrar Seattle.

public static var seattle: Camera = .init(latitude:
47.6210296,longitude: -122.3496903, heading: 149.0, tilt: 77.0, roll: 0.0, range: 4000)

Agrega un botón para activar la animación.

Abre tu CameraDemo.swift. Declara una nueva variable booleana dentro de struct.

Llámala animate con un valor inicial de false.

@State private var animate: Bool = false

Agrega un Button debajo de VStack. El Button iniciará la animación del mapa.

Dale al Button un Text adecuado, como "Comenzar a volar".

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera:Camera = .sanFrancisco
  @State private var animate: Bool = false

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
      Button("Start Flying") {
      }
    }
  }
}

En el cierre del botón, agrega código para activar o desactivar el estado de la variable animate.

      Button("Start Flying") {
        animate.toggle()
      }

Inicia la animación.

Agrega el código para activar la animación flyCameraTo() cuando cambie el estado de la variable animate.

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
        .flyCameraTo(
          .seattle,
          duration: 5,
          trigger: animate,
          completion: {  }
        )
      Button("Start Flying") {
        animate.toggle()
      }
    }
  }

Cómo volar alrededor de una ubicación

Puedes volar alrededor de una ubicación con el método Map.flyCameraAround(). Este método toma varios parámetros:

  • un Camera que define la ubicación y la vista.
  • un duration en segundos.
  • rounds: Es la cantidad de veces que se debe repetir la animación.
  • trigger: Es un objeto observable que activará la animación.
  • callback: Es el código que se ejecutará cuando se ejecute la animación.

Define una nueva variable @State llamada flyAround, con un valor inicial de false.

Cuando lo hayas hecho, agrega una llamada a flyCameraAround() inmediatamente después de la llamada al método flyCameraTo().

La duración del recorrido debe ser relativamente larga para que la vista cambie sin problemas.

Asegúrate de activar la animación flyCameraAround() cambiando el estado del objeto del activador cuando se complete flyCameraTo().

Tu código debería verse de la siguiente manera:

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera:Camera = .sanFrancisco
  @State private var animate: Bool = false
  @State private var flyAround: Bool = false

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
        .flyCameraTo(
          .seattle,
          duration: 5,
          trigger: animate,
          completion: { flyAround = true }
        )
        .flyCameraAround(
          .seattle,
          duration: 15,
          rounds: 0.5,
          trigger: flyAround,
          callback: {  }
        )
      Button("Start Flying") {
        animate.toggle()
      }
    }
  }
}

#Preview {
  CameraDemo()
}

Obtén una vista previa de la app o ejecútala para ver que la cámara vuela alrededor del destino una vez que se complete la animación de flyCameraTo().

6. Agrega un marcador a tu mapa.

En este paso, aprenderás a dibujar un pin de marcador en el mapa.

Crearás un objeto Marker y lo agregarás a tu mapa. El SDK usará un ícono predeterminado para el marcador. Por último, ajustarás la altitud del marcador y otras propiedades para cambiar su apariencia.

Mapa 3D que muestra un marcador de mapa

Crea una nueva vista de SwiftUI para tu demo de marcadores.

Agrega un nuevo archivo Swift a tu proyecto. Asígnale el nombre MarkerDemo.swift.

Agrega el esquema de una vista de SwiftUI y, luego, inicializa el mapa como lo hiciste en tu CameraDemo.

import SwiftUI
import GoogleMaps3D

struct MarkerDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
    }
  }
}

Cómo inicializar un objeto Marker

Declara una nueva variable de marcador llamada mapMarker. En la parte superior del bloque de código struct en MarkerDemo.swift.

Coloca la definición en la línea debajo de la declaración camera. Este código de muestra inicializa todas las propiedades disponibles.

  @State var mapMarker: Marker = .init(
    position: .init(
      latitude: 37.8044862,
      longitude: -122.4301493,
      altitude: 0.0),
    altitudeMode: .absolute,
    collisionBehavior: .required,
    extruded: false,
    drawsWhenOccluded: true,
    sizePreserved: true,
    zIndex: 0,
    label: "Test"
  )

Agrega el marcador a tu mapa.

Para dibujar el marcador, agrégalo a un cierre que se llame cuando se cree el mapa.

struct MarkerDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        mapMarker
      }
    }
  }
}

Agrega un NavigationLink nuevo a GoogleMaps3DDemoApp.swift con un destino de MarkerDemo() y Text que lo describa como "Demostración de marcadores".

...
      NavigationView {
        List {
          NavigationLink(destination: Map()) {
            Text("Basic Map")
          }
          NavigationLink(destination: CameraDemo()) {
            Text("Camera Demo")
          }
          NavigationLink(destination: MarkerDemo()) {
            Text("Marker Demo")
          }
        }
      }
...

Obtén una vista previa de tu app y ejecútala

Actualiza la vista previa o ejecuta la app para ver el marcador.

Marcadores extruidos

Los marcadores se pueden colocar sobre el suelo o la malla 3D con altitude y altitudeMode.

Copia la declaración mapMarker en MarkerDemo.swift a una nueva variable Marker llamada extrudedMarker.

Establece un valor distinto de cero para altitude, 50 es suficiente.

Cambia altitudeMode a .relativeToMesh y establece extruded en true. Usa latitude y longitude del fragmento de código aquí para colocar el marcador en la parte superior de un rascacielos.

  @State var extrudedMarker: Marker = .init(
    position: .init(
      latitude: 37.78980534,
      longitude:  -122.3969349,
      altitude: 50.0),
    altitudeMode: .relativeToMesh,
    collisionBehavior: .required,
    extruded: true,
    drawsWhenOccluded: true,
    sizePreserved: true,
    zIndex: 0,
    label: "Extruded"
  )

Vuelve a ejecutar o a obtener una vista previa de la app. El marcador debe aparecer en la parte superior de un edificio 3D.

7. Agrega un modelo a tu mapa.

Se puede agregar un Model de la misma manera que un Marker. Necesitarás un archivo de modelo al que se pueda acceder mediante una URL o agregarlo como archivo local en tu proyecto. En este paso, usaremos un archivo local que puedes descargar del repositorio de GitHub de este codelab.

Mapa en 3D de San Francisco con un modelo de globo aerostático

Agrega un archivo de modelo a tu proyecto

Crea una carpeta nueva en tu proyecto de Xcode llamada Models.

Descarga el modelo desde el repositorio de apps de ejemplo de GitHub. Para agregarlo a tu proyecto, arrástralo a la carpeta nueva en la vista del proyecto de Xcode.

Asegúrate de establecer el objetivo principal para tu app.

Revisa la configuración de Build Phases > Copy Bundle Resources de tu proyecto. El archivo del modelo debe estar en la lista de recursos copiados en el paquete. Si no está allí, haz clic en "+" para agregarla.

Agrega el modelo a tu app.

Crea un archivo SwiftUI nuevo llamado ModelDemo.swift.

Agrega sentencias import para SwiftUI y GoogleMaps3D como en los pasos anteriores.

Declara un Map dentro de un VStack en tu body.

import SwiftUI
import GoogleMaps3D

struct ModelDemo: View {
  @State var camera: Camera = .sanFrancisco
  
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        
      }
    }
  }
}

Obtén la ruta de acceso del modelo de tu paquete. Agrega el código para esto fuera de struct.

private let fileUrl = Bundle.main.url(forResource: "balloon", withExtension: "glb")

Declara una variable para tu modelo dentro de la estructura.

Proporciona un valor predeterminado en caso de que no se proporcione fileUrl.

  @State var balloonModel: Model = .init(
    position: .init(
      latitude: 37.791376,
      longitude: -122.397571,
      altitude: 300.0),
    url: URL(fileURLWithPath: fileUrl?.relativePath ?? ""),
    altitudeMode: .absolute,
    scale: .init(x: 5, y: 5, z: 5),
    orientation: .init(heading: 0, tilt: 0, roll: 0)
  )

3. Usa el modelo con tu mapa.

Al igual que con la adición de un Marker, solo proporciona la referencia a tu Model en la declaración Map.

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        balloonModel
      }
    }
  }

Obtén una vista previa de tu app y ejecútala

Agrega un nuevo NavigationLink a GoogleMaps3DDemoApp.swift, con un destino de ModelDemo() y la Text "Demo de modelos".

...
          NavigationLink(destination: ModelDemo()) {
            Text("Model Demo")
          }
...

Actualiza la vista previa o ejecuta la app para ver el modelo.

8. Dibuja una línea y un polígono en tu mapa.

En este paso, aprenderás a agregar líneas y formas de polígonos a tu mapa 3D.

Para simplificar, definirás las formas como arrays de objetos LatLngAltitude. En una aplicación real, los datos se pueden cargar desde un archivo, una llamada a la API o una base de datos.

Mapa 3D de San Francisco que muestra dos polígonos y una polilínea

Crea algunos objetos de forma para administrar los datos de forma.

Agrega una nueva definición de Camera a MapHelpers.swift que apunte al centro de San Francisco.

  public static var downtownSanFrancisco: Camera = .init(latitude: 37.7905, longitude: -122.3989, heading: 25, tilt: 71, range: 2500) 

Agrega un archivo nuevo a tu proyecto llamado ShapesDemo.swift. Agrega un struct llamado ShapesDemo que implemente el protocolo View y agrégale un body.

struct ShapesDemo: View {
  @State var camera: Camera = .downtownSanFrancisco

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {

      }
    }
  }
}

Las clases que usarás para administrar los datos de forma son Polyline y Polygon. Abre ShapesDemo.swift y agrégalos a struct de la siguiente manera.

var polyline: Polyline = .init(coordinates: [
    LatLngAltitude(latitude: 37.80515638571346, longitude: -122.4032569467164, altitude: 0),
    LatLngAltitude(latitude: 37.80337073509504, longitude: -122.4012878349353, altitude: 0),
    LatLngAltitude(latitude: 37.79925208843463, longitude: -122.3976697250461, altitude: 0),
    LatLngAltitude(latitude: 37.7989102378512, longitude: -122.3983408725656, altitude: 0),
    LatLngAltitude(latitude: 37.79887832784348, longitude: -122.3987094864192, altitude: 0),
    LatLngAltitude(latitude: 37.79786443410338, longitude: -122.4066878788802, altitude: 0),
    LatLngAltitude(latitude: 37.79549248916587, longitude: -122.4032992702785, altitude: 0),
    LatLngAltitude(latitude: 37.78861484290265, longitude: -122.4019489189814, altitude: 0),
    LatLngAltitude(latitude: 37.78618687561075, longitude: -122.398969592545, altitude: 0),
    LatLngAltitude(latitude: 37.7892310309145, longitude: -122.3951458683092, altitude: 0),
    LatLngAltitude(latitude: 37.7916358762409, longitude: -122.3981969390652, altitude: 0)
  ])
  .stroke(GoogleMaps3D.Polyline.StrokeStyle(
    strokeColor: UIColor(red: 0.09803921568627451, green: 0.403921568627451, blue: 0.8235294117647058, alpha: 1),
    strokeWidth: 10.0,
    outerColor: .white,
    outerWidth: 0.2
    ))
  .contour(GoogleMaps3D.Polyline.ContourStyle(isGeodesic: true))

  var originPolygon: Polygon = .init(outerCoordinates: [
    LatLngAltitude(latitude: 37.79165766856578, longitude:  -122.3983762901255, altitude: 300),
    LatLngAltitude(latitude: 37.7915324439261, longitude:  -122.3982171091383, altitude: 300),
    LatLngAltitude(latitude: 37.79166617650914, longitude:  -122.3980478493319, altitude: 300),
    LatLngAltitude(latitude: 37.79178986470217, longitude:  -122.3982041104199, altitude: 300),
    LatLngAltitude(latitude: 37.79165766856578, longitude:  -122.3983762901255, altitude: 300 )
  ],
  altitudeMode: .relativeToGround)
  .style(GoogleMaps3D.Polygon.StyleOptions(fillColor:.green, extruded: true) )

  var destinationPolygon: Polygon = .init(outerCoordinates: [
      LatLngAltitude(latitude: 37.80515661739527, longitude:  -122.4034307490334, altitude: 300),
      LatLngAltitude(latitude: 37.80503794515428, longitude:  -122.4032633416024, altitude: 300),
      LatLngAltitude(latitude: 37.80517850164195, longitude:  -122.4031056058006, altitude: 300),
      LatLngAltitude(latitude: 37.80529346901115, longitude:  -122.4032622466595, altitude: 300),
      LatLngAltitude(latitude: 37.80515661739527, longitude:  -122.4034307490334, altitude: 300 )
  ],
  altitudeMode: .relativeToGround)
  .style(GoogleMaps3D.Polygon.StyleOptions(fillColor:.red, extruded: true) )

Observa los parámetros de inicialización que se usan.

  • altitudeMode: .relativeToGround se usa para extruir los polígonos a una altura específica sobre el suelo.
  • altitudeMode: .clampToGround se usa para que la polilínea siga la forma de la superficie de la Tierra.
  • Los estilos se establecen en los objetos Polygon encadenando una llamada de método a styleOptions() después de llamar a .init().

Agrega las formas al mapa

Al igual que en los pasos anteriores, las formas se pueden agregar directamente al cierre Map. Crea tu Map dentro de un VStack.

...
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        polyline
        originPolygon
        destinationPolygon
      }
    }
  }
...

Obtén una vista previa de tu app y ejecútala

Agrega código de vista previa y, luego, inspecciona tu app en el panel de vista previa de Xcode.

#Preview {
  ShapesDemo()
}

Para ejecutar la app, agrega un nuevo NavigationLink a GoogleMaps3DDemoApp.swift que abra la nueva vista de demostración.

...
          NavigationLink(destination: ShapesDemo()) {
            Text("Shapes Demo")
          }
...

Ejecuta la app y explora las formas que agregaste.

9. Controla los eventos de presión en los marcadores de Place

En este paso, aprenderás a responder a los toques del usuario en los marcadores de Place.

Mapa que muestra una ventana emergente con un ID de Place

Nota: Para ver los marcadores de Place en el mapa, debes establecer MapMode en .hybrid.

Para controlar el toque, debes implementar el método Map.onPlaceTap.

El evento onPlaceTap proporciona un objeto PlaceTapInfo desde el que puedes obtener el ID de Place del marcador de Place que se presionó.

Puedes usar el ID de lugar para buscar más detalles con el SDK de Places o la API de Places.

Agrega una nueva vista de Swift

Agrega el siguiente código a un nuevo archivo Swift llamado PlaceTapDemo.swift.

import GoogleMaps3D
import SwiftUI

struct PlaceTapDemo: View {
  @State var camera: Camera = .sanFrancisco
  @State var isPresented = false
  @State var tapInfo: PlaceTapInfo?

  var body: some View {
    Map(camera: $camera, mode: .hybrid)
      .onPlaceTap { tapInfo in
        self.tapInfo = tapInfo
        isPresented.toggle()
      }
      .alert(
        "Place tapped - \(tapInfo?.placeId ?? "nil")",
        isPresented: $isPresented,
        actions: { Button("OK") {} }
      )
  }
}
#Preview {
  PlaceTapDemo()
}

Obtén una vista previa y ejecuta tu app

Abre el panel de vista previa para obtener una vista previa de la app.

Para ejecutar la app, agrega un NavigationLink nuevo a GoogleMaps3DDemoApp.swift.

...
          NavigationLink(destination: PlaceTapDemo()) {
            Text("Place Tap Demo")
          }
...

10. (Opcional) Ve más allá

Animaciones de cámara avanzadas

Algunos casos de uso requieren animaciones fluidas a lo largo de una secuencia o lista de ubicaciones o estados de la cámara, por ejemplo, un simulador de vuelo o la repetición de una caminata o una carrera.

En este paso, aprenderás a cargar una lista de ubicaciones desde un archivo y a animar cada ubicación en secuencia.

Vista de mapa en 3D del acercamiento a Innsbruck

Carga un archivo que contenga una secuencia de ubicaciones.

Descarga flightpath.json del repositorio de apps de ejemplo de GitHub.

Crea una carpeta nueva en tu proyecto de Xcode llamada JSON.

Arrastra flightpath.json a la carpeta JSON en Xcode.

Establece el objetivo principal de tu app. Verifica que la configuración de Copy Bundle Resources de tu proyecto incluya este archivo.

Crea dos archivos Swift nuevos en tu app llamados FlightPathData.swift y FlightDataLoader.swift.

Copia el siguiente código en tu app. Este código crea estructuras y clases que leen un archivo local llamado "flighpath.json" y lo decodifican como JSON.

Las estructuras FlightPathData y FlightPathLocation representan la estructura de datos en el archivo JSON como objetos Swift.

La clase FlightDataLoader lee los datos del archivo y los decodifica. Adopta el protocolo ObservableObject para permitir que tu app observe los cambios en sus datos.

Los datos analizados se exponen a través de una propiedad publicada.

FlightPaths.swift

import GoogleMaps3D

struct FlightPathData: Decodable {
  let flight: [FlightPathLocation]
}

struct FlightPathLocation: Decodable {
  let timestamp: Int64
  let latitude: Double
  let longitude: Double
  let altitude: Double
  let bearing: Double
  let speed: Double
}

FlightDataLoader.swift

import Foundation

public class FlightDataLoader : ObservableObject {

  @Published var flightPathData: FlightPathData = FlightPathData(flight:[])
  @Published var isLoaded: Bool = false

  public init() {
    load("flightpath.json")
  }
  
  public func load(_ path: String) {
    if let url = Bundle.main.url(forResource: path, withExtension: nil){
      if let data = try? Data(contentsOf: url){
        let jsondecoder = JSONDecoder()
        do{
          let result = try jsondecoder.decode(FlightPathData.self, from: data)
          flightPathData = result
          isLoaded = true
        }
        catch {
          print("Error trying to load or parse the JSON file.")
        }
      }
    }
  }
}

Anima la cámara a lo largo de cada ubicación

Para animar la cámara entre una secuencia de pasos, usarás un KeyframeAnimator.

Cada Keyframe se creará como un CubicKeyframe, de modo que los cambios en el estado de la cámara se animen de forma fluida.

El uso de flyCameraTo() haría que la vista "rebote" entre cada ubicación.

Primero, declara una cámara llamada "innsbruck" en MapHelpers.swift.

public static var innsbruck: Camera = .init(
  latitude: 47.263,
  longitude: 11.3704,
  altitude: 640.08,
  heading: 237,
  tilt: 80.0,
  roll: 0.0,
  range: 200)

Ahora, configura una nueva vista en un archivo nuevo llamado FlyAlongRoute.swift.

Importa SwiftUI y GoogleMaps3D. Agrega un Map y un Button dentro de un VStack. Configura Button para activar o desactivar el estado de la variable booleana animation.

Declara un objeto State para FlightDataLoader, que cargará el archivo JSON cuando se inicialice.

import GoogleMaps3D
import SwiftUI

struct FlyAlongRoute: View {
  @State private var camera: Camera = .innsbruck
  @State private var flyToDuration: TimeInterval = 5
  @State var animation: Bool = true

  @StateObject var flightData: FlightDataLoader = FlightDataLoader()

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
      Button("Fly Along Route"){
        animation.toggle()
      }
    }
  }
}

Crea los fotogramas clave

El proceso básico es crear una función que devuelva un fotograma nuevo en la secuencia de animación. Cada fotograma nuevo define el siguiente estado de la cámara para que el animador lo anime. Una vez que se cree esta función, llámala con cada ubicación del archivo en secuencia.

Agrega dos funciones a tu struct FlyAlongRoute. La función makeKeyFrame muestra un CubicKeyframe con un estado de la cámara. La función makeCamera toma un paso en la secuencia de datos de vuelo y muestra un objeto Camera que representa el paso.

func makeKeyFrame(step: FlightPathLocation) -> CubicKeyframe<Camera> {
  return CubicKeyframe(
    makeCamera(step: step),
    duration: flyToDuration
  )
}

func makeCamera(step: FlightPathLocation) -> Camera {
  return .init(
    latitude: step.latitude,
    longitude: step.longitude,
    altitude: step.altitude,
    heading: step.bearing,
    tilt: 75,
    roll: 0,
    range: 200
  )
}

Cómo crear la animación

Llama a keyframeAnimator después de la inicialización de Map y establece los valores iniciales.

Necesitarás un estado inicial de la cámara basado en la primera ubicación de la ruta de vuelo.

La animación debe activarse en función de un estado de cambio de variable.

El contenido de keyframeAnimator debe ser un mapa.

La lista real de fotogramas clave se genera mediante un bucle en cada ubicación de la ruta de vuelo.

   VStack {
      Map(camera: $camera, mode: .hybrid)
        .keyframeAnimator(
          initialValue: makeCamera(step: flightData.flightPathData.flight[0]),
          trigger: animation,
          content: { view, value in
            Map(camera: .constant(value), mode: .hybrid)
          },
          keyframes: { _ in
            KeyframeTrack(content: {
              for i in  1...flightData.flightPathData.flight.count-1 {
                makeKeyFrame(step: flightData.flightPathData.flight[i])
              }
            })
          }
        )
   }

Obtén una vista previa de la app y ejecútala.

Abre el panel de vista previa para obtener una vista previa de tu vista.

Agrega un NavigationLink nuevo con un destino de FlightPathDemo() a GoogleMaps3DDemoApp.swift y ejecuta la app para probarlo.

11. Felicitaciones

Compilaste correctamente una aplicación que hace lo siguiente:

  • Agrega un mapa 3D básico a tu app.
  • Agrega marcadores, líneas, polígonos y modelos a tu mapa.
  • Implementa código para controlar la cámara y volar por el mapa y alrededor de ubicaciones específicas.

Qué aprendiste

  • Cómo agregar el paquete GoogleMaps3D a una app de SwiftUI de Xcode
  • Cómo inicializar un mapa 3D con una clave de API y una vista predeterminada
  • Cómo agregar marcadores, modelos 3D, líneas y polígonos a tu mapa
  • Cómo controlar la cámara para animar el movimiento a otra ubicación
  • Cómo controlar los eventos de clic en los marcadores de Place

Próximos pasos

  • Consulta la guía para desarrolladores para obtener más detalles sobre lo que puedes hacer con el SDK de Mapas 3D para iOS.
  • Responde la siguiente encuesta para ayudarnos a crear el contenido que te resulte más útil:

¿Qué otros codelabs te gustaría ver?

Visualización de datos en los mapas Más información para personalizar el estilo de mis mapas Cómo crear interacciones 3D en los mapas