mistakeNot
mistakeNot

Reputation: 783

How to reposition compass of MKMapView?

I want to move the MKMapView compass. I wanted to get a reference for it by something like this:

let compassView = mapView.subviews.filter {$0 is NSClassFromString("MKCompassView")}

However the compiler complains " Use of undeclared type 'NSClassFromString' ". How can I fix this code?

Upvotes: 4

Views: 3215

Answers (5)

app4g
app4g

Reputation: 840

I'm Targeting iOS12+. I wanted to reposition the default compass that is supplied with the MKTrackingButton but found out I can't. This is what I ended up with.

  // https://stackoverflow.com/a/48712222/14414215
  // Hide the default compass that comes with MkTrackingButton
  vcTrainMapView.showsCompass = false
  
  // Create the Compass
  gpsCompassButton = MKCompassButton(mapView:vcTrainMapView)
  gpsCompassButton.compassVisibility = .visible
  
  // Add Compass to MapView
  vcTrainMapView.addSubview(gpsCompassButton)
  
  // Add Constrains
  gpsCompassButton.translatesAutoresizingMaskIntoConstraints = false
  gpsCompassButton.topAnchor.constraint(equalTo: gpsEnabledIcon.bottomAnchor, constant: 10).isActive = true
  gpsCompassButton.trailingAnchor.constraint(equalTo: vcTrainMapView.trailingAnchor, constant: -5).isActive = true

enter image description here

Upvotes: 0

Fattie
Fattie

Reputation: 12582

The current answer as of about iOS 15 / 2024

The old "move the MKCompassView" method does not work

Apple have changed the view structure. If you try moving the frame of an MKCompassView you'll see it does nothing. Don't waste your time. (And just FWIW in theory you're using a hidden class so the app could be denied by Apple.)

You simply do this

///MapView with rose in different position
class BetterMKMapView: MKMapView {
    
    lazy var comp: MKCompassButton = {
        let v = MKCompassButton(mapView: self)
        v.isUserInteractionEnabled = true
        addSubview(v)
        return v
    }()
    
    override func layoutSubviews() {
        super.layoutSubviews()
        showsCompass = false
        comp.frame = CGRect(
            origin: CGPoint(x: 666, y: 666),
            size: comp.bounds.size)
    }
}

Everything works absolutely identically to the standard compass, in every way. Appearance, disappearance, tapping, animations, etc.

Note that "showsCompass" is (surprise) badly named, it should be named something like includeDefaultCompass. "showsCompass" has NO effect on any compasses you include.

Note that compassVisibility = .adaptive is the default, but include it if you wish.

Note that during experimentation, isUserInteractionEnabled sometimes came out as false. I could never quite make this reproducible but I left in isUserInteractionEnabled = true as a safety measure. (If you can ever repro the issue, give a yell.)

FYI, presently (2024) the default compass is inset 64 from right and 49 from top, if you want to match some of those values. Hence ...

If you want it bottom-right, copy-paste

/// Bottom-right rose; match (current) Apple feel
class DesignishMKMapView: MKMapView {
    
    lazy var comp: MKCompassButton = {
        let v = MKCompassButton(mapView: self)
        v.isUserInteractionEnabled = true
        addSubview(v)
        return v
    }()
    
    override func layoutSubviews() {
        super.layoutSubviews()
        // FYI, for 2024, apple design is insets 49 top 64 right
        showsCompass = false
        comp.frame = CGRect(
            origin: CGPoint(x: bounds.width - 64 - comp.bounds.size.width,
                            y: bounds.height - 49 - comp.bounds.size.height),
            size: comp.bounds.size)
    }
}

If you truly want to literally move the supplied compass, perhaps experimentally

As of writing it's easy,

///Hah-hah Apple
class WeirdMKMapView: MKMapView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if let compass = first(ofType: "MKCompassView") {
            compass.superview!.frame = CGRect(
                origin: CGPoint(x: 200, y: 200),
                size: compass.superview!.bounds.size)
        }
    }
}

but this can and will change any time Apple happens to be fooling with it.

Just FTR re the experimental one, if you don't have the utility function here it is

extension UIView {
    var subviewsRecursive: [UIView] {
        return subviews + subviews.flatMap { $0.subviewsRecursive }
    }
    ///Find by type by string.
    func first(ofType: String) -> UIView? {
        return subviewsRecursive.first(where:
                         {String(describing: $0).contains(ofType)})
    }
}

Upvotes: 2

Kaiser Abliz
Kaiser Abliz

Reputation: 161

This is my solution for repositioning the compass view by subclassing MKMapView.
The code is Swift 5.0 tested on iOS10 and above.
Note: When you test this on iOS10 devices you have to rotate the map in order to make compass visible.

import MapKit
class MapView: MKMapView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if #available(iOS 10.0, *) {
            self.showsCompass = true //*** - You have to set this true here, it does not work if you set it on storyboards or in a View Controller - ***
            if let compassButton = (self.subviews.filter { String(describing:$0).contains("MKCompassView") }.first) {
                compassButton.frame = CGRect(x: 20, y: 40, width: 36, height: 36)
            }
        } else {
            let compassButton = MKCompassButton(mapView:self)
            compassButton.frame.origin = CGPoint(x: 20, y: 40)
            compassButton.compassVisibility = .visible
            self.addSubview(compassButton)
        }
    }
}

Upvotes: 0

mugx
mugx

Reputation: 10105

iOS 11

you should use MKCompassButton, doc explaining the new stuff: WWDC 2017 new MapKit presentation.

let compassButton = MKCompassButton(mapView:mapView)
compassButton.frame.origin = CGPoint(x: 20, y: 20)
compassButton.compassVisibility = .visible
view.addSubview(compassButton)

iOS < 11

You might try to use String(describing:), something like:

if let compassButton = (mapView.subviews.filter { String(describing:$0).contains("MKCompassView") }.first) {
   print(compassButton)
}

Upvotes: 7

Kosuke Ogawa
Kosuke Ogawa

Reputation: 7451

For iOS 11 and above, use MKCompassButton.

let compass = MKCompassButton(mapView: mapView)

Upvotes: 2

Related Questions