Criar seu primeiro mapa 3D com o SwiftUI

1. Antes de começar

Este codelab ensina a criar um app de mapas 3D no SwiftUI usando o SDK do Maps 3D para iOS.

App mostrando um mapa 3D de São Francisco

Você vai aprender a:

  • Como controlar a câmera para visualizar locais e voar pelo mapa.
  • Como adicionar marcadores e modelos
  • Como desenhar linhas e polígonos
  • Como processar cliques do usuário em marcadores de lugar.

Pré-requisitos

  • Um projeto do Google Console com faturamento ativado
  • Uma chave de API, opcionalmente restrita ao SDK do Maps 3D para iOS.
  • Conhecimento básico de desenvolvimento para iOS com SwiftUI.

Atividades deste laboratório

  • Configurar o Xcode e importar o SDK usando o Swift Package Manager
  • Configurar o app para usar uma chave de API
  • Adicionar um mapa 3D básico ao seu app
  • Controlar a câmera para voar para e em torno de locais específicos
  • Adicionar marcadores, linhas, polígonos e modelos ao mapa

O que é necessário

  • Xcode 15 ou mais recente.

2. Começar a configuração

Para a etapa de ativação a seguir, é necessário ativar o SDK 3D do Maps para iOS.

Configurar a Plataforma Google Maps

Caso você ainda não tenha uma conta do Google Cloud Platform e um projeto com faturamento ativado, veja como criá-los no guia da Plataforma Google Maps.

  1. No Console do Cloud, clique no menu suspenso do projeto e selecione o projeto que você quer usar neste codelab.

  1. Ative as APIs e os SDKs da Plataforma Google Maps necessários para este codelab no Google Cloud Marketplace. Para fazer isso, siga as etapas descritas neste vídeo ou nesta documentação.
  2. Gere uma chave de API na página Credenciais do Console do Cloud. Siga as etapas indicadas neste vídeo ou nesta documentação. Todas as solicitações à Plataforma Google Maps exigem uma chave de API.

Ativar o SDK do Maps 3D para iOS

Você pode encontrar o SDK 3D do Maps para iOS usando o link do menu "Plataforma Google Maps > APIs e serviços" no console.

Clique em "Ativar" para ativar a API no projeto selecionado.

Ativar o SDK 3D do Maps no Google Console

3. Criar um app SwiftUI básico

Observação: você pode encontrar o código da solução para cada etapa no repositório de apps de exemplo do codelab no GitHub .

Crie um novo app no Xcode.

O código dessa etapa pode ser encontrado na pasta GoogleMaps3DDemo no GitHub.

Abra o Xcode e crie um novo app. Especifique a SwiftUI.

Chame o app de GoogleMaps3DDemo, com um nome de pacote com.example.GoogleMaps3DDemo.

Importar a biblioteca GoogleMaps3D para o projeto

Adicione o SDK ao seu projeto usando o Swift Package Manager.

No projeto ou espaço de trabalho do Xcode, acesse "File > Add Package Dependencies". Insira https://github.com/googlemaps/ios-maps-3d-sdk como o URL, pressione Enter para extrair o pacote e clique em "Adicionar pacote".

Na janela "Escolher produtos do pacote", verifique se GoogleMaps3D será adicionado ao destino principal designado. Quando terminar, clique em "Adicionar pacote".

Para verificar a instalação, acesse o painel "Geral" do destino. Em "Frameworks, bibliotecas e conteúdo incorporado", você vai encontrar os pacotes instalados. Você também pode conferir a seção "Dependências do pacote" do Project Navigator para verificar o pacote e a versão dele.

Adicionar sua chave de API

Você pode codificar a chave da API no app, mas essa não é uma prática recomendada. A adição de um arquivo de configuração permite manter a chave de API como um segredo e evita que ela seja verificada no controle de origem.

Criar um novo arquivo de configuração na pasta raiz do projeto

No Xcode, verifique se você está visualizando a janela do explorador de projetos. Clique com o botão direito do mouse na raiz do projeto e selecione "New File from Template". Role a tela até encontrar "Arquivo de configurações de configuração". Selecione essa opção e clique em "Próxima". Nomeie o arquivo como Config.xcconfig e verifique se a pasta raiz do projeto está selecionada. Clique em "Criar" para criar o arquivo.

No editor, adicione uma linha ao arquivo de configuração da seguinte maneira: MAPS_API_KEY = YOUR_API_KEY

Substitua YOUR_API_KEY pela sua chave de API.

Adicione essa configuração a Info.plist.

Para fazer isso, selecione a raiz do projeto e clique na guia "Informações".

