Thêm bản đồ vào ứng dụng iOS của bạn bằng SwiftUI (Swift)

1. Trước khi bắt đầu

Lớp học lập trình này hướng dẫn bạn cách sử dụng SDK Maps dành cho iOS bằng SwiftUI.

chụp ảnh màn hình-iphone-12-đen@2x.png

Điều kiện tiên quyết

  • Kiến thức cơ bản về Swift
  • Quen thuộc cơ bản với SwiftUI

Bạn sẽ thực hiện

  • Bật và sử dụng SDK Maps dành cho iOS để thêm Google Maps vào ứng dụng iOS bằng SwiftUI.
  • Thêm điểm đánh dấu vào bản đồ.
  • Chuyển trạng thái từ chế độ xem SwiftUI sang đối tượng GMSMapView và ngược lại.

Bạn cần có

2. Bắt đầu thiết lập

Bật bước Maps SDK cho iOS cho bước bật sau đây.

Thiết lập Nền tảng Google Maps

Nếu bạn chưa có tài khoản Google Cloud Platform và một dự án đã bật tính năng thanh toán, vui lòng xem hướng dẫn Bắt đầu sử dụng Google Maps Platform để tạo tài khoản thanh toán và một dự án.

  1. Trong Cloud Console, hãy nhấp vào trình đơn thả xuống dự án và chọn dự án mà bạn muốn sử dụng cho lớp học lập trình này.

  1. Bật API và SDK của Nền tảng Google Maps bắt buộc cho lớp học lập trình này trong Google Cloud Marketplace. Để làm như vậy, hãy làm theo các bước trong video này hoặc tài liệu này.
  2. Tạo khoá API trong trang Thông tin xác thực của Cloud Console. Bạn có thể làm theo các bước trong video này hoặc tài liệu này. Tất cả các yêu cầu gửi đến Google Maps Platform đều yêu cầu khóa API.

3. Tải mã bắt đầu

Để bắt đầu nhanh nhất có thể, hãy tham khảo khóa học lập trình này để tham khảo một số mã dành cho người mới bắt đầu. Bạn có thể chuyển sang giải pháp này, nhưng nếu muốn làm theo tất cả các bước để tự xây dựng giải pháp, hãy đọc tiếp.

  1. Sao chép kho lưu trữ nếu bạn đã cài đặt git.
git clone https://github.com/googlecodelabs/maps-ios-swiftui.git

Ngoài ra, bạn có thể nhấp vào nút sau để tải mã nguồn xuống.

  1. Khi nhận được mã, ở thiết bị đầu cuối cd vào phòng thử nghiệm starter/GoogleMapsSwiftUI.
  2. Chạy carthage update --platform iOS để tải SDK Maps xuống cho iOS
  3. Cuối cùng, hãy mở tệp GoogleMapsSwiftUI.xcodeproj trong Xcode

4. Tổng quan về mã

Trong dự án dành cho người mới bắt đầu mà bạn đã tải xuống, các lớp sau đây đã được cung cấp và triển khai cho bạn:

  • AppDelegateUIApplicationDelegate của ứng dụng. Đây là nơi SDK Maps dành cho iOS sẽ được khởi tạo.
  • City - một cấu trúc đại diện cho một thành phố (chứa tên và tọa độ của thành phố).
  • MapViewController – một UIKit UIViewController đơn giản chứa Google Maps (GMSMapView)
  • SceneDelegate – ứng dụng UIWindowSceneDelegate mà từ đó ContentView được tạo bản sao.

Ngoài ra, các lớp sau đây có một phần triển khai và sẽ được bạn hoàn thành vào cuối khóa học này:

  • ContentView – chế độ xem SwiftUI cấp cao nhất chứa ứng dụng của bạn.
  • MapViewControllerBridge – một lớp kết nối chế độ xem UIKit với chế độ xem SwiftUI. Cụ thể, đây là lớp sẽ giúp bạn có thể truy cập vào MapViewController trong SwiftUI.

