【问题标题】:How Do I Draw a String in an MKOverlayRender如何在 MKOverlayRenderer 中绘制字符串
【发布时间】:2023-01-27 00:30:34
【问题描述】:

我有一个用例,我想在一个州中绘制和标记县。注释似乎不是解决此问题的正确方法。首先,标签指的是区域而不是点。第二,太多了;因此,我将不得不根据缩放级别(实际上更像是 MKCoordinateRegion 跨度的大小)有选择地显示和隐藏注释。最后,除非用户开始放大,否则县标签并不是那么相关。

附带说明一下,县界可能会出现在地图块中,但不会被强调。此外,还有许多我可能想要绘制的其他边界,它们在地图图块中完全不存在。

最终,我想要做的是为每个县形状创建一个叠加层(县是可点击的,我可以导航到详细信息)和另一组标签叠加层。我将县形状和标签分开,因为县形状很乱,我只使用县的中心。这种方法不能保证标签不会绘制在县形状之外,这意味着当绘制其他县时,标签最终可能会被剪掉。

绘制县城形状相对容易,或者至少有据可查。我不包括任何渲染形状的代码。另一方面,绘图文本并不直截了当,没有很好的记录,而且大多数关于这个主题的帖子都是古老的。缺乏关于该主题的最新帖子以及大多数帖子提出的解决方案不再有效、使用已弃用的 API 或仅解决部分问题的事实激发了这篇帖子。当然,在这个问题上缺乏活动可能是因为我的策略愚蠢得令人麻木。

我已经发布了该问题的完整解决方案。如果您可以改进以下解决方案或认为有更好的方法,我将不胜感激。或者,如果您正在尝试找到解决此问题的方法,您会发现这篇文章比我看过的几十篇文章更有帮助,总的来说,这篇文章让我走到了现在的位置。

【问题讨论】:

    标签: swift swiftui mapkit


    【解决方案1】:

    下面是一个可以在 Xcode 单视图 Playground 中运行的完整解决方案。我正在运行 Xcode 14.2。最重要的代码是 LabelOverlayRenderer 的重写绘制函数。那段代码是我花了一天多的时间努力编写的。我几乎放弃了。另一个关键点是在绘制文本时,使用 CoreText。与绘制和管理文本相关的 API 有很多,而且大多数都有很多名称更改和弃用。

    import UIKit
    import MapKit
    import SwiftUI
    
    class LabelOverlayRenderer: MKOverlayRenderer {
        let title: String
        let center: CLLocationCoordinate2D
        
        init(overlay: LabelOverlay) {
            center = overlay.coordinate
            title = overlay.title!
            super.init(overlay: overlay)
        }
        
        override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
            context.saveGState()
            // Set Drawing mode
            context.setTextDrawingMode(.fillStroke)
            // If I don't do this, the text is upside down.
            context.textMatrix = CGAffineTransformMakeScale(1.0, -1.0);
            // Text size is crazy big because label has to be miles across
            // to be visible.
            var attrs = [ NSAttributedString.Key : Any]()
            attrs[NSAttributedString.Key.font] = UIFont(name: "Helvetica", size: 128000.0)!
            attrs[NSAttributedString.Key.foregroundColor] = UIColor(Color.red)
            let attributedString = NSAttributedString(string: title, attributes: attrs)
            let line = CTLineCreateWithAttributedString(attributedString)
            // Get the size of the whole string, so the string can
            // be centered. CGSize is huge because I don't want
            // to clip or wrap the string. The range setting
            // is just cut and paste.  Looks like a place holder.
            // Ideally, it is the range of that portion
            // of the string for which I want the size.
            let frameSetter = CTFramesetterCreateWithAttributedString(attributedString)
            let size = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, CGSize(width: 1000000, height: 1000000), nil)
            
            // Center is lat-lon, but map is in meters (maybe? definitely
            // not lat-lon). Center string and draw.
            var p = point(for: MKMapPoint(center))
            p.x -= size.width/2
            p.y += size.height/2
            // There is no "at" on CTLineDraw. The string
            // is positioned in the context.
            context.textPosition = p
            CTLineDraw(line, context)
            
            context.restoreGState()
        }
    }
    
    class LabelOverlay: NSObject, MKOverlay {
        let title: String?
        let coordinate: CLLocationCoordinate2D
        let boundingMapRect: MKMapRect
        
        init(title: String, coordinate: CLLocationCoordinate2D, boundingMapRect: MKMapRect) {
            self.title = title
            self.coordinate = coordinate
            self.boundingMapRect = boundingMapRect
        }
    }
    
    class MapViewCoordinator: NSObject, MKMapViewDelegate {
        func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
            if let overlay = overlay as? LabelOverlay {
                 return LabelOverlayRenderer(overlay: overlay)
            }
            
            fatalError("Unknown overlay type!")
        }
    }
    
    
    struct MyMapView: UIViewRepresentable {
        func makeCoordinator() -> MapViewCoordinator {
            return MapViewCoordinator()
        }
        
        func updateUIView(_ view: MKMapView, context: Context){
            // Center on Georgia
            let center = CLLocationCoordinate2D(latitude: 32.6793, longitude: -83.62245)
            let span = MKCoordinateSpan(latitudeDelta: 4.875, longitudeDelta: 5.0003)
            let region = MKCoordinateRegion(center: center, span: span)
            view.setRegion(region, animated: true)
            view.delegate = context.coordinator
            
            let coordinate = CLLocationCoordinate2D(latitude: 32.845084, longitude: -84.3742)
            let mapRect = MKMapRect(x: 70948460.0, y: 107063759.0, width: 561477.0, height: 613908.0)
            let overlay = LabelOverlay(title: "Hello World!", coordinate: coordinate, boundingMapRect: mapRect)
            view.addOverlay(overlay)
        }
        
        func makeUIView(context: Context) -> MKMapView {
            // Create a map with constrained zoom gestures only
            let mapView = MKMapView(frame: .zero)
            mapView.isPitchEnabled = false
            mapView.isRotateEnabled = false
            
            let zoomRange = MKMapView.CameraZoomRange(
                minCenterCoordinateDistance: 160000,
                maxCenterCoordinateDistance: 1400000
            )
            mapView.cameraZoomRange = zoomRange
            return mapView
        }
        
        
    }
    
    struct ContentView: View {
        var body: some View {
            VStack {
                MyMapView()
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-30
      • 1970-01-01
      相关资源
      最近更新 更多