Adicione uma nova propriedade chamada MAPS_API_KEY com o valor $(MAPS_API_KEY).

O código de app de exemplo tem um arquivo Info.plist que especifica essa propriedade.

Adicionar um mapa

Abra o arquivo chamado GoogleMaps3DDemoApp.swift. Esse é o ponto de entrada e a navegação principal do app.

Ele chama ContentView(), que mostra uma mensagem Hello World.

Abra ContentView.swift no editor.

Adicione uma instrução import para GoogleMaps3D.

Exclua o código dentro do bloco var body: some View {}. Declare uma nova Map() dentro do body.

A configuração mínima necessária para inicializar um Map é um MapMode. Ele tem dois valores possíveis:

  • .hybrid: imagens de satélite com vias e marcadores ou
  • .satellite: somente imagens de satélite.

Escolha a .hybrid.

Seu arquivo ContentView.swift vai ficar assim.

import GoogleMaps3D
import SwiftUI

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

Defina sua chave de API.

A chave da API precisa ser definida antes da inicialização do mapa.

Para fazer isso, defina Map.apiKey no manipulador de eventos init() de qualquer View que contenha um mapa. Também é possível definir o valor em GoogleMaps3DDemoApp.swift antes de chamar ContentView().

Em GoogleMaps3DDemoApp.swift, defina Map.apiKey no manipulador de eventos onAppear do WindowGroup.

Buscar a chave de API no arquivo de configuração

Use Bundle.main.infoDictionary para acessar a configuração MAPS_API_KEY que você criou no arquivo de configuração.

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
    }
  }
}

Crie e execute o app para verificar se ele carrega corretamente. Você vai ver um mapa do globo.

Mapa 3D mostrando a Terra

4. Usar uma câmera para controlar a visualização do mapa

Criar um objeto de estado da câmera

As visualizações de mapa 3D são controladas pela classe Camera. Nesta etapa, você vai aprender a especificar o local, a altitude, a direção, a inclinação, a rotação e o alcance para personalizar a visualização do mapa.

Visualização do mapa 3D de São Francisco

Criar uma classe Helpers para armazenar as configurações da câmera

Adicione um novo arquivo vazio chamado MapHelpers.swift. No novo arquivo, importe GoogleMaps3D e adicione uma extensão à classe Camera. Adicione uma variável chamada sanFrancisco. Inicialize essa variável como um novo objeto Camera. Localize a câmera em latitude: 37.39, longitude: -122.08.

import GoogleMaps3D

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

Adicionar uma nova visualização ao app

Crie um novo arquivo chamado CameraDemo.swift. Adicione o esboço básico de uma nova visualização do SwiftUI ao arquivo.

Adicione uma variável @State com o nome camera do tipo Camera. Inicialize-a para a câmera sanFrancisco que você acabou de definir.

O uso de @State permite vincular o mapa ao estado da câmera e usá-lo como a fonte da verdade.

@State var camera: Camera = .sanFrancisco

Mude a chamada de função Map() para incluir uma propriedade camera. Use a vinculação de estado da câmera $camera para inicializar a propriedade camera no objeto @State da câmera (.sanFrancisco).

import SwiftUI
import GoogleMaps3D

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

Adicionar a interface de navegação básica ao app

Adicione um NavigationView ao ponto de entrada principal do app, GoogleMaps3DDemoApp.swift.

Isso permite que os usuários vejam uma lista de demonstrações e cliquem em cada uma para abri-las.

Edite GoogleMaps3DDemoApp.swift para adicionar uma nova NavigationView.

Adicione um List que contenha duas declarações NavigationLink.

O primeiro NavigationLink precisa abrir ContentView() com uma descrição Text Basic Map.

O segundo NavigationLink vai abrir CameraDemo().

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

Adicionar uma visualização do Xcode

As prévias são um recurso poderoso do Xcode que permite visualizar e interagir com o app enquanto você faz mudanças nele.

Para adicionar uma visualização, abra CameraDemo.swift. Adicione um bloco de código #Preview {} fora do struct.

#Preview {
 CameraDemo()
}

Abra ou atualize o painel de visualização no Xcode. O mapa deve mostrar São Francisco.

Configurar visualizações 3D personalizadas

É possível especificar outros parâmetros para controlar a câmera:

  • heading: a direção em graus a partir do norte para apontar a câmera.
  • tilt: o ângulo de inclinação em graus, em que 0 é diretamente acima e 90 é horizontal.
  • roll: o ângulo de inclinação em torno do plano vertical da câmera, em graus
  • range: a distância em metros da câmera em relação à latitude, longitude
  • altitude: a altura da câmera acima do nível do mar

