Contents
Chào mừng bạn đến với Fx Studio. Chủ đề bài viết lần này là: Giới thiệu về Map & MapKit (chính chủ) trên SwiftUI (chính chủ). Cũng tới một lúc, chúng ta được Apple hỗ trợ đầy đủ nhất về Map trên SwiftUI. Nên tới bây giờ, mình mới viết bài này nhóe. Hi vọng sẽ giúp ích được cho mọi người khi phát triển dự án SwiftUI.
Nếu mọi việc đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
Chúng ta sẽ tìm hiểu về Map & MapKit mới nhất với SwiftUI. Do đó, bạn cần sử dụng các OS version và tools mới nhất nhóe. Cơ bản là:
-
- Xcode 15.x
- iOS 17.x
- Swift 5.9
- …
Về mặt kiến thức, bạn cần thông thạo một ít SwiftUI. Nếu bạn chưa biết về nó thì có thể bắt đầu tìm hiểu SwiftUI thông các 1 loạt các series trên Fx Studio nhóe!
MapKit & Map
Nếu bạn đã từng là một người theo dõi sự phát triển của SwiftUI ngay từ những ngày đầu, thì việc sử dụng Map rất vất vả. Nó chủ yếu là cầu nối từ UIKit sang SwiftUI mà thôi. Và rồi cũng tới ngày, là ta có thể dụng Map trên SwiftUI. Nhưng là sử dụng một cách …
Khá đơn giản!
Xem qua ví dụ như sau nha:
import SwiftUI import MapKit struct ContentView: View { var body: some View { VStack { Map() } .padding() } }
Trong đó:
- Bạn cần
import
MapKit để sử dụng nhóe - Gọi thẻ
Map()
Bạn chờ một tí, màn hình Preview sẽ hiển thị ra như sau:
Ở đây, MapKit chính là thư viện của Apple để phục vụ việc hiển thị bản đồ và dữ liệu cho bản đồ. Hay người ta còn gọi là “Apple Map”. Với Map, bạn hiểu nó tương tự như Text, Button, Image … chỉ là một đối tượng giao diện để hiển thị lên giao diện người dùng mà thôi.
Nếu đơn giản không có bất kì tham số hay content gì cho Map, thì Apple sẽ dựa vào wifi/gps/4g … của bạn mà hiển thị Map đúng ở quốc gia hay nơi bạn đang đứng. Khá EZ!
Marker & Annotation
Công việc tiếp theo, ta sẽ thêm nội dung vào Map. Ta sẽ có tùy chỉnh 2 loại cơ bản là Marker & Annotation. Ta sẽ chuẩn bị 1 địa điểm cụ thể nhóe.
extension CLLocationCoordinate2D { static let dragonBridge = CLLocationCoordinate2D(latitude: 16.06101183799654, longitude: 108.22761720565889) }
Đây là vị trí của “Cầu Rồng, Đà Nẵng, Việt Nam”. Mình tạo một biến static
để tiện sử dụng cho các đoạn code ở dưới.
Marker
Marker dùng để đánh dấu một vị trí trên bản đồ. Mặc định, nó là một cái hình bong bóng và hiện thị tên của địa điểm trên bản đồ. Về code SwiftUI cho MapKit mới, bạn sẽ code như thế này:
Map { Marker("Dragon Bridge", coordinate: CLLocationCoordinate2D.dragonBridge) }
Trong đó:
- Marker là đối tượng sử dụng trong Map
- Bạn chỉ cần thêm các tham số cơ bản mà thôi
So ra, SwiftUI khá đơn giản so với UIKit hỉ! Kết quả ở Preview như sau:
Annotation
Cũng tương tự như Marker, nhưng chúng ta có thể tùy biến nó nhiều hơn. Cấu trúc cơ bản của một Annotation mới như sau:
coordinate
chứa thông tin tọa độ cơ bản 2Dcontent
phần hiển thị chính, bạn có thể nhét các View,Image ….label
haytitle
tùy thuộc bạn muốn hiển thị nội dung cho tên của nó như thế nào mà có lựa chọn phù hợp.
Ví dụ code nha!
Annotation("Cầu Rồng", coordinate: CLLocationCoordinate2D.dragonBridge) { Image(systemName: "flame.circle.fill") .padding (4) .foregroundStyle(.white) .background (Color.red) .cornerRadius (4) }
Kết quả nhóe!
Ngoài ra, Annotation có một tham số khá là thú vị. Đó là anchor
. Nó sẽ quyết định nội dung của Annotation sẽ đặt theo hướng nào so với tọa độ điểm (coordinate
). Xem ví dụ code tiếp nhóe!
Map { Marker("Dragon Bridge", coordinate: CLLocationCoordinate2D.dragonBridge) Annotation("Cầu Rồng", coordinate: CLLocationCoordinate2D.dragonBridge, anchor: .top) { Image(systemName: "flame.circle.fill") .padding (4) .foregroundStyle(.white) .background (Color.red) .cornerRadius (4) } }
Trong đó, mình sử dụng luôn cả 2 Marker & Annotation cùng một vị trí. Và xét anchor = .top
, bạn xem qua hình kết quả là có thể hiểu được liền.
EZ!
Map Styles
Với SwiftUI & MapKit, bạn có thể thay đổi một vài kiểu style cho bản đồ. Chỉ cần thêm modifier .mapStyle()
cho Map là được. Chúng ta sẽ có cơ bản một vài kiểu như sau:
- Bản đồ truyền thống
- Có giao thông
- Bản đồ thực tế
- Kết hợp các kiểu với nhau
- …
Ví dụ code cơ bản:
.mapStyle(.standard) // or .mapStyle(.standard(elevation: .realistic)) // or .mapStyle(.imagery(elevation: .realistic)) // or .mapStyle(.hybrid(elevation: .realistic))
Mình liệt kê vài hình cơ bản thôi nha.
Region
Bạn để ý một điều là Map trên SwiftUI, việc hiển thị sẽ hầu như tự động điều chỉnh dựa theo một số tiêu chí mặc định. Như wifi, marker, annotation … Như vậy, muốn để hiển thị Map theo ý mình, thì bạn cần sử dụng tới một cách mới.
Đó là
initialPosition
, tức là khởi tạo Map với một vị trí nào đó.
Đây là một điểm nâng cấp của MapKit trên SwiftUI, nhằm phù hợp với nền tảng này. Chúng ta sẽ tìm hiểu cách hiển thị theo từng vùng (region
) như thế nào nhóe.
Setup View
Trước tiên, ta sẽ thay đổi một chút giao diện lại như sau:
var body: some View { VStack { VStack { //MAP Map() //REGION BUTTONS HStack { //region #1 Button { // code here } label: { Label("1", systemImage: "1.square.fill") } .buttonStyle(.borderedProminent) //region #2 Button { // code here } label: { Label("2", systemImage: "2.square.fill") } .buttonStyle(.borderedProminent) //region #3 Button { // code here } label: { Label("3", systemImage: "3.square.fill") } .buttonStyle(.borderedProminent) } .labelStyle(.iconOnly) } } .padding() }
Thay đổi lại layout một chút nha, thêm các Button cho các vùng muốn hiển thị. Giao diện tạm thời như thế này!
MKCoordinateRegion
Sau khi đã có UI ưng ý, bạn tới bước chuẩn bị dữ liệu nhóe. Đối tượng MKCoordinateRegion sẽ được dùng để quản lý dữ liệu của Map theo từng vùng. Do đó, ta cần chuẩn bị vài vùng cần hiển thị trước tiên. Xem code ví dụ sau:
extension CLLocationCoordinate2D { static let turtleTower = CLLocationCoordinate2D(latitude: 21.027910, longitude: 105.852298) static let dragonBridge = CLLocationCoordinate2D(latitude: 16.06101183799654, longitude: 108.22761720565889) static let benthanhMarket = CLLocationCoordinate2D(latitude: 10.772823166683567, longitude: 106.69893065796946) } extension MKCoordinateRegion { static let turtleTower = MKCoordinateRegion( center: CLLocationCoordinate2D.turtleTower, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1) ) static let dragonBridge = MKCoordinateRegion( center: CLLocationCoordinate2D.dragonBridge, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1) ) static let benthanhMarket = MKCoordinateRegion( center: CLLocationCoordinate2D.benthanhMarket, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1) ) }
Ta có 2 extension cho CLLocationCoordinate2D & MKCoordinateRegion. Với Region, bạn cần khởi tạo với 2 tham số chính:
center
: tọa độ trung tâmspan
: kích thước vùng hiển thị
MapCameraPosition
Vì đây là SwiftUI, nên mình sẽ cố gắng hướng tới việc hướng dẫn sao cho đúng tư tưởng của nền tảng lập trình này. Do đó, ta sẽ không dùng cách init
trực tiếp position
vào Map. Mà sẽ tạo một biến State để quản lý position
.
Trước tiên, chúng ta sẽ thêm một thuộc tính cho ContentView:
@State private var position: MapCameraPosition = .automatic
Đối tượng MapCameraPosition sẽ đảm đương vai trò quản lý việc hiển thị một khung hình Map. Trong giới hạn của bài viết, mình chỉ nói về việc xét hiển thị theo từng vùng mà thôi.
Tiếp theo, bạn chỉ cần khởi tạo lại Map với tham số posiion
nhóe!
Map(position: $position) .animation(.easeIn, value: position)
Mình thêm phần animation
cho Map. Để mỗi lần người dùng tương tác, thì biến position
thay đổi giá trị. Và Map sẽ tiến hành biến đổi kèm hiệu ứng …
Nhìn đõa con mắt!
Set Region
Cuối cùng, chúng ta cũng tới công việc chính. Thay đổi giá trị của position
hay thay đổi vùng hiển thị theo ý đồ của chúng ta. Bạn sẽ tiến hành gán các giá trị mới cho biến trạng thái position
tại các action
của các Button. Code ví dụ như sau:
var body: some View { VStack { VStack { Map(position: $position) .animation(.easeIn, value: position) HStack { //region #1 Button { position = .region(MKCoordinateRegion.turtleTower) } label: { Label("1", systemImage: "1.square.fill") } .buttonStyle(.borderedProminent) //region #2 Button { position = .region(MKCoordinateRegion.dragonBridge) } label: { Label("2", systemImage: "2.square.fill") } .buttonStyle(.borderedProminent) //region #3 Button { position = .region(MKCoordinateRegion.benthanhMarket) } label: { Label("3", systemImage: "3.square.fill") } .buttonStyle(.borderedProminent) } .labelStyle(.iconOnly) } } .padding() }
Và công việc cũng đơn giản vậy thôi. Vì đây là SwiftUI, mọi thứ đã được chúng ta kết nối, giao diện sẽ tự động phản ứng lại các giá trị trạng thái. Chờ màn hình Preview hiển thị xong, bạn hãy test nhóe. Kết quả như sau:
- Region #1
- Region #2
- Region #3
Mình xin hết thúc bài viết tại đây. Có cơ hội sẽ viết tiếp các phần tiếp theo cho MapKit trên SwiftUI, kha khá điều thú vị đấy!
Tạm kết
- Cách mới để sử dụng Map & MapKit trên SwiftUI
- Thêm các nội dung cơ bản là Marker & Annotation lên Map
- Thay đổi các kiểu hiển thị của bản đồ
- Sử dụng dữ liệu để hiển thị các vùng trên bản đồ
Okay! Tới đây, mình xin kết thúc bài viết giới thiệu về MapKit trên SwiftUI . Nếu có gì thắc mắc hay góp ý cho mình, thì bạn có thể để lại bình luận hoặc gửi email theo trang Contact.
- Bạn có thể checkout code tại đây.
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)