MKMapView's scale is not shown
Asked Answered
T

4

7

I'm doing an iOS application. In Xcode 9.1 I create a MKMapView by

let mapView = MKMapView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height))
mapView.isUserInteractionEnabled = false
mapView.mapType = .satellite
mapView.showsCompass = false
mapView.showsScale = true
view.addSubview(mapView)

but when I run it in the simulator the scale is not shown and I get three messages in the log:

Could not inset compass from edges 9

Could not inset scale from edge 9

Could not inset legal attribution from corner 4

The compass is not shown (as expected) but it's not shown if I change mapView.showsCompass to trueeither. However, the Legal link is shown. What am I missing here? I'm guessing it's something about the new safe areas introduced with iOS 11, but I fail to see how that is important for a view I want to be covering the whole screen.

Triceratops answered 13/11, 2017 at 20:5 Comment(6)
In iOS 11 the compass is only shown if the map is rotated away from North and the scale is shown while zooming. I recommend the WWDC session on what’s new in MapKit. I think you can ignore those messages.Bestraddle
The WWDC session explains how to add a compass button to have a compass always displayedBestraddle
I will watch the WWDC session, but the question was about showing the scale. The code sets the compass to not be shown, so that's not an issue.Triceratops
The scale is only shown while zooming by default. The video also discusses the new scale view that you can addBestraddle
@Bestraddle for 2024 the scale is simply never shown, end of story. (Also, FWIW, the default position is totally absurd and useless.) You just add the two lines of code needed in your MKMapView.Audiphone
I have an app in the store that shows the scale and compass as I described; scale while zooming, compass when rotated. I updated it a week ago (so it is built with iOS 17 SDK)Bestraddle
P
11

In iOS 10 or lower

As @Paulw11 says, the scale is only shown while zooming by default.

In iOS 11

You can use scaleVisibility. https://developer.apple.com/documentation/mapkit/mkscaleview/2890254-scalevisibility

let scale = MKScaleView(mapView: mapView)
scale.scaleVisibility = .visible // always visible
view.addSubview(scale)
Phrygian answered 13/11, 2017 at 23:45 Comment(1)
While a great tip, you would almost certainly add it to the map and not the VC, and indeed you have to position the view you add has to be layed-out every layout cycle.Audiphone
S
8

had the same problem with the scale today. I want that scale visible all the time. Cost me several hours to solve it. So I add the code here, just in case, someone run into the same issue.

Got some hints:

from this thread: Use Safe Area Layout programmatically

and this website: Pain Free Constraints with Layout Anchors

Happy coding ...

Hardy

// "self.MapOnScreen" refers to the map currently displayed

// check if we have to deal with the scale
if #available(iOS 11.0, *) {

    // as we will change the UI, ensure it's on main thread
    DispatchQueue.main.async(execute: {

        // switch OFF the standard scale (otherwise both will be visible when zoom in/out)
        self.MapOnScreen.showsScale = false

        // build the view
        let scale = MKScaleView(mapView: self.MapOnScreen)

        // we want to use autolayout
        scale.translatesAutoresizingMaskIntoConstraints = false

        // scale should be visible all the time
        scale.scaleVisibility = .visible // always visible

        // add it to the map
        self.MapOnScreen.addSubview(scale)

        // get the current safe area of the map
        let guide = self.MapOnScreen.safeAreaLayoutGuide

        // Activate this array of constraints, which at the time removes leftovers if any
        NSLayoutConstraint.activate(
            [
                // LEFT (I do not want a change if right-to-left language) margin with an offset to safe area
                // alternative would be ".leadingAnchor", which switches to the right margin, if right-to-left language is used        
                scale.leftAnchor.constraint(equalTo: guide.leftAnchor, constant: 16.0),

                // right edge will be the middle of the map
                scale.rightAnchor.constraint(equalTo: guide.centerXAnchor),

                // top margin is the top safe area
                scale.topAnchor.constraint(equalTo: guide.topAnchor),

                // view will be 20 points high
                scale.heightAnchor.constraint(equalToConstant: 20.0)
            ]
        )
    })
}
Sellingplater answered 16/12, 2017 at 18:48 Comment(0)
D
0

Objective c equivalent:-

if (@available(iOS 11.0, *)) {
        // switch OFF the standard scale (otherwise both will be visible when zoom in/out)
    self.map.showsScale = false;

        // build the view

    MKScaleView* scale = [MKScaleView scaleViewWithMapView:self.map];

        // we want to use autolayout
    scale.translatesAutoresizingMaskIntoConstraints = false;

        // scale should be visible all the time
    scale.scaleVisibility = MKFeatureVisibilityVisible;// always visible
    
    // add it to the map
    [self.view addSubview:scale];
   
        // get the current safe area of the map
    UILayoutGuide * guide = self.view.safeAreaLayoutGuide;

        // Activate this array of constraints, which at the time removes leftovers if any
        [NSLayoutConstraint activateConstraints:
            @[
                // LEFT (I do not want a change if right-to-left language) margin with an offset to safe area
                // alternative would be ".leadingAnchor", which switches to the right margin, if right-to-left language is used
               //[scale.leftAnchor constraintEqualToAnchor: guide.centerXAnchor constant: -(scale.frame.size.width/2.0)],
          
                // right edge will be the middle of the map
                [scale.rightAnchor constraintEqualToAnchor: guide.centerXAnchor constant: (scale.frame.size.width/2.0)],

                // top margin is the top safe area
                [scale.bottomAnchor constraintEqualToAnchor: guide.bottomAnchor constant:-self.toolBar.frame.size.height],

                // view will be 20 points high
                [scale.heightAnchor constraintEqualToConstant: 50.0]
            ]
        ];
    
    [self.view bringSubviewToFront:scale];
}
Duff answered 10/2, 2022 at 13:1 Comment(0)
A
0

Simple modern approach

import MapKit

///Repaired map view, includes scale
class BetterMKMapView: MKMapView {
    ///Ideally, get the frame of the legal labels, else a reasonable guess
    var guessLabelsPosition: CGRect {
        for v in subviews {
            if String(describing: type(of: v)) == "MKAttributionLabel" {
                return v.frame }
        }
        return CGRect(x: 10, y: bounds.height - 100, width: 100, height: 20)
    }
    ///Why oh why don't Apple just do this?
    lazy var scale: MKScaleView = {
        let v = MKScaleView(mapView: self)
        v.scaleVisibility = .visible
        addSubview(v)
        return v
    }()
    let spaceBetweenScaleAndLegalLabels = 22.0 // your choice
    override func layoutSubviews() {
        super.layoutSubviews()
        // Position the scale nicely
        scale.frame = CGRect(
            origin: CGPoint(x: 8,
               y: guessLabelsPosition.minY - scale.bounds.height
                     - spaceBetweenScaleAndLegalLabels),
            size: scale.bounds.size)
        // Other repairs, eg, change absurd default compass position
    }
}

Regarding the "8" it's not so easy to get the minX of the overall legal labels, but it's close to 7 or 8.

There's never a case where you use MKMapView without subclassing it, so, that's it.

Audiphone answered 15/6 at 16:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.