5. Sử dụng SwiftUI và UIKit

SwiftUI đã được giới thiệu trong iOS 13 như một khung giao diện người dùng thay thế trên UIKit để phát triển các ứng dụng iOS. So với phiên bản UIKit trước đây, SwiftUI mang lại một số lợi thế. Chẳng hạn như:

  • Chế độ xem tự động cập nhật khi trạng thái thay đổi. Khi sử dụng các đối tượng có tên là Trạng thái, mọi thay đổi đối với giá trị cơ bản mà đối tượng đó chứa sẽ khiến giao diện người dùng tự động cập nhật.
  • Tính năng xem trước trực tiếp giúp phát triển nhanh hơn. Bản xem trước trực tiếp giảm thiểu nhu cầu xây dựng và triển khai mã cho trình mô phỏng để xem các thay đổi trực quan dưới dạng bản xem trước của chế độ xem SwiftUI.
  • Nguồn của sự kiện là trong Swift. Tất cả chế độ xem trong SwiftUI đều được khai báo trong Swift. Do đó, bạn không cần phải sử dụng Trình tạo giao diện nữa.
  • Tương tác với UIKit. Khả năng tương tác với UIKit đảm bảo rằng các ứng dụng hiện có có thể sử dụng SwiftUI tăng dần với chế độ xem hiện có. Ngoài ra, bạn vẫn có thể sử dụng các thư viện chưa hỗ trợ SwiftUI, chẳng hạn như SDK Maps dành cho iOS, trong SwiftUI.

Cũng có một số hạn chế:

  • SwiftUI chỉ có trên iOS 13 trở lên.
  • Không thể kiểm tra hệ phân cấp chế độ xem trong bản xem trước Xcode.

Trạng thái và luồng dữ liệu của SwiftUI

SwiftUI cung cấp một cách mới để tạo giao diện người dùng bằng phương pháp khai báo – bạn cho SwiftUI biết cách bạn muốn chế độ xem của mình nhìn vào tất cả các trạng thái khác nhau cho nó và hệ thống sẽ thực hiện các phần còn lại. SwiftUI xử lý việc cập nhật chế độ xem bất cứ khi nào trạng thái cơ bản thay đổi do một sự kiện hoặc hành động của người dùng. Thiết kế này thường được gọi là luồng dữ liệu một chiều. Mặc dù chi tiết cụ thể của thiết kế này nằm ngoài phạm vi của lớp học lập trình này, nhưng bạn nên đọc về cách hoạt động của thiết kế này trong tài liệu về Trạng thái và luồng dữ liệu của Apple.

Kết nối UIKit và SwiftUI bằng cách sử dụng UIViewRepresentationable hoặc UIViewControllerRepresentable

Vì SDK Maps dành cho iOS được xây dựng dựa trên UIKit, và chưa cung cấp chế độ xem tương thích với SwiftUI, nên việc sử dụng SDK trong SwiftUI yêu cầu phải tuân thủ UIViewRepresentable hoặc UIViewControllerRepresentable. Các giao thức này cho phép SwiftUI bao gồm UIViewUIViewController do UIKit tạo tương ứng. Mặc dù bạn có thể sử dụng một trong hai giao thức để thêm Google Maps vào chế độ xem SwiftUI, nhưng trong bước tiếp theo, chúng ta sẽ xem xét bằng cách sử dụng UIViewControllerRepresentable để bao gồm UIViewController chứa bản đồ.

6. Thêm bản đồ

Trong phần này, bạn sẽ thêm Google Maps vào chế độ xem SwiftUI.

add-a-map- Screenshot@2x.png

Thêm khóa API của bạn

Khóa API mà bạn đã tạo ở bước trước đó cần được cung cấp cho SDK Maps dành cho iOS để liên kết tài khoản của bạn với bản đồ sẽ được hiển thị trên ứng dụng.

