У меня есть вид карты, в котором есть кнопка, которая при нажатии должна центрировать карту по текущему местоположению пользователя. Я пытаюсь достичь этого с помощью фреймворка Swift. Я попытался решить эту проблему, добавив свойство @State
с именем mapCenter
и присвоив этому свойству в теме assign(to:on:)
комбайна, следующим образом:
struct MapWithButtonView: View {
// What the current map view center should be.
@State var mapCenter = CLLocationCoordinate2D(latitude: 42.35843, longitude: -71.05977)
// A subject whose `send(_:)` method is being called from within the CenterButton view to center the map on the user's location.
private var centerButtonTappedPublisher = PassthroughSubject<Bool, Never>()
// A publisher that turns a "center button tapped" event into a coordinate.
private var centerButtonTappedCoordinatePublisher: AnyPublisher<CLLocationCoordinate2D?, Never> {
centerButtonTappedPublisher
.map { _ in LocationManager.default.currentUserCoordinate }
.eraseToAnyPublisher()
}
private var coordinatePublisher: AnyPublisher<CLLocationCoordinate2D, Never> {
Publishers.Merge(LocationManager.default.$initialUserCoordinate, centerButtonTappedCoordinatePublisher)
.replaceNil(with: CLLocationCoordinate2D(latitude: 42.35843, longitude: -71.05977))
.eraseToAnyPublisher()
}
private var cancellableSet: Set<AnyCancellable> = []
init() {
// This does not result in an update to the view... why not?
coordinatePublisher
.receive(on: RunLoop.main)
.handleEvents(receiveSubscription: { (subscription) in
print("Receive subscription")
}, receiveOutput: { output in
print("Received output: \(String(describing: output))")
}, receiveCompletion: { _ in
print("Receive completion")
}, receiveCancel: {
print("Receive cancel")
}, receiveRequest: { demand in
print("Receive request: \(demand)")
})
.assign(to: \.mapCenter, on: self)
.store(in: &cancellableSet)
}
var body: some View {
ZStack {
MapView(coordinate: mapCenter)
.edgesIgnoringSafeArea(.all)
CenterButton(buttonTappedPublisher: centerButtonTappedPublisher)
}
}
}
MapView
- это представление UIViewRepresentable
и выглядит следующим образом:
struct MapView: UIViewRepresentable {
// The center of the map.
var coordinate: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView(frame: .zero)
mapView.showsUserLocation = true
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
}
}
CenterButton
- это простая кнопка, которая выглядит следующим образом:
struct CenterButton: View {
var buttonTappedPublisher: PassthroughSubject<Bool, Never>
var body: some View {
Button(action: {
self.buttonTappedPublisher.send(true)
}) {
Image(systemName: "location.fill")
.imageScale(.large)
.accessibility(label: Text("Center map"))
}
}
}
А LocationManager
- это ObservableObject
, который публикует текущий пользователь и начальное местоположение:
class LocationManager: NSObject, ObservableObject {
// The first location reported by the CLLocationManager.
@Published var initialUserCoordinate: CLLocationCoordinate2D?
// The latest location reported by the CLLocationManager.
@Published var currentUserCoordinate: CLLocationCoordinate2D?
private let locationManager = CLLocationManager()
static let `default` = LocationManager()
private override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.activityType = .other
locationManager.requestWhenInUseAuthorization()
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .authorizedAlways, .authorizedWhenInUse:
NSLog("Location authorization status changed to '\(status == .authorizedAlways ? "authorizedAlways" : "authorizedWhenInUse")'")
enableLocationServices()
case .denied, .restricted:
NSLog("Location authorization status changed to '\(status == .denied ? "denied" : "restricted")'")
disableLocationServices()
case .notDetermined:
NSLog("Location authorization status changed to 'notDetermined'")
default:
NSLog("Location authorization status changed to unknown status '\(status)'")
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// We are only interested in the user's most recent location.
guard let location = locations.last else { return }
// Use the location to update the location manager's published state.
let coordinate = location.coordinate
if initialUserCoordinate == nil {
initialUserCoordinate = coordinate
}
currentUserCoordinate = coordinate
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
NSLog("Location manager failed with error: \(error)")
}
// MARK: Helpers.
private func enableLocationServices() {
locationManager.startUpdatingLocation()
}
private func disableLocationServices() {
locationManager.stopUpdatingLocation()
}
}
К сожалению, выше не работает. Представление никогда не обновляется при нажатии CenterButton
. Я решил эту проблему, используя ObservableObject
объект модели представления со свойством @Published var mapCenter
, однако я не знаю, почему мое первоначальное решение с использованием @State
не работает. Что не так с обновлением @State
, как я это делал выше?
Обратите внимание, что если вы попытаетесь воспроизвести это, вам нужно будет добавить ключ NSLocationWhenInUseUsageDescription
со значением, например «Это приложение требует доступа к вашему location "в вашем файле Info.plist
, чтобы иметь возможность предоставлять разрешения для местоположения.