【问题标题】:How to add a move back to user location button in swiftUI?如何在swiftUI中添加返回用户位置按钮?
【发布时间】:2019-10-15 15:47:09
【问题描述】:

我对 Swift 和 SwiftUI 还很陌生,我想在地图视图的顶部添加一个用户跟踪按钮,以便在点击时用户的当前位置可以回到屏幕中心。我已经有了地图视图和按钮,但未能使其工作。

这里是 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()

        }
    }
}

【问题讨论】:

  • 您找到问题的答案了吗?我想我也有同样的问题。我让它工作了,但是当我想处理所有可能的授权更改时,我发现自己遇到了一个奇怪的错误????如果我发现了什么,我会告诉你的。
  • 我有解决办法????我只需要显示一个警报,然后我会在这里分享它????????

标签: swift mapkit swiftui


【解决方案1】:

我遇到了同样的问题,几个小时后,我设法让某些东西按要求工作

  • 启动时,如果用户授权,它会显示用户位置,但会等待他点击按钮以激活关注。
  • 如果他授权并点击按钮,它会跟随他。
  • 如果应用由于某种原因无法访问他的位置(拒绝访问、限制……),它会告诉他在“设置”中进行更改并将他重定向到那里。
  • 如果他在运行应用程序时更改了授权,它会自动更改。
  • 如果地图跟随他,按钮就会消失。
  • 如果他拖动地图(停止跟随),按钮会再次出现。
  • (按钮支持深色模式)

我真的希望它对你有所帮助,我想有一天我会把它放在 GitHub 上。如果有,我会在此处添加链接。

首先,我不想每次都重新创建 MKMapView,所以我把它放在了一个名为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)
            }
        }
        
    }
    
}

最后,将其放入 SwiftUI 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())
    }
    
}

【讨论】:

  • 很好的解决方案。如果有人想从一开始就放大到用户位置,请将 @State private var userTrackingMode: MKUserTrackingMode = .none 更改为 .follow
【解决方案2】:

对于其他在实施时遇到困难的人,@Remi b。回答是否是一个非常可行的选择,我花了很多时间试图在我的项目中实现它,但我最终采用了不同的方式。这允许位置按钮工作并循环仅类型的位置跟踪和按钮图像,如地图应用程序中的。这就是我的选择:

添加我的基本MKMapView 后,我为MKUserTrackingButton 创建了一个UIViewRepresentable,如下所示: 注意:@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()
                }
            }
        }
    }
}

【讨论】:

    【解决方案3】:

    这是我返回用户位置的方法。我使用通知来通知 Mapview。我在github上传了演示项目

    首先,定义一个通知名称

    extension Notification.Name {
      static let goToCurrentLocation = Notification.Name("goToCurrentLocation")
    }
    
    

    其次,在 MapView coordinator 中监听通知:

    
    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)
        }
    
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-11
      • 2019-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多