Để cung cấp khóa API của bạn, hãy mở tệp AppDelegate.swift và chuyển đến phương thức application(_, didFinishLaunchingWithOptions). Hiện tại, SDK được khởi tạo qua GMSServices.provideAPIKey() với chuỗi " THIS_API_KEY". Thay thế chuỗi đó bằng khóa API của bạn. Khi hoàn tất bước này, SDK Maps sẽ khởi chạy cho iOS khi ứng dụng khởi chạy.

Thêm Bản đồ Google bằng MapViewControllerBridge

Giờ đây, bạn đã cung cấp khóa API của mình cho SDK, bước tiếp theo là hiển thị bản đồ trên ứng dụng.

Bộ điều khiển chế độ xem được cung cấp trong mã dành cho người mới bắt đầu, MapViewController hiện chứa một GMSMapView trong chế độ xem. Tuy nhiên, vì bộ điều khiển chế độ xem này được tạo trong UIKit, bạn sẽ cần kết nối lớp này với SwiftUI để có thể sử dụng lớp này trong ContentView. Cách thực hiện:

  1. Mở tệp MapViewControllerBridge trong Xcode.

Lớp này tuân thủ UIViewControllerRepresentationable, giao thức cần thiết để bao bọc một UIKit UIViewController để có thể dùng làm giao diện SwiftUI. Nói cách khác, việc tuân thủ giao thức này cho phép bạn cầu nối một chế độ xem UIKit vào chế độ xem SwiftUI. Việc tuân thủ giao thức này yêu cầu bạn phải triển khai hai phương thức:

  • makeUIViewController(context) – phương thức này được SwiftUI gọi để tạo UIViewController cơ bản. Đây là nơi bạn sẽ tạo UIViewController và chuyển trạng thái ban đầu của thiết bị.
  • updateUIViewController(_, context) – phương thức này được SwiftUI gọi bất cứ khi nào trạng thái thay đổi. Đây là nơi bạn sẽ thực hiện bất kỳ sửa đổi nào đối với UIViewController cơ bản để phản ứng với sự thay đổi trạng thái.
  1. Tạo một MapViewController

Bên trong hàm makeUIViewController(context), hãy tạo MapViewController mới và trả về kết quả đó. Sau khi làm như vậy, MapViewControllerBridge của bạn sẽ trông giống như sau:

MapViewControllerbridge

import GoogleMaps
import SwiftUI

struct MapViewControllerBridge: UIViewControllerRepresentable {

  func makeUIViewController(context: Context) -> MapViewController {
    return MapViewController()
  }

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
  }
}

Sử dụng MapViewControllerBridge trong ContentView

Bây giờ, MapViewControllerBridge đang tạo một phiên bản của MapViewController, bước tiếp theo là sử dụng cấu trúc này trong ContentView để hiển thị bản đồ.

  1. Mở tệp ContentView trong Xcode.

ContentView được tạo bản sao trong SceneDelegate và chứa chế độ xem ứng dụng cấp cao nhất. Bản đồ sẽ được thêm từ trong tệp này.

  1. Tạo MapViewControllerBridge trong thuộc tính body.

Trong thuộc tính body của tệp này, ZStack đã được cung cấp và triển khai cho bạn. ZStack hiện chứa danh sách các thành phố có thể kéo và tương tác mà bạn sẽ sử dụng trong bước tiếp theo. Hiện tại, trong ZStack, hãy tạo MapViewControllerBridge làm chế độ xem con đầu tiên của ZStack để bản đồ sẽ hiển thị trong ứng dụng phía sau danh sách thành phố. Khi làm như vậy, nội dung của thuộc tính body trong ContentView sẽ có dạng như sau:

Chế độ xem nội dung