Se você não fornecer nenhum desses parâmetros adicionais, os valores padrão serão usados.

Para que a visualização da câmera mostre mais dados em 3D, defina os parâmetros iniciais para mostrar uma visualização mais próxima e inclinada.

Edite o Camera definido em MapHelpers.swift para incluir valores para altitude, heading, tilt, roll e 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)

Crie e execute o app para conferir e testar a nova visualização em 3D.

5. Animações básicas da câmera

Até agora, você usou a câmera para especificar um único local com inclinação, altitude, rumo e alcance. Nesta etapa, você vai aprender a mover a visualização da câmera animando essas propriedades de um estado inicial para um novo.

Mapa 3D de Seattle

Voar para um local

Você vai usar o método Map.flyCameraTo() para animar a câmera do local inicial para um novo local.

O método flyCameraTo() usa vários parâmetros:

  • um Camera que representa o local final.
  • duration: quanto tempo a animação vai durar, em segundos.
  • trigger: um objeto observável que aciona a animação quando o estado dele muda.
  • completion: código que será executado quando a animação for concluída.

Definir um local para voar

Abra o arquivo MapHelpers.swift.

Defina um novo objeto de câmera 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)

Adicione um botão para acionar a animação.

Abra o CameraDemo.swift. Declare uma nova variável booleana dentro do struct.

Chame-a de animate com um valor inicial de false.

@State private var animate: Bool = false

Adicione um Button abaixo do VStack. O Button vai iniciar a animação do mapa.

Dê ao Button um Text adequado, como "Start Flying".

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") {
      }
    }
  }
}

Na declaração de fechamento do botão, adicione um código para alternar o estado da variável animate.

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

Inicia a animação.

Adicione o código para acionar a animação flyCameraTo() quando o estado da variável animate mudar.

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

Voar em torno de um local

É possível voar em torno de um local usando o método Map.flyCameraAround(). Esse método usa vários parâmetros:

  • um Camera que define o local e a visualização.
  • uma duration em segundos.
  • rounds: o número de vezes que a animação será repetida.
  • trigger: um objeto observável que aciona a animação.
  • callback: código que será executado quando a animação for executada.

Defina uma nova variável @State chamada flyAround, com um valor inicial de false.

Depois disso, adicione uma chamada para flyCameraAround() imediatamente após a chamada do método flyCameraTo().

A duração do voo precisa ser relativamente longa para que a visualização mude sem problemas.

Ative a animação flyCameraAround() mudando o estado do objeto de acionamento quando flyCameraTo() for concluído.

O código ficará assim.

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()
}

Visualize ou execute o app para conferir se a câmera voa ao redor do destino quando a animação flyCameraTo() é concluída.

6. Adicione um marcador ao mapa.

Nesta etapa, você vai aprender a desenhar um marcador no mapa.

Você vai criar um objeto Marker e adicioná-lo ao mapa. O SDK vai usar um ícone padrão para o marcador. Por fim, você vai ajustar a altitude do marcador e outras propriedades para mudar a forma como ele aparece.

Mapa 3D mostrando um marcador

Crie uma nova visualização SwiftUI para a demonstração do marcador.

Adicione um novo arquivo Swift ao projeto. Dê o nome MarkerDemo.swift a ele.

Adicione o contorno de uma visualização do SwiftUI e inicialize o mapa como fez na CameraDemo.

import SwiftUI
import GoogleMaps3D

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

Inicializar um objeto Marker

Declare uma nova variável de marcador com o nome mapMarker. Na parte de cima do bloco de código struct em MarkerDemo.swift.

Coloque a definição na linha abaixo da declaração camera. Este código de exemplo inicializa todas as propriedades disponíveis.

  @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"
  )

Adicione o marcador ao mapa.

Para desenhar o marcador, adicione-o a uma interdição chamada quando o mapa for criado.

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

Adicione um novo NavigationLink a GoogleMaps3DDemoApp.swift com um destino de MarkerDemo() e Text descrevendo-o como "Marker Demo".

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

Visualizar e executar o app

Atualize a visualização ou execute o app para conferir o marcador.

Marcadores extrudados

Os marcadores podem ser colocados acima do solo ou da malha 3D usando altitude e altitudeMode.

Copie a declaração mapMarker em MarkerDemo.swift para uma nova variável Marker chamada extrudedMarker.

Defina um valor diferente de zero para altitude. 50 é suficiente.

Mude altitudeMode para .relativeToMesh e defina extruded como true. Use latitude e longitude do snippet de código aqui para colocar o marcador em cima de um arranha-céu.

  @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"
  )

