Reputation: 783
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
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
Upvotes: 0
Reputation: 12582
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.)
///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 ...
/// 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)
}
}
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
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
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
Reputation: 7451
For iOS 11 and above, use MKCompassButton
.
let compass = MKCompassButton(mapView: mapView)
Upvotes: 2