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