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 Maps SDK cho iOS với SwiftUI.
Điều kiện tiên quyết
- Kiến thức cơ bản về Swift
- Hiểu biết cơ bản về SwiftUI
Bạn sẽ thực hiện
- Bật và sử dụng Maps SDK cho iOS để thêm Google Maps vào một ứng dụng iOS bằng SwiftUI.
- Thêm điểm đánh dấu vào bản đồ.
- Truyền trạng thái giữa một đối tượng SwiftUI và
GMSMapView
.
Bạn cần có
- Xcode 11.0 trở lên
- Tài khoản Google có bật tính năng thanh toán
- Maps SDK cho iOS
- Carthage
2. Bắt đầu thiết lập
Đối với bước bật sau đây, hãy bật Maps SDK cho iOS.
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à dự án có 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à dự án.
- Trong Cloud Console, hãy nhấp vào trình đơn thả xuống dự án rồi chọn dự án mà bạn muốn sử dụng cho lớp học lập trình này.
- Bật các API và SDK của Google Maps Platform cần thiết 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.
- 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 Nền tảng Google Maps đều cần có khoá API.
3. Tải mã bắt đầu
Để giúp bạn bắt đầu nhanh nhất có thể, sau đây là một số mã khởi đầu giúp bạn theo dõi lớp học lập trình này. Bạn có thể chuyển ngay đến giải pháp, 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 tiếp tục đọc.
- 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 đây để tải mã nguồn xuống.
- Sau khi nhận được mã, trong một dòng lệnh, hãy
cd
vào thư mụcstarter/GoogleMapsSwiftUI
. - Chạy
carthage update --platform iOS
để tải Maps SDK cho iOS xuống - Cuối cùng, hãy mở tệp
GoogleMapsSwiftUI.xcodeproj
trong Xcode
4. Tổng quan về mã
Trong dự án khởi đầ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:
AppDelegate
–UIApplicationDelegate
của ứng dụng. Đây là nơi SDK Bản đồ dành cho iOS sẽ được khởi chạy.City
– một cấu trúc đại diện cho một thành phố (chứa tên và toạ độ của thành phố).MapViewController
– một UIKit có phạm vi hạn chếUIViewController
chứa Google Maps (GMSMapView)SceneDelegate
–UIWindowSceneDelegate
của ứng dụng màContentView
được tạo thực thể.
Ngoài ra, các lớp sau đây có một phần nội dung triển khai và bạn sẽ hoàn tất các lớp này vào cuối Lớp học lập trình này:
ContentView
– khung hiển thị SwiftUI cấp cao nhất chứa ứng dụng của bạn.MapViewControllerBridge
– một lớp kết nối khung hiển thị UIKit với khung hiển thị SwiftUI. Cụ thể, đây là lớp sẽ giúpMapViewController
có thể truy cập trong SwiftUI.
5. SwiftUI so với UIKit
SwiftUI được ra mắt trong iOS 13 dưới dạng một khung giao diện người dùng thay thế cho UIKit để phát triển các ứng dụng iOS. So với UIKit, SwiftUI có một số ưu điểm. Ví dụ:
- Khung hiển thị sẽ tự động cập nhật khi trạng thái thay đổi. Khi dùng các đối tượng có tên là State, mọi thay đổi đối với giá trị cơ bản mà đối tượng này 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. Tính năng xem trước trực tiếp giảm thiểu nhu cầu tạo và triển khai mã cho trình mô phỏng để xem các thay đổi về hình ảnh vì bạn có thể dễ dàng xem trước khung hiển thị SwiftUI trên Xcode.
- Nguồn đáng tin cậy nằm trong Swift. Tất cả các thành phần hiển thị trong SwiftUI đều được khai báo bằng Swift nên bạn không cần dùng Interface Builder 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ể tăng dần việc sử dụng SwiftUI với các khung hiển thị hiện có. Ngoài ra, những thư viện chưa hỗ trợ SwiftUI (chẳng hạn như Maps SDK dành cho iOS) vẫn có thể được dùng trong SwiftUI.
Tuy nhiên, cũng có một số nhược điểm:
- SwiftUI chỉ có trên iOS 13 trở lên.
- Bạn không thể kiểm tra hệ thống phân cấp khung hiển thị trong bản xem trước Xcode.
Trạng thái và luồng dữ liệu SwiftUI
SwiftUI cung cấp một cách thức mới để tạo giao diện người dùng bằng cách sử dụng phương pháp khai báo – bạn cho SwiftUI biết hình thức bạn muốn cho khung hiển thị cùng với tất cả các trạng thái khác nhau của khung hiển thị đó, và hệ thống sẽ làm phần còn lại. SwiftUI xử lý việc cập nhật khung hiển thị 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ù thông tin cụ thể về 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 State and Data Flow (Trạng thái và luồng dữ liệu) của Apple.
Kết nối UIKit và SwiftUI bằng UIViewRepresentable hoặc UIViewControllerRepresentable
Vì Maps SDK cho iOS được xây dựng trên UIKit và không cung cấp khung hiển thị tương thích với SwiftUI, nên việc sử dụng SDK này trong SwiftUI yêu cầu tuân thủ UIViewRepresentable
hoặc UIViewControllerRepresentable
. Các giao thức này cho phép SwiftUI lần lượt đưa các UIView
và UIViewController
được tạo bằng UIKit vào. Mặc dù bạn có thể sử dụng một trong hai giao thức để thêm Google Maps vào khung hiển thị SwiftUI, nhưng ở bước tiếp theo, chúng ta sẽ xem xét việc sử dụng UIViewControllerRepresentable
để thêm UIViewController
có chứa bản đồ.
6. Thêm bản đồ
Trong phần này, bạn sẽ thêm Google Maps vào một khung hiển thị SwiftUI.
Thêm khoá API
Bạn cần cung cấp khoá API mà bạn đã tạo ở bước trước cho Maps SDK cho iOS để liên kết tài khoản của bạn với bản đồ sẽ xuất hiện trên ứng dụng.
Để cung cấp khoá API, hãy mở tệp AppDelegate.swift
rồi chuyển đến phương thức application(_, didFinishLaunchingWithOptions)
. SDK được khởi chạy bằng GMSServices.provideAPIKey()
với chuỗi "YOUR_API_KEY". Thay thế chuỗi đó bằng khoá API của bạn. Khi hoàn tất bước này, Maps SDK cho iOS sẽ được khởi chạy khi ứng dụng khởi động.
Thêm Google Map bằng MapViewControllerBridge
Giờ đây, khi khoá API của bạn được cung cấp cho SDK, bước tiếp theo là hiển thị bản đồ trên ứng dụng.
Bộ điều khiển khung hiển thị được cung cấp trong mã khởi đầu, MapViewController
chứa một GMSMapView
trong khung hiển thị của bộ điều khiển đó. Tuy nhiên, vì trình kiểm soát khung hiển thị này được tạo trong UIKit, nên bạn sẽ cần phải bắc cầu lớp này sang SwiftUI để có thể sử dụng bên trong ContentView
. Cách làm như sau:
- Mở tệp
MapViewControllerBridge
trong Xcode.
Lớp này tuân thủ UIViewControllerRepresentable, đây là giao thức cần thiết để bao bọc một UIViewController
UIKit để có thể dùng làm khung hiển thị SwiftUI. Nói cách khác, việc tuân thủ giao thức này sẽ giúp bạn dễ dàng chuyển đổi một khung hiển thị UIKit sang một khung hiển thị SwiftUI. Để tuân thủ giao thức này, bạn cần triển khai 2 phương thức:
makeUIViewController(context)
– phương thức này được SwiftUI gọi để tạoUIViewController
cơ bản. Đây là nơi bạn sẽ khởi tạoUIViewController
và truyền trạng thái ban đầu cho thành phần này.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 mọi sửa đổi đối vớiUIViewController
cơ bản để phản ứng với thay đổi về trạng thái.
- Tạo một
MapViewController
Bên trong hàm makeUIViewController(context)
, hãy khởi tạo một MapViewController
mới và trả về hàm này dưới dạng kết quả. Sau khi thực hiện, MapViewControllerBridge
của bạn sẽ có dạ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
Giờ đây, MapViewControllerBridge
đang tạo một thực thể 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 đồ.
- Mở tệp
ContentView
trong Xcode.
ContentView
được khởi tạo trong SceneDelegate
và chứa khung hiển thị ứng dụng cấp cao nhất. Bản đồ sẽ được thêm từ bên trong tệp này.
- Tạo một
MapViewControllerBridge
trong thuộc tínhbody
.
Trong thuộc tính body
của tệp này, một ZStack
đã được cung cấp và triển khai cho bạn. ZStack
chứa một danh sách các thành phố có thể tương tác và kéo được mà bạn sẽ sử dụng ở bước sau. Hiện tại, trong ZStack
, hãy tạo một MapViewControllerBridge
làm khung hiển thị con đầu tiên của ZStack
để bản đồ sẽ xuất hiện trong ứng dụng phía sau khung hiển thị danh sách thành phố. Sau khi thực hiện, nội dung của thuộc tính body
trong ContentView
sẽ có dạng như sau:
ContentView
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()
} // ...
}
}
}
- Bây giờ, hãy chạy ứng dụng. Bạn sẽ thấy bản đồ tải trên màn hình thiết bị cùng với danh sách các 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 đồ
Ở bước trước, bạn đã thêm một bản đồ cùng với một 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 đó.
Điểm đánh dấu dưới dạng trạng thái
ContentView
khai báo một thuộc tính có tên là markers
, đây là danh sách GMSMarker
đại diện cho từng 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 phải do SwiftUI quản lý. Vì vậy, nếu phát hiện thấy bất kỳ thay đổi nào đối với thuộc tính này, chẳng hạn như thêm hoặc xoá một điểm đánh dấu, thì các khung hiển thị sử dụng trạng thái này sẽ được cập nhật.
ContentView
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 truyền thuộc tính đó vào lớp CitiesList
.
CitiesList
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)
}
}
}
}
Truyền trạng thái đến MapViewControllerBridge bằng @Binding
Ngoài danh sách các thành phố hiển thị dữ liệu từ thuộc tính markers
, hãy truyền thuộc tính này đến cấu trúc MapViewControllerBridge
để có thể dùng thuộc tính này hiển thị các điểm đánh dấu đó trên bản đồ. Cách thực hiện như sau:
- Khai báo một thuộc tính
markers
mới trongMapViewControllerBridge
được chú thích bằng@Binding
MapViewControllerBridge
struct MapViewControllerBridge: : UIViewControllerRepresentable {
@Binding var markers: [GMSMarker]
// ...
}
- Trong
MapViewControllerBridge
, hãy cập nhật phương thứcupdateUIViewController(_, context)
để sử dụng thuộc tínhmarkers
Như đã đề cập ở 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 thức 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 việc 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 }
}
}
- Truyền thuộc tính
markers
từContentView
sangMapViewControllerBridge
Vì bạn đã thêm một thuộc tính mới trong MapViewControllerBridge
, nên giờ đây, bạn phải truyền giá trị cho thuộc tính này trong 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 ứng dụng không biên dịch được. Để khắc phục vấn đề này, hãy cập nhật ContentView
nơi MapViewControllerBridge
được tạo và truyền 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 ý rằng tiền tố $
được dùng để truyền markers
vào MapViewControllerBridge
vì nó mong đợi một thuộc tính được liên kết. $
là tiền tố dành riêng để sử dụng với trình bao bọc thuộc tính Swift. Khi áp dụng cho một Trạng thái, nó sẽ trả về một Binding.
- Hãy chạy ứng dụng để xem các điểm đánh dấu xuất hiện trên bản đồ.
8. Tạo hiệu ứng chuyển động đến một thành phố đã chọn
Ở bước trước, bạn đã thêm các điểm đánh dấu vào bản đồ bằng cách truyền Trạng thái từ một khung hiển thị SwiftUI sang một khung hiển thị khác. Ở bước này, bạn sẽ tạo hiệu ứng chuyển động đến một thành phố hoặc điểm đánh dấu sau khi thành phố hoặc điểm đánh dấu đó được nhấn vào trong 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 đối với một Trạng thái bằng cách sửa đổi vị trí camera của bản đồ khi thay đổi xảy ra. Để tìm hiểu thêm về khái niệm camera của bản đồ, hãy xem phần Camera và khung hiển thị.
Tạo hiệu ứng ảnh động cho bản đồ đến thành phố đã chọn
Cách tạo hiệu ứng chuyển động cho bản đồ đến một thành phố đã chọn:
- Xác định một Binding mới trong
MapViewControllerBridge
ContentView
có một thuộc tính State (Trạng thái) tên là selectedMarker
. Thuộc tính này được khởi tạo thành giá trị rỗng 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 do khung hiển thị CitiesList
buttonAction
trong ContentView
xử lý.
ContentView
CitiesList(markers: $markers) { (marker) in
guard self.selectedMarker != marker else { return }
self.selectedMarker = marker
// ...
}
Bất cứ khi nào selectedMarker
thay đổi, MapViewControllerBridge
phải nhận biết được sự thay đổi trạng thái này để có thể tạo hiệu ứng động cho bản đồ đến điểm đánh dấu đã chọn. Vì vậy, hãy xác định một Binding mới trong MapViewControllerBridge
thuộc loại GMSMarker
và đặt tên cho thuộc tính này là selectedMarker
.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
@Binding var selectedMarker: GMSMarker?
}
- Cập nhật
MapViewControllerBridge
để tạo hiệu ứng cho bản đồ bất cứ khi nàoselectedMarker
thay đổi
Sau khi khai báo một Binding mới, bạn cần cập nhật hàm updateUIViewController_, context)
của MapViewControllerBridge
để bản đồ chuyển động đến điểm đánh dấu đã chọn. Hãy tiếp tục và thực hiện việc này bằng cách sao chép mã sau:
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 ảnh động trên bản đồ bằng hàm animate(with)
của GMSMapView
.
- Truyền
selectedMarker
củaContentView
đếnMapViewControllerBridge
Sau khi MapViewControllerBridge
khai báo Binding mới, hãy tiếp tục cập nhật ContentView
để truyền vào selectedMarker
nơi MapViewControllerBridge
được khởi tạo.
ContentView
struct ContentView: View {
// ...
var body: some View {
// ...
GeometryReader { geometry in
ZStack(alignment: .top) {
// Map
MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker)
// ...
}
}
}
}
Khi bạn hoàn tất bước này, bản đồ sẽ chuyển động mỗi khi một thành phố mới được chọn trong danh sách.
Tạo ảnh động cho khung hiển thị SwiftUI để làm nổi bật thành phố
SwiftUI đơn giản hoá quy trình tạo ảnh động cho các khung hiển thị, vì nó sẽ xử lý việc thực hiện ảnh động cho các quá trình chuyển đổi State. Để minh hoạ điều này, bạn sẽ thêm nhiều ảnh động hơn bằng cách lấy tiêu điểm của khung hiển thị vào thành phố đã chọn sau khi ảnh động trên bản đồ hoàn tất. Để thực hiện việc này, hãy hoàn tất các bước sau:
- Thêm
onAnimationEnded
vàoMapViewControllerBridge
Vì ảnh động SwiftUI sẽ được thực hiện sau chuỗi ảnh động trên bản đồ mà bạn đã thêm trước đó, hãy khai báo một bao đóng mới có tên là onAnimationEnded
trong MapViewControllerBridge
và gọi bao đóng này sau khi trễ 0,5 giây sau ảnh động cuối cùng trên 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()
})
})
}
}
}
}
}
- Triển khai
onAnimationEnded
trongMapViewControllerBridge
Triển khai phương thức đóng onAnimationEnded
trong đó MapViewControllerBridge
được tạo thực thể trong ContentView
. Sao chép và dán mã sau đây để thêm một Trạng thái mới có tên là zoomInCenter
, đồng thời sửa đổi khung hiển thị bằng cách sử dụng clipShape
và thay đổi đường kính của hình dạng bị cắt tuỳ thuộc vào giá trị của zoomInCenter
ContentView
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))
}
}
}
}
- Hãy chạy ứng dụng để xem ảnh động!
9. Gửi một sự kiện đến SwiftUI
Trong bước này, bạn sẽ theo dõi 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 uỷ quyền cho khung hiển thị bản đồ và lắng nghe các sự kiện di chuyển camera để khi một thành phố được lấy làm tiêu điểm và camera bản đồ di chuyển do một cử chỉ, khung hiển thị bản đồ sẽ huỷ lấy tiêu điểm để bạn có thể xem thêm nội dung trên bản đồ.
Sử dụng các đối tượng điều phối SwiftUI
GMSMapView
phát ra các sự kiện như thay đổi vị trí camera hoặc khi người dùng nhấn vào một điểm đánh dấu. Cơ chế để theo dõi các sự kiện này là thông qua giao thức GMSMapViewDelegate. SwiftUI giới thiệu khái niệm về Trình điều phối, được dùng riêng để đóng vai trò là đại biểu cho các trình điều khiển chế độ xem UIKit. Vì vậy, trong thế giới SwiftUI, một Trình điều phối sẽ chịu trách nhiệm tuân thủ giao thức GMSMapViewDelegate
. Để làm việc này, vui lòng hoàn thành các bước sau:
- Tạo một đối tượng Điều phối có tên là
MapViewCoordinator
trongMapViewControllerBridge
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
dưới dạng một thuộc tính.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
var mapViewControllerBridge: MapViewControllerBridge
init(_ mapViewControllerBridge: MapViewControllerBridge) {
self.mapViewControllerBridge = mapViewControllerBridge
}
}
}
- Triển khai
makeCoordinator()
trongMapViewControllerBridge
Tiếp theo, hãy triển khai phương thức makeCoordinator()
trong MapViewControllerBridge
và trả về một phiên bản của MapViewCoodinator
mà bạn đã tạo ở bước trước.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
func makeCoordinator() -> MapViewCoordinator {
return MapViewCoordinator(self)
}
}
- Đặt
MapViewCoordinator
làm uỷ quyền của chế độ xem bản đồ
Sau khi tạo bộ điều phối tuỳ chỉnh, bước tiếp theo là đặt bộ điều phối làm uỷ quyền cho chế độ xem bản đồ của trình điều khiển khung hiển thị. Để thực hiện việc này, hãy cập nhật quá trình khởi tạo trình điều khiển khung hiển thị trong makeUIViewController(context)
. Bạn có thể truy cập vào trình điều phối đã tạo ở bước trước thông qua đối tượng Context.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
func makeUIViewController(context: Context) -> MapViewController {
let uiViewController = MapViewController()
uiViewController.map.delegate = context.coordinator
return uiViewController
}
}
- Thêm một lệnh đóng vào
MapViewControllerBridge
để sự kiện di chuyển camera có thể được truyền lên
Vì mục tiêu là cập nhật khung hiển thị khi camera di chuyển, hãy khai báo một thuộc tính đóng mới chấp nhận giá trị boolean trong MapViewControllerBridge
có tên là mapViewWillMove
và gọi đóng này trong phương thức uỷ quyền mapView(_, willMove)
trong MapViewCoordinator
. Truyền giá trị của gesture
đến khối đóng để khung hiển thị SwiftUI chỉ có thể phản ứng với các sự kiện di chuyển camera 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)
}
}
}
- Cập nhật ContentView để truyền giá trị cho
mapWillMove
Với thông báo đóng cửa mới vào ngày MapViewControllerBridge
, hãy cập nhật ContentView
để truyền vào một giá trị cho thông báo đóng cửa mới này. Trong khối đóng đó, hãy chuyển đổi Trạng thái zoomInCenter
thành false
nếu sự kiện di chuyển có liên quan đến một cử chỉ. Thao tác này sẽ cho thấy bản đồ ở chế độ xem đầy đủ một lần nữa khi bản đồ được di chuyển bằng một cử chỉ.
ContentView
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
})
// ...
}
}
}
}
- Hãy chạy ứng dụng để xem các thay đổi mới!
10. Xin chúc mừng
Chúc mừng bạn đã đi được đến đây! Bạn đã học được rất nhiều kiến thức và hy vọng rằng những bài học bạn đã học được sẽ giúp bạn xây dựng ứng dụng SwiftUI của riêng mình bằng Maps SDK cho iOS.
Kiến thức 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 State và Binding
- Cách gửi một sự kiện từ khung hiển thị bản đồ đến SwiftUI bằng Trình điều phối
Tiếp theo là gì?
- Maps SDK cho iOS
- tài liệu chính thức cho Maps SDK dành cho iOS
- Places SDK for iOS – tìm các doanh nghiệp địa phương và địa điểm yêu thích xung quanh bạn
- maps-sdk-for-ios-samples
- mã mẫu trên GitHub minh hoạ tất cả các tính năng trong Maps SDK 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 ra nội dung hữu ích nhất cho bạn bằng cách trả lời khảo sát sau:
Bạn muốn xem những lớp học lập trình nào khác?
Bạn không tìm thấy lớp học lập trình mà mình quan tâm nhất? Yêu cầu cấp lại bằng cách báo lỗi mới tại đây.