我正在尝试在我的应用程序中实现地图功能。我在使用 UIKit 和 SwiftUI 时遇到了 MapKit 问题。这是我的问题。我在名为 EventViewModel 的类中有一个名为 events 的 @Published 数组。我将此视图模型作为环境对象传递,因为我在许多视图中使用它。在一个视图中,我接收用户输入并将事件添加到数组中。这工作正常,我的事件以列表设计呈现。但是,我还在 Map() 中使用事件数组作为注释项,因为事件包含位置坐标。我的问题是当我添加新事件时,新注释不会在地图上呈现。但是,如果我在 @Published 事件数组中预加载了数据,则注释加载正常。
这是与 UIKit 交互的 SwiftUI
import SwiftUI
struct MapView: View {
@EnvironmentObject var eventViewModel: EventViewModel
var body: some View {
MapViewController(eventViewModel: eventViewModel)
.ignoresSafeArea()
}
}
struct MapViewController: UIViewControllerRepresentable {
typealias UIViewControllerType = ViewController
let eventViewModel: EventViewModel
func makeUIViewController(context: Context) -> ViewController {
let vc = ViewController()
vc.eventViewModel = eventViewModel
return vc
}
func updateUIViewController(_ uiViewController: ViewController, context: Context) {
}
}
class ViewController: UIViewController {
var locationManager: CLLocationManager?
var eventViewModel: EventViewModel? {
didSet {
subscribeToEventChanges()
}
}
private var cancellables: Set<AnyCancellable> = []
private func subscribeToEventChanges() {
eventViewModel?.$events
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.updateMapAnnotations()
}
.store(in: &cancellables)
}
private func updateMapAnnotations() {
// Clear existing annotations on the map
mapView.removeAnnotations(mapView.annotations)
// Add new annotations based on the updated eventViewModel.events
addEventPins()
print("Update Map Annotations") // Get's into this function when an event is added
}
lazy var mapView: MKMapView = {
let map = MKMapView()
map.showsUserLocation = true
map.isRotateEnabled = false
map.translatesAutoresizingMaskIntoConstraints = false
return map
}()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestWhenInUseAuthorization()
locationManager?.requestLocation()
mapView.delegate = self
createMap()
addEventPins()
}
private func createMap() {
view.addSubview(mapView)
mapView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
mapView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
mapView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
mapView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func checkLocationAuthorization() {
guard let locationManager = locationManager,
let location = locationManager.location else { return }
switch locationManager.authorizationStatus {
case .authorizedAlways, .authorizedWhenInUse:
let region = MKCoordinateRegion(
center: location.coordinate,
latitudinalMeters: 750,
longitudinalMeters: 750
)
mapView.setRegion(region, animated: true)
case .notDetermined, .restricted:
print("Location cannot be determined or is restricted")
case .denied:
print("Location services have been denied.")
@unknown default:
print("Unknown error occurred. Unable to get location.")
}
}
private func addEventPins() {
guard let eventViewModel = eventViewModel else { return }
for event in eventViewModel.events {
let annotation = MKPointAnnotation()
annotation.coordinate = event.coordinate
annotation.title = event.name
annotation.subtitle = event.host
let _ = LocationAnnotationView(annotation: annotation, reuseIdentifier: "eventAnnotation")
mapView.addAnnotation(annotation)
print(mapView.annotations) // Properly adds the annotation when an event is added
}
}
}
final class LocationAnnotationView: MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
centerOffset = CGPoint(x: -125, y: -125)
canShowCallout = true
setupUI(name: annotation?.title ?? "")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI(name: String?) {
backgroundColor = .clear
let hostingController = UIHostingController(rootView: Pin(name: name ?? ""))
hostingController.view.frame = CGRect(x: 0, y: 0, width: 250, height: 250)
hostingController.view.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
addSubview(hostingController.view)
hostingController.view.backgroundColor = .clear
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "locationAnnotation")
if annotationView == nil {
annotationView = LocationAnnotationView(annotation: annotation, reuseIdentifier: "locationAnnotation")
} else {
annotationView?.annotation = annotation
}
return annotationView
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuthorization()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
}
最后这是我在另一个视图中的createEvent()函数,它也使用了
@EnvironmentObject var eventViewModel: EventViewModel
private func createEvent() {
var coordinates = CLLocationCoordinate2D()
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(self.location) {
placemarks, error in
let placemark = placemarks?.first
let lat = placemark?.location?.coordinate.latitude
let lon = placemark?.location?.coordinate.longitude
// Format is Address, City, State Zipcode
coordinates = CLLocationCoordinate2D(latitude: lon ?? CLLocationDegrees(), longitude: lat ?? CLLocationDegrees())
print(coordinates)
let event = Event(name: self.eventName, host: self.hosts.first ?? "No Host", date: self.date, coordinate: coordinates)
let date = Calendar.current.startOfDay(for: event.date)
eventViewModel.groupedEvents[date, default: []].append(event)
eventViewModel.events.append(event)
print(eventViewModel.events)
}
}
起初我完全从 MapKit 的 SwiftUI 版本开始,但发现了同样的问题,即当事件附加到数组时注释不会更新。正如许多人所说,切换到 UIKit 可以提供更大的灵活性,因为当前版本的 MapKit 仍然相当有限。
只需更改:
let eventViewModel: EventViewModel
到
let events: [Event]
然后实施:
func updateUIViewController(...
每当
events
发生变化时都会调用此函数。从地图中删除数组中不再存在的所有内容并添加新的内容。
我们在 SwiftUI 中不使用视图模型对象,
View
结构已经是视图模型了。