var body: some View {

  let scrollViewHeight: CGFloat = 80

  GeometryReader { geometry in
    ZStack(alignment: .top) {
      // Map
      MapViewControllerBridge()

      // Cities List
      CitiesList(markers: $markers) { (marker) in
        guard self.selectedMarker != marker else { return }
        self.selectedMarker = marker
        self.zoomInCenter = false
        self.expandList = false
      }  handleAction: {
        self.expandList.toggle()
      } // ...
    }
  }
}
  1. Bây giờ, hãy tiếp tục và chạy ứng dụng. Bây giờ, bạn sẽ thấy tải bản đồ trên màn hình của thiết bị cùng với danh sách thành phố có thể kéo ở cuối màn hình.

7. Thêm điểm đánh dấu vào bản đồ

Trong bước trước đó, bạn đã thêm bản đồ cùng với danh sách có thể tương tác hiển thị danh sách các thành phố. Trong phần này, bạn sẽ thêm điểm đánh dấu cho từng thành phố trong danh sách đó.

map-with-marks@2x.png

Điểm đánh dấu là Trạng thái

ContentView hiện khai báo một thuộc tính có tên là markers, đây là danh sách GMSMarker đại diện cho mỗi thành phố được khai báo trong thuộc tính tĩnh cities. Xin lưu ý rằng thuộc tính này được chú thích bằng trình bao bọc thuộc tính SwiftUI State để cho biết rằng thuộc tính này sẽ do SwiftUI quản lý. Do đó, nếu phát hiện thấy bất kỳ thay đổi nào với thuộc tính này, chẳng hạn như thêm hoặc xóa một điểm đánh dấu, thì các chế độ xem sử dụng trạng thái này sẽ được cập nhật.

Chế độ xem nội dung

  static let cities = [
    City(name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: 37.7576, longitude: -122.4194)),
    City(name: "Seattle", coordinate: CLLocationCoordinate2D(latitude: 47.6131742, longitude: -122.4824903)),
    City(name: "Singapore", coordinate: CLLocationCoordinate2D(latitude: 1.3440852, longitude: 103.6836164)),
    City(name: "Sydney", coordinate: CLLocationCoordinate2D(latitude: -33.8473552, longitude: 150.6511076)),
    City(name: "Tokyo", coordinate: CLLocationCoordinate2D(latitude: 35.6684411, longitude: 139.6004407))
  ]

  /// State for markers displayed on the map for each city in `cities`
  @State var markers: [GMSMarker] = cities.map {
    let marker = GMSMarker(position: $0.coordinate)
    marker.title = $0.name
    return marker
  }

Xin lưu ý rằng ContentView sử dụng thuộc tính markers để hiển thị danh sách các thành phố bằng cách chuyển thuộc tính này đến lớp CitiesList.

Danh sách thành phố

struct CitiesList: View {

  @Binding var markers: [GMSMarker]

  var body: some View {
    GeometryReader { geometry in
      VStack(spacing: 0) {
        // ...
        // List of Cities
        List {
          ForEach(0..<self.markers.count) { id in
            let marker = self.markers[id]
            Button(action: {
              buttonAction(marker)
            }) {
              Text(marker.title ?? "")
            }
          }
        }.frame(maxWidth: .infinity)
      }
    }
  }
}

Chuyển Trạng thái đến MapViewControllerBridge qua Binding

Ngoài danh sách thành phố hiển thị dữ liệu từ tài sản markers, hãy chuyển tài sản này đến cấu trúc MapViewControllerBridge để có thể sử dụng tài sản đó để hiển thị những điểm đánh dấu đó trên bản đồ. Cách làm như sau:

  1. Khai báo một tài sản markers mới trong MapViewControllerBridge được chú thích bằng @Binding

MapViewControllerbridge

struct MapViewControllerBridge: : UIViewControllerRepresentable {
  @Binding var markers: [GMSMarker]
  // ...
}
  1. Trong MapViewControllerBridge, hãy cập nhật phương thức updateUIViewController(_, context) để sử dụng thuộc tính markers

