我对Swift和SwiftUI还是很陌生,我想在mapview的顶部添加一个用户跟踪按钮,以便在点击时用户的当前位置可以回到屏幕中央。我已经有了mapview和按钮,但是无法正常工作。
这是ContentView.swift文件,我被加在****位置:
import SwiftUI
import MapKit
struct ContentView: View {
var body: some View {
ZStack {
MapView(locationManager: $locationManager)
.edgesIgnoringSafeArea(.bottom)
HStack {
Spacer()
VStack {
Spacer()
Button(action: {
******
}) {
Image(systemName: "location")
.imageScale(.small)
.accessibility(label: Text("Locate Me"))
.padding()
}
.background(Color.white)
.cornerRadius(10)
.padding()
}
}
}
}
这是MapView.swift:
import SwiftUI
import MapKit
import CoreLocation
import ECMapNavigationAble
struct MapView: UIViewRepresentable, ECMapNavigationAble{
var locationManager = CLLocationManager()
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
MKMapView()
}
func updateUIView(_ view: MKMapView, context: UIViewRepresentableContext<MapView>){
view.showsUserLocation = true
view.isPitchEnabled = false
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
if let userLocation = locationManager.location?.coordinate {
let userLocationEC = ECLocation(coordinate : userLocation, type: .wgs84)
let viewRegion = MKCoordinateRegion(center: userLocationEC.gcj02Coordinate, latitudinalMeters: 200, longitudinalMeters: 200)
view.userTrackingMode = .follow
view.setRegion(viewRegion, animated: true)
}
DispatchQueue.main.async{
self.locationManager.startUpdatingLocation()
}
}
}
答案 0 :(得分:0)
我真的希望它能对您有所帮助,我想有一天会把它放在GitHub上。如果可以,我将在此处添加链接。
MapViewContainer
尽管??♂️
,但我认为这不是一个好习惯如果您不想使用它,只需将
@EnvironmentObject private var mapViewContainer: MapViewContainer
中的let mapView = MKMapView(frame: .zero)
替换为MKMapViewRepresentable
(并将mapViewContainer.mapView
更改为mapView
)>
import MapKit
class MapViewContainer: ObservableObject {
@Published public private(set) var mapView = MKMapView(frame: .zero)
}
MapViewRepresentable
import SwiftUI
import MapKit
// MARK: - MKMapViewRepresentable
struct MKMapViewRepresentable: UIViewRepresentable {
var userTrackingMode: Binding<MKUserTrackingMode>
@EnvironmentObject private var mapViewContainer: MapViewContainer
func makeUIView(context: UIViewRepresentableContext<MKMapViewRepresentable>) -> MKMapView {
mapViewContainer.mapView.delegate = context.coordinator
context.coordinator.followUserIfPossible()
return mapViewContainer.mapView
}
func updateUIView(_ mapView: MKMapView, context: UIViewRepresentableContext<MKMapViewRepresentable>) {
if mapView.userTrackingMode != userTrackingMode.wrappedValue {
mapView.setUserTrackingMode(userTrackingMode.wrappedValue, animated: true)
}
}
func makeCoordinator() -> MapViewCoordinator {
let coordinator = MapViewCoordinator(self)
return coordinator
}
// MARK: - Coordinator
class MapViewCoordinator: NSObject, MKMapViewDelegate, CLLocationManagerDelegate {
var control: MKMapViewRepresentable
let locationManager = CLLocationManager()
init(_ control: MKMapViewRepresentable) {
self.control = control
super.init()
setupLocationManager()
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.pausesLocationUpdatesAutomatically = true
}
func followUserIfPossible() {
switch CLLocationManager.authorizationStatus() {
case .authorizedAlways, .authorizedWhenInUse:
control.userTrackingMode.wrappedValue = .follow
default:
break
}
}
private func present(_ alert: UIAlertController, animated: Bool = true, completion: (() -> Void)? = nil) {
// UIApplication.shared.keyWindow has been deprecated in iOS 13,
// so you need a little workaround to avoid the compiler warning
// https://stackoverflow.com/a/58031897/10967642
let keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
keyWindow?.rootViewController?.present(alert, animated: animated, completion: completion)
}
// MARK: MKMapViewDelegate
func mapView(_ mapView: MKMapView, didChange mode: MKUserTrackingMode, animated: Bool) {
#if DEBUG
print("\(type(of: self)).\(#function): userTrackingMode=", terminator: "")
switch mode {
case .follow: print(".follow")
case .followWithHeading: print(".followWithHeading")
case .none: print(".none")
@unknown default: print("@unknown")
}
#endif
if CLLocationManager.locationServicesEnabled() {
switch mode {
case .follow, .followWithHeading:
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
// Possibly due to active restrictions such as parental controls being in place
let alert = UIAlertController(title: "Location Permission Restricted", message: "The app cannot access your location. This is possibly due to active restrictions such as parental controls being in place. Please disable or remove them and enable location permissions in settings.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
// Redirect to Settings app
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert)
DispatchQueue.main.async {
self.control.userTrackingMode.wrappedValue = .none
}
case .denied:
let alert = UIAlertController(title: "Location Permission Denied", message: "Please enable location permissions in settings.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
// Redirect to Settings app
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert)
DispatchQueue.main.async {
self.control.userTrackingMode.wrappedValue = .none
}
default:
DispatchQueue.main.async {
self.control.userTrackingMode.wrappedValue = mode
}
}
default:
DispatchQueue.main.async {
self.control.userTrackingMode.wrappedValue = mode
}
}
} else {
let alert = UIAlertController(title: "Location Services Disabled", message: "Please enable location services in settings.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
// Redirect to Settings app
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert)
DispatchQueue.main.async {
self.control.userTrackingMode.wrappedValue = mode
}
}
}
// MARK: CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
#if DEBUG
print("\(type(of: self)).\(#function): status=", terminator: "")
switch status {
case .notDetermined: print(".notDetermined")
case .restricted: print(".restricted")
case .denied: print(".denied")
case .authorizedAlways: print(".authorizedAlways")
case .authorizedWhenInUse: print(".authorizedWhenInUse")
@unknown default: print("@unknown")
}
#endif
switch status {
case .authorizedAlways, .authorizedWhenInUse:
locationManager.startUpdatingLocation()
control.mapViewContainer.mapView.setUserTrackingMode(control.userTrackingMode.wrappedValue, animated: true)
default:
control.mapViewContainer.mapView.setUserTrackingMode(.none, animated: true)
}
}
}
}
View
import SwiftUI
import CoreLocation.CLLocation
import MapKit.MKAnnotationView
import MapKit.MKUserLocation
struct MapView: View {
@State private var userTrackingMode: MKUserTrackingMode = .none
var body: some View {
ZStack {
MKMapViewRepresentable(userTrackingMode: $userTrackingMode)
.environmentObject(MapViewContainer())
.edgesIgnoringSafeArea(.all)
VStack {
if !(userTrackingMode == .follow || userTrackingMode == .followWithHeading) {
HStack {
Spacer()
Button(action: { self.followUser() }) {
Image(systemName: "location.fill")
.modifier(MapButton(backgroundColor: .primary))
}
.padding(.trailing)
}
.padding(.top)
}
Spacer()
}
}
}
private func followUser() {
userTrackingMode = .follow
}
}
fileprivate struct MapButton: ViewModifier {
let backgroundColor: Color
var fontColor: Color = Color(UIColor.systemBackground)
func body(content: Content) -> some View {
content
.padding()
.background(self.backgroundColor.opacity(0.9))
.foregroundColor(self.fontColor)
.font(.title)
.clipShape(Circle())
}
}
答案 1 :(得分:0)
对于其他在实现方面遇到困难的人,@ Remi b。回答是否是一个非常可行的选择,我花了很多时间试图将其实施到我的项目中,但最终还是采取了另一种方式。这样,位置按钮就可以正常工作并循环显示位置跟踪类型,并且按钮图像类似于Maps App中的图像。这就是我的目的:
在添加基本的MKMapView
之后,我为UIViewRepresentable
创建了一个MKUserTrackingButton
,如下所示:
注意:@EnvironmentObject var viewModel: ViewModel
包含我的mapView
)
struct LocationButton: UIViewRepresentable {
@EnvironmentObject var viewModel: ViewModel
func makeUIView(context: Context) -> MKUserTrackingButton {
return MKUserTrackingButton(mapView: viewModel.mapView)
}
func updateUIView(_ uiView: MKUserTrackingButton, context: Context) { }
}
然后在我的SwiftUI ContentView中或要将跟踪按钮添加到的任何位置:
struct MapButtonsView: View {
@EnvironmentObject var viewModel: ViewModel
var body: some View {
ZStack {
VStack {
Spacer()
Spacer()
HStack {
Spacer()
VStack(spacing: 12) {
Spacer()
// User tracking button
LocationButton()
.frame(width: 20, height: 20)
.background(Color.white)
.padding()
.cornerRadius(8)
}
.padding()
}
}
}
}
}
答案 2 :(得分:0)
这是我返回用户位置的方法。我使用 Notification 来通知 Mapview。我在我的 github
中上传了演示项目首先定义一个通知名称
extension Notification.Name {
static let goToCurrentLocation = Notification.Name("goToCurrentLocation")
}
其次,在 MapView 协调器中监听通知:
import SwiftUI
import MapKit
import Combine
struct MapView: UIViewRepresentable {
private let mapView = MKMapView()
func makeUIView(context: Context) -> MKMapView {
mapView.isRotateEnabled = false
mapView.showsUserLocation = true
mapView.delegate = context.coordinator
let categories: [MKPointOfInterestCategory] = [.restaurant, .atm, .hotel]
let filter = MKPointOfInterestFilter(including: categories)
mapView.pointOfInterestFilter = filter
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
}
func makeCoordinator() -> Coordinator {
.init(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
private var control: MapView
private var lastUserLocation: CLLocationCoordinate2D?
private var subscriptions: Set<AnyCancellable> = []
init(_ control: MapView) {
self.control = control
super.init()
NotificationCenter.default.publisher(for: .goToCurrentLocation)
.receive(on: DispatchQueue.main)
.sink { [weak self] output in
guard let lastUserLocation = self?.lastUserLocation else { return }
control.mapView.setCenter(lastUserLocation, animated: true)
}
.store(in: &subscriptions)
}
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
lastUserLocation = userLocation.coordinate
}
}
}
最后,当用户点击按钮时发送通知:
var body: some View {
return ZStack {
MapView()
.ignoresSafeArea(.all)
.onAppear() {
viewModel.startLocationServices()
goToUserLocation()
}
VStack {
Spacer()
Button(action: {
goToUserLocation()
}, label: {
Image(systemName: "location")
.font(.title2)
.padding(10)
.background(Color.primary)
.clipShape(Circle())
})
}
.frame(maxWidth: .infinity, alignment: .trailing)
.padding()
}
private func goToUserLocation() {
NotificationCenter.default.post(name: .goToCurrentLocation, object: nil)
}