在我的项目中,我使用 SwiftUI:
Map(coordinateRegion: $region)
我添加了一个 MKUserTrackingButton:
struct UserTrackingButton: UIViewRepresentable {
func makeUIView(context: Context) -> MKUserTrackingButton {
MKUserTrackingButton(mapView: context.environment.mkMapView)
}
func updateUIView(_ view: MKUserTrackingButton, context: Context) { }
}
但是它不起作用。那么如何将我的 SwiftUI map View 链接到此按钮?
最佳答案
在创建 MapView 的方法中,使用 MapView 作为引用创建 MKUserTrackingButton,然后将该按钮作为 subview 添加到 map View 中。所以像这样:
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView(frame: UIScreen.main.bounds)
mapView.delegate = context.coordinator
// set mapView properties as you need them
let trackingButton = MKUserTrackingButton(mapView: mapView)
mapView.addSubview(trackingButton)
// positioning the button on the right size with a bit
// of autolayout constraints. Adjust constants to
// your liking.
trackingButton.translatesAutoresizingMaskIntoConstraints = false
trackingButton.trailingAnchor.constraint(equalTo: mapView.layoutMarginsGuide.trailingAnchor, constant: -16).isActive = true
trackingButton.topAnchor.constraint(equalTo: mapView.layoutMarginsGuide.topAnchor, constant: 42).isActive = true
return mapView
}
我使用自动布局约束来定位按钮,具体取决于 map 上可能发生的其他情况,您可能需要调整这些和/或设置按钮的 x、y、宽度、高度属性。
编辑:2022 年 6 月 8 日
上面是一个非常令人不满意的答案,部分原因是,trackingButton 对象是 mkmapview 的 subview ,与 SwifUI 的布局机制不太兼容(实际上根本不与 SWIFTUI 兼容)。解决这个问题的一个更好的方法是通过 MapView 的模型类,尽管仍然感觉太老套了。这里是添加 MapType 按钮的简短代码布局。标准 map 类型按钮(例如跟踪按钮)不存在,因此这是提供跟踪按钮的更通用方法。
MapTypeButton 是一个简单的 UIButton,专门用于循环浏览 MKMapType 枚举选项以及您在按钮中选择的图像。由于 MKMapView 和 UIButton 都不属于 SWIFTUI 组件集,因此首先需要将它们包装在 UIViewRepresentable 中。这采用熟悉的形式:
Struct abcView:UIViewRepresentable {
func makeUIView(context: Context) -> abcView {
}
func updateUIView(_ uiView: abcView, context: Context)
}
class Coordinator: NSObject {
init(_ parent: abcView) {
self.parent = parent
super.init()
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
}
显然用你的类名替换“abcView”。下面给出了 MKMapView 和 MapType 目标 UIButton 的骨架版本以及它们的模型类,从而生成 MapView 和 MapTypeButtonView。
struct MapView : UIViewRepresentable {
var mapViewModel:MapViewModel
func makeUIView(context: Context) -> MKMapView {
let mkMapView = MKMapView(frame: UIScreen.main.bounds)
mkMapView.delegate = context.coordinator
// any mkMapView set up stuff goes here
mkMapView.region = mapViewModel.region
mkMapView.showsCompass = false
mapViewModel.mkMapView = mkMapView. // <— forms the bridge to MapTypeButtonView
return mkMapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
super.init()
}
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
struct MapTypeButtonView: UIViewRepresentable {
@Binding var mkMapView:MKMapView?
func makeUIView(context:Context) -> MapTypeButton {
let button = MapTypeButton()
return button
}
func updateUIView(_ uiView: MapTypeButton, context: Context) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
uiView.mkMapView = mkMapView. // <— makes MKMapView available to the button
}
class Coordinator : NSObject {
var parent : MapTypeButtonView
init (_ parent:MapTypeButtonView) {
self.parent = parent
super.init()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
final class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
let startLocation = CLLocationCoordinate2D(latitude: 40.7128, longitude: -74.0060)
let defaultSpan = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
@Published var region = MKCoordinateRegion(center: startLocation, span: defaultSpan)
@Published var mkMapView:MKMapView?
@discardableResult
func checkIfLocationServicesEnabled() -> Bool { // <— customize this to your liking
if CLLocationManager.locationServicesEnabled() {
locationMgr = CLLocationManager()
if locationMgr == nil { return false }
locationMgr!.desiredAccuracy = kCLLocationAccuracyBest
locationMgr!.delegate = self
locationMgr!.requestLocation()
locationMgr!.startUpdatingLocation()
locationMgr!.startUpdatingHeading()
return true
}
else {
// show alert ?
return false
}
}
private func checkLocationMgrAuthorization() {
guard let mgr = locationMgr else { return }
switch mgr.authorizationStatus {
case .notDetermined: mgr.requestWhenInUseAuthorization()
case .restricted: print("ALERT: LOCMGR RESTRICTED")
case .denied: print("ALERT: LOCMGR DENIED")
case .authorizedAlways:
region = MKCoordinateRegion(center: mgr.location!.coordinate, span: self.defaultSpan)
case .authorizedWhenInUse:
// Info.plist must contain both “NSLocationAlwaysAndWhenInUseUsageDescription”
// and “NSLocationWhenInUseUsageDescription”
mgr.requestAlwaysAuthorization()
@unknown default:
break
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationMgrAuthorization()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
//
}
}
我设置了 MapType 按钮,使其仅在 .standard 和 .hybrid map 类型之间循环,调用 nextMapType() 进行循环。根据您的喜好进行更改。
class MapTypeButton : UIView {
let buttonWidthHeight = 32
weak open var mkMapView: MKMapView?
private var mapTypes:[MKMapType] = [.standard, .hybrid]
private var mapTypeImageNames:[String] = [“map”, "map.fill"]
private var button:UIButton?
override var intrinsicContentSize: CGSize {
return CGSize(width: buttonWidthHeight, height: buttonWidthHeight)
}
var mapType : MKMapType {
get { return mkMapView?.mapType ?? .standard }
set { mkMapView?.mapType = newValue }
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init() {
super.init(frame: CGRect(x: 0, y: 0, width: buttonWidthHeight, height: buttonWidthHeight))
self.setup()
}
private func setup() {
let action = UIAction(image: UIImage(systemName: mapTypeImageNames[0]),
handler: { _ in self.nextMapType() })
self.button = UIButton(primaryAction: action)
if let btn = self.button {
self.addSubview(btn)
btn.contentVerticalAlignment = .fill
btn.contentHorizontalAlignment = .fill
btn.translatesAutoresizingMaskIntoConstraints = false
_ = btn.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
_ = btn.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
_ = btn.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
_ = btn.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
}
}
func nextMapType() {
guard let mapView = self.mkMapView else { return }
if let idx = self.mapTypes.firstIndex(of: mapView.mapType) {
var i = mapTypes.distance(from: mapTypes.startIndex, to: idx)
i += 1
i = i % self.mapTypes.count
mapView.mapType = mapTypes[i]
self.button?.setImage(UIImage(systemName: mapTypeImageNames[i]), for:.normal)
self.button?.contentMode = .scaleAspectFit
self.button?.imageView?.contentMode = .scaleAspectFit
}
}
}
最后,将它们放在一个内容 View 中,其中 map 位于背景中,顶部的 MapTypeButton(以及用于显示和讲述的其他一些按钮)水平 float 在中心);
struct ContentView: View {
@StateObject var mapViewModel = MapViewModel()
var body: some View {
ZStack {
MapView(mapViewModel: mapViewModel)
HStack {
Button(action: {}) { Image(systemName: "heart.fill") }
Spacer()
MapTypeButtonView(mkMapView: $mapViewModel.mkMapView)
}
}
}
}
您现在可以为 TrackingButton 创建类似的设置(我不会使用 MKTrackingButton)。 MapTypeButtonView 应在 SWIFTUI 中正常运行并遵循所有预期的布局要求,但可以直接连接到 MKMapView 来控制它。
是的,有大量代码,其中大部分也需要用于其他目的,因此并非完全出乎意料,但仍然比应有的更麻烦。希望它对您或其他人有所帮助。
关于swiftui - 如何将 MKUserTrackingButton 链接到 SwiftUI map View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71151969/