Như đã đề cập trong bước trước, updateUIViewController(_, context) sẽ được SwiftUI gọi bất cứ khi nào trạng thái thay đổi. Trong phương pháp này, chúng ta muốn cập nhật bản đồ để hiển thị các điểm đánh dấu trong markers. Để làm điều này, bạn cần cập nhật thuộc tính map của mỗi điểm đánh dấu. Sau khi hoàn tất bước này, MapViewControllerBridge của bạn sẽ có dạng như sau:

import GoogleMaps
import SwiftUI

struct MapViewControllerBridge: UIViewControllerRepresentable {

  @Binding var markers: [GMSMarker]

  func makeUIViewController(context: Context) -> MapViewController {
    return MapViewController()
  }

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
    // Update the map for each marker
    markers.forEach { $0.map = uiViewController.map }
  }
}
  1. Chuyển thuộc tính markers từ ContentView sang MapViewControllerBridge

Vì bạn đã thêm một thuộc tính mới trong MapViewControllerBridge, nên bây giờ, yêu cầu phải chuyển giá trị cho thuộc tính này vào trình khởi tạo cho MapViewControllerBridge. Vì vậy, nếu cố gắng tạo ứng dụng, bạn sẽ nhận thấy rằng ứng dụng sẽ không biên dịch. Để khắc phục lỗi này, hãy cập nhật ContentView khi MapViewControllerBridge được tạo và chuyển vào thuộc tính markers như sau:

struct ContentView: View {
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        MapViewControllerBridge(markers: $markers)
        // ...
      }
    }
  }
}

Lưu ý: tiền tố $ được dùng để chuyển vào markers thành MapViewControllerBridge vì cần có thuộc tính liên kết. $ là một tiền tố đã được đặt trước để dùng với trình bao bọc thuộc tính Swift. Khi được áp dụng cho một Tiểu bang, hàm này sẽ trả về Liên kết.

  1. Hãy tiếp tục và chạy ứng dụng để xem các điểm đánh dấu được hiển thị trên bản đồ.

8. Hoạt hình cho một thành phố đã chọn

Ở bước trước, bạn đã thêm điểm đánh dấu vào bản đồ bằng cách chuyển Trạng thái từ chế độ xem SwiftUI này sang chế độ xem khác. Trong bước này, bạn sẽ tạo ảnh động cho thành phố/điểm đánh dấu sau khi nhấn vào danh sách có thể tương tác. Để thực hiện ảnh động, bạn sẽ phản ứng với các thay đổi thành một Trạng thái bằng cách sửa đổi vị trí máy ảnh của bản đồ khi thay đổi này xảy ra. Để tìm hiểu thêm về khái niệm máy ảnh của bản đồ, hãy xem Máy ảnh và chế độ xem.

hoạt-động@@x.png

Hoạt hình bản đồ đến thành phố đã chọn

Để tạo bản đồ cho một thành phố được chọn:

  1. Xác định liên kết mới trong MapViewControllerBridge

ContentView có một thuộc tính Tiểu bang là selectedMarker được khởi tạo thành giá trị nil và được cập nhật bất cứ khi nào một thành phố được chọn trong danh sách. Việc này được xử lý trong chế độ xem CitiesList buttonAction trong ContentView.

Chế độ xem nội dung

CitiesList(markers: $markers) { (marker) in
  guard self.selectedMarker != marker else { return }
  self.selectedMarker = marker
  // ...
}

Bất cứ khi nào selectedMarker thay đổi, MapViewControllerBridge cần biết về trạng thái này để có thể tạo bản đồ cho điểm đánh dấu được chọn. Do đó, hãy xác định một Liên kết mới trong MapViewControllerBridge thuộc loại GMSMarker và đặt tên cho thuộc tính selectedMarker.

MapViewControllerbridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  @Binding var selectedMarker: GMSMarker?
}
  1. Cập nhật MapViewControllerBridge để tạo hiệu ứng cho bản đồ bất cứ khi nào selectedMarker thay đổi

Sau khi liên kết mới được khai báo, bạn cần cập nhật hàm updateUIViewController_, context) của MapViewControllerBridge để bản đồ đó chuyển động tới điểm đánh dấu đã chọn. Hãy tiếp tục và thực hiện bằng cách sao chép mã bên dưới:

struct MapViewControllerBridge: UIViewControllerRepresentable {
  @Binding var selectedMarker: GMSMarker?

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
    markers.forEach { $0.map = uiViewController.map }
    selectedMarker?.map = uiViewController.map
    animateToSelectedMarker(viewController: uiViewController)
  }

  private func animateToSelectedMarker(viewController: MapViewController) {
    guard let selectedMarker = selectedMarker else {
      return
    }

    let map = viewController.map
    if map.selectedMarker != selectedMarker {
      map.selectedMarker = selectedMarker
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        map.animate(toZoom: kGMSMinZoomLevel)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
          map.animate(with: GMSCameraUpdate.setTarget(selectedMarker.position))
          DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
            map.animate(toZoom: 12)
          })
        }
      }
    }
  }
}

Hàm animateToSelectedMarker(viewController) sẽ thực hiện một chuỗi các ảnh động bằng bản đồ bằng hàm animate(with) của GMSMapView.

  1. Chuyển ContentView\39;s selectedMarker tới MapViewControllerBridge

Sau khi MapViewControllerBridge đã khai báo Binding mới, hãy tiếp tục và cập nhật ContentView để chuyển vào selectedMarker nơi MapViewControllerBridge được tạo bản sao.

Chế độ xem nội dung

struct ContentView: View {
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker)
        // ...
      }
    }
  }
}

Khi hoàn tất bước này, bản đồ sẽ được tạo một bản đồ bất cứ khi nào một thành phố mới được chọn trong danh sách.

Hoạt ảnh chế độ xem SwiftUI để nhấn mạnh thành phố

SwiftUI giúp hoạt ảnh chế độ xem rất dễ dàng vì nó sẽ xử lý các hiệu ứng động cho quá trình chuyển đổi Trạng thái. Để minh họa điều này, bạn sẽ thêm các ảnh động khác bằng cách tập trung chế độ xem vào thành phố đã chọn sau khi ảnh động bản đồ hoàn tất. Để làm việc này, hãy hoàn tất các bước sau:

  1. Thêm trạng thái đóng cửa onAnimationEnded vào MapViewControllerBridge

Vì ảnh động SwiftUI sẽ được thực hiện sau trình tự hoạt ảnh bản đồ mà bạn đã thêm trước đó, hãy khai báo trạng thái đóng mới có tên là onAnimationEnded trong MapViewControllerBridge và gọi lệnh đóng này sau độ trễ 0,5 giây sau ảnh động cuối cùng trong bản đồ trong phương thức animateToSelectedMarker(viewController).

MapViewControllerbridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
    var onAnimationEnded: () -> ()

    private func animateToSelectedMarker(viewController: MapViewController) {
    guard let selectedMarker = selectedMarker else {
      return
    }

    let map = viewController.map
    if map.selectedMarker != selectedMarker {
      map.selectedMarker = selectedMarker
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        map.animate(toZoom: kGMSMinZoomLevel)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
          map.animate(with: GMSCameraUpdate.setTarget(selectedMarker.position))
          DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
            map.animate(toZoom: 12)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
              // Invoke onAnimationEnded() once the animation sequence completes
              onAnimationEnded()
            })
          })
        }
      }
    }
  }
}
  1. Triển khai onAnimationEnded trong MapViewControllerBridge