Execute ou visualize o app novamente. O marcador vai aparecer em cima de um edifício 3D.

7. Adicione um modelo ao mapa.

Um Model pode ser adicionado da mesma forma que um Marker. Você vai precisar de um arquivo de modelo que possa ser acessado por URL ou adicionado como um arquivo local no projeto. Nesta etapa, vamos usar um arquivo local que você pode fazer o download no repositório do GitHub para este codelab.

Mapa 3D de São Francisco com um modelo de balão de ar quente

Adicionar um arquivo de modelo ao projeto

Crie uma nova pasta no projeto Xcode chamada Models.

Faça o download do modelo no repositório de apps de exemplo do GitHub. Adicione-o ao seu projeto arrastando-o para a nova pasta na visualização do projeto do Xcode.

Defina o destino como o destino principal do app.

Verifique as configurações de Build Phases > Copy Bundle Resources do seu projeto. O arquivo de modelo precisa estar na lista de recursos copiados para o pacote. Se não estiver, clique em "+" para adicionar.

Adicione o modelo ao app.

Crie um novo arquivo SwiftUI chamado ModelDemo.swift.

Adicione instruções import para SwiftUI e GoogleMaps3D, como nas etapas anteriores.

Declare uma Map dentro de uma VStack na body.

import SwiftUI
import GoogleMaps3D

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

Receba o caminho do modelo do seu pacote. Adicione o código para isso fora da struct.

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

Declare uma variável para o modelo dentro da estrutura.

Forneça um valor padrão caso fileUrl não seja fornecido.

  @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. Use o modelo com seu mapa.

Assim como ao adicionar um Marker, basta fornecer a referência ao Model na declaração Map.

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

Visualizar e executar o app

Adicione um novo NavigationLink a GoogleMaps3DDemoApp.swift, com um destino de ModelDemo() e o Text "Model Demo".

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

Atualize a visualização ou execute o app para conferir o modelo.

8. Desenhe uma linha e um polígono no mapa.

Nesta etapa, você vai aprender a adicionar linhas e formas poligonais ao mapa 3D.

Para simplificar, você vai definir as formas como matrizes de objetos LatLngAltitude. Em um aplicativo real, os dados podem ser carregados de um arquivo, de uma chamada de API ou de um banco de dados.

Mapa 3D de São Francisco mostrando dois polígonos e uma polilinha

Crie alguns objetos de forma para gerenciar os dados de forma.

Adicione uma nova definição de Camera a MapHelpers.swift que analise o centro de São Francisco.

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

Adicione um novo arquivo ao projeto chamado ShapesDemo.swift. Adicione uma struct chamada ShapesDemo que implemente o protocolo View e adicione uma body a ela.

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

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

      }
    }
  }
}

As classes que você vai usar para gerenciar dados de forma são Polyline e Polygon. Abra ShapesDemo.swift e adicione-os ao struct da seguinte maneira:

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) )

Observe os parâmetros de inicialização usados.

  • altitudeMode: .relativeToGround é usado para extrair os polígonos a uma altura específica acima do solo.
  • altitudeMode: .clampToGround é usado para fazer a polilinha seguir a forma da superfície da Terra.
  • Os estilos são definidos nos objetos Polygon encadeando uma chamada de método para styleOptions() depois que .init() é chamado

Adicionar as formas ao mapa

Assim como nas etapas anteriores, as formas podem ser adicionadas diretamente à interseção Map. Crie o Map dentro de um VStack.

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

Visualizar e executar o app

Adicione o código de visualização e inspecione o app no painel de visualização do Xcode.

#Preview {
  ShapesDemo()
}

Para executar o app, adicione um novo NavigationLink a GoogleMaps3DDemoApp.swift que abra a nova visualização de demonstração.

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

Execute o app e confira as formas que você adicionou.

9. Processar eventos de toque em marcadores de lugar

Nesta etapa, você vai aprender a responder aos toques do usuário nos marcadores de lugar.

Mapa mostrando uma janela pop-up com um ID de lugar

Observação: para ver os marcadores de lugar no mapa, defina MapMode como .hybrid.

O processamento do toque requer a implementação do método Map.onPlaceTap.

O evento onPlaceTap fornece um objeto PlaceTapInfo, em que é possível acessar o ID do lugar do marcador de lugar tocado.

É possível usar o ID do lugar para procurar mais detalhes usando o SDK do Places ou a API Places.

Adicionar uma nova visualização Swift

Adicione o código abaixo a um novo arquivo Swift chamado 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()
}

Visualizar e executar o app

