Erik Magnusson
Erik Magnusson

Reputation: 73

MKMapView's scale is not shown

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.

Upvotes: 7

Views: 7377

Answers (4)

Fattie
Fattie

Reputation: 12582

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.

Upvotes: 0

goelectric
goelectric

Reputation: 386

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];
}

Upvotes: 0

Hardy_Germany
Hardy_Germany

Reputation: 1279

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

Upvotes: 8

Kosuke Ogawa
Kosuke Ogawa

Reputation: 7451

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)

Upvotes: 11

Related Questions