Triển khai lệnh đóng onAnimationEnded khi MapViewControllerBridge được tạo trong ContentView. Sao chép và dán mã sau để thêm Trạng thái mới có tên là zoomInCenter và cũng sửa đổi chế độ xem bằng cách sử dụng clipShape và thay đổi đường kính của hình dạng bị cắt tùy thuộc vào giá trị của zoomInCenter

Chế độ xem nội dung

struct ContentView: View {
  @State var zoomInCenter: Bool = false
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        let diameter = zoomInCenter ? geometry.size.width : (geometry.size.height * 2)
        MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker, onAnimationEnded: {
          self.zoomInCenter = true
        })
        .clipShape(
           Circle()
             .size(
               width: diameter,
               height: diameter
             )
             .offset(
               CGPoint(
                 x: (geometry.size.width - diameter) / 2,
                 y: (geometry.size.height - diameter) / 2
               )
             )
        )
        .animation(.easeIn)
        .background(Color(red: 254.0/255.0, green: 1, blue: 220.0/255.0))
      }
    }
  }
}
  1. Hãy tiếp tục và chạy ứng dụng này để xem ảnh động!

9. Gửi sự kiện đến SwiftUI

Trong bước này, bạn sẽ nghe các sự kiện phát ra từ GMSMapView và gửi sự kiện đó đến SwiftUI. Cụ thể, bạn sẽ đặt một đại biểu cho chế độ xem bản đồ và lắng nghe các sự kiện di chuyển máy ảnh để khi một thành phố được tập trung và máy ảnh bản đồ di chuyển từ một cử chỉ, chế độ xem bản đồ sẽ không lấy nét để bạn có thể xem thêm bản đồ.

Sử dụng trình điều phối SwiftUI

GMSMapView phát ra các sự kiện như vị trí của máy ảnh thay đổi hoặc khi một điểm đánh dấu được nhấn vào. Cơ chế lắng nghe những sự kiện này là thông qua giao thức GMSMapViewDelegate. SwiftUI giới thiệu khái niệm Điều phối viên được sử dụng cụ thể để làm đại biểu cho các bộ điều khiển chế độ xem UIKit. Vì vậy, trong thế giới SwiftUI, Người điều phối phải chịu trách nhiệm tuân thủ giao thức GMSMapViewDelegate. Để làm việc này, hãy hoàn tất các bước sau:

  1. Tạo Điều phối viên có tên là MapViewCoordinator trong MapViewControllerBridge

Tạo một lớp lồng nhau bên trong lớp MapViewControllerBridge và gọi lớp đó là MapViewCoordinator. Lớp này phải tuân thủ GMSMapViewDelegate và phải khai báo MapViewControllerBridge là một thuộc tính.

MapViewControllerbridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  // ...
  final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
    var mapViewControllerBridge: MapViewControllerBridge

    init(_ mapViewControllerBridge: MapViewControllerBridge) {
      self.mapViewControllerBridge = mapViewControllerBridge
    }
  }
}
  1. Triển khai makeCoordinator() trong MapViewControllerBridge

Tiếp theo, hãy triển khai phương thức makeCoordinator() trong MapViewControllerBridge và trả về một bản sao của MapViewCoodinator mà bạn đã tạo ở bước trước.

MapViewControllerbridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  // ...
  func makeCoordinator() -> MapViewCoordinator {
    return MapViewCoordinator(self)
  }
}
  1. Đặt MapViewCoordinator làm đại biểu của chế độ xem bản đồ

Với cách tạo tọa độ tùy chỉnh, bước tiếp theo là đặt tọa độ làm đại biểu cho chế độ xem bản đồ của bộ điều khiển chế độ xem. Để thực hiện việc này, hãy cập nhật quá trình khởi tạo bộ điều khiển chế độ xem trong makeUIViewController(context). Bạn có thể truy cập tọa độ đã tạo từ bước trước đó từ đối tượng Ngữ cảnh.

MapViewControllerbridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  // ...
  func makeUIViewController(context: Context) -> MapViewController {
    let uiViewController = MapViewController()
    uiViewController.map.delegate = context.coordinator
    return uiViewController
  }
  1. Thêm trạng thái đóng vào MapViewControllerBridge để máy ảnh có thể di chuyển sự kiện lên

Vì mục tiêu là cập nhật chế độ xem bằng quá trình di chuyển máy ảnh, hãy khai báo một thuộc tính đóng mới chấp nhận boolean trong MapViewControllerBridge có tên là mapViewWillMove và gọi lệnh đóng này trong phương thức ủy quyền mapView(_, willMove) trong MapViewCoordinator. Chuyển giá trị của gesture đến trạng thái đóng để chế độ xem SwiftUI chỉ có thể phản ứng với các sự kiện di chuyển máy ảnh liên quan đến cử chỉ.

MapViewControllerbridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  var mapViewWillMove: (Bool) -> ()
  //...

  final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
    // ...
    func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
      self.mapViewControllerBridge.mapViewWillMove(gesture)
    }
  }
}
  1. Cập nhật ContentView để chuyển một giá trị cho mapWillMove

Với trạng thái đóng cửa mới được khai báo vào MapViewControllerBridge, hãy cập nhật ContentView để chuyển một giá trị cho trạng thái đóng cửa mới này. Trong trường hợp đóng cửa đó, hãy chuyển đổi Trạng thái zoomInCenter thành false nếu sự kiện di chuyển liên quan đến một cử chỉ. Thao tác này sẽ hiển thị lại bản đồ ở chế độ xem toàn bộ khi bản đồ được di chuyển bằng một cử chỉ.

Chế độ xem nội dung

struct ContentView: View {
  @State var zoomInCenter: Bool = false
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        let diameter = zoomInCenter ? geometry.size.width : (geometry.size.height * 2)
        MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker, onAnimationEnded: {
          self.zoomInCenter = true
        }, mapViewWillMove: { (isGesture) in
          guard isGesture else { return }
          self.zoomInCenter = false
        })
        // ...
      }
    }
  }
}
  1. Hãy tiếp tục và chạy ứng dụng để xem các thay đổi mới!

10. Xin chúc mừng

Chúc mừng bạn đã đạt được rất nhiều bước! Bạn đã đi sâu vào nhiều kiến thức cơ bản và hy vọng những bài học đã học cho phép bạn hiện xây dựng ứng dụng SwiftUI của riêng mình bằng cách sử dụng SDK Maps dành cho iOS.

Những điều bạn đã học được

  • Sự khác biệt giữa SwiftUI và UIKit
  • Cách kết nối giữa SwiftUI và UIKit bằng UIViewControllerRepresentable
  • Cách thay đổi chế độ xem bản đồ bằng Trạng tháiLiên kết
  • Cách gửi một sự kiện từ chế độ xem bản đồ đến SwiftUI bằng cách sử dụng Coordinator

Nội dung tiếp theo là gì?

  • SDK Maps dành cho iOS – tài liệu chính thức về SDK Maps dành cho iOS
  • SDK địa điểm cho iOS - tìm doanh nghiệp địa phương và điểm yêu thích xung quanh bạn
  • maps-sdk-for-ios-samples – mã mẫu trên GitHub minh họa tất cả các tính năng trong SDK Maps dành cho iOS.
  • SwiftUI – Tài liệu chính thức của Apple về SwiftUI
  • Hãy giúp chúng tôi tạo nội dung mà bạn thấy hữu ích nhất bằng cách trả lời câu hỏi dưới đây:

Bạn muốn xem những lớp học lập trình nào khác?

Hình ảnh dữ liệu trên bản đồ Tìm hiểu thêm về cách tùy chỉnh kiểu bản đồ của tôi Xây dựng hoạt động tương tác 3D trong bản đồ

Lớp học lập trình mà bạn muốn không được liệt kê ở trên? Yêu cầu phát hành vấn đề mới tại đây.