Abra o painel de visualização para conferir o app.

Para executar o app, adicione uma nova NavigationLink a GoogleMaps3DDemoApp.swift.

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

10. (Opcional) Vá além

Animações avançadas da câmera

Alguns casos de uso exigem animação suave em uma sequência ou lista de locais ou estados da câmera, por exemplo, um simulador de voo ou a reprodução de uma caminhada ou corrida.

Nesta etapa, você vai aprender a carregar uma lista de locais de um arquivo e animar cada local em sequência.

Visualização de mapa 3D da aproximação de Innsbruck

Carregar um arquivo com uma sequência de locais.

Faça o download de flightpath.json no repositório de apps de exemplo do GitHub.

Crie uma nova pasta no projeto Xcode chamada JSON.

Arraste flightpath.json para a pasta JSON no Xcode.

Defina o destino como o destino principal do app. Verifique se as configurações de recursos de cópia de pacote do projeto incluem esse arquivo.

Crie dois novos arquivos Swift no app chamados FlightPathData.swift e FlightDataLoader.swift.

Copie o código abaixo no seu app. Esse código cria estruturas e classes que leem um arquivo local chamado "flighpath.json" e o decodifica como JSON.

Os structs FlightPathData e FlightPathLocation representam a estrutura de dados no arquivo JSON como objetos Swift.

A classe FlightDataLoader lê e decodifica os dados do arquivo. Ele adota o protocolo ObservableObject para permitir que o app observe mudanças nos dados.

Os dados analisados são expostos por uma propriedade 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.")
        }
      }
    }
  }
}

Animar a câmera em cada local

Para animar a câmera entre uma sequência de etapas, use uma KeyframeAnimator.

Cada Keyframe será criado como um CubicKeyframe, para que as mudanças no estado da câmera sejam animadas sem problemas.

O uso de flyCameraTo() faz com que a visualização "rebote" entre cada local.

Primeiro, declare uma câmera chamada "innsbruck" em 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)

Agora, configure uma nova visualização em um arquivo chamado FlyAlongRoute.swift.

Importe SwiftUI e GoogleMaps3D. Adicione uma Map e uma Button dentro de uma VStack. Configure o Button para alternar o estado da variável booleana animation.

Declare um objeto State para FlightDataLoader, que vai carregar o arquivo JSON quando ele for inicializado.

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()
      }
    }
  }
}

Criar os frames-chave

O processo básico é criar uma função que retorne um novo frame na sequência de animação. Cada novo frame define o próximo estado da câmera para a animação. Depois que essa função for criada, chame-a com cada local do arquivo em sequência.

Adicione duas funções à estrutura FlyAlongRoute. A função makeKeyFrame retorna um CubicKeyframe com um estado da câmera. A função makeCamera considera uma etapa na sequência de dados de voo e retorna um objeto Camera que representa essa etapa.

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
  )
}

Criar a animação

Chame o keyframeAnimator após a inicialização do Map e defina os valores iniciais.

Você vai precisar de um estado inicial da câmera com base no primeiro local no trajeto do voo.

A animação precisa ser acionada com base em uma variável que muda de estado.

O conteúdo de keyframeAnimator precisa ser um mapa.

A lista real de keyframes é gerada com a repetição de cada local no caminho do voo.

   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])
              }
            })
          }
        )
   }

Visualize e execute o app.

Abra o painel de visualização para conferir sua visualização.

Adicione um novo NavigationLink com um destino de FlightPathDemo() para GoogleMaps3DDemoApp.swift e execute o app para testar.

11. Parabéns

Você criou um aplicativo que

  • Adiciona um mapa 3D básico ao seu app.
  • Adiciona marcadores, linhas, polígonos e modelos ao mapa.
  • Implementa o código para controlar a câmera e fazer com que ela voe pelo mapa e em torno de locais específicos.

O que você aprendeu

  • Como adicionar o pacote GoogleMaps3D a um app SwiftUI do Xcode.
  • Como inicializar um mapa 3D com uma chave de API e uma visualização padrão.
  • Como adicionar marcadores, modelos 3D, linhas e polígonos ao mapa.
  • Como controlar a câmera para animar o movimento para outro local.
  • Como processar eventos de clique em marcadores de lugar.

A seguir

  • Confira o guia para desenvolvedores e saiba mais sobre o que você pode fazer com o SDK do Maps 3D para iOS.
  • Ajude a criar conteúdo útil respondendo à seguinte pesquisa:

Quais outros codelabs você quer ver?

Visualização de dados em mapas Como personalizar o estilo dos meus mapas Como criar interações 3D em mapas