How to reposition MKMapView's legal label in iOS 11?

Hello, everyone.


I'm in a bit of a hurdle. I am trying to place a UIView on top of a MKMapView, but the view should have a blur effect, so that the map shines through the blur a bit. However, doing this results in the UIView blocking the legal label of the map, and so my app will be rejected from the App Store.

So, my question is, how do i move the legal label? I have tried creating a subclass for MKMapView, and then repositioning the label from there, but this does not seem to work with iOS 11. Here's that piece of code:


class MapViewSubclass: MKMapView {
    override func layoutSubviews() {
        super.layoutSubviews()
        let legalLabel = self.subviews[1]
        legalLabel.center = CGPoint(x: legalLabel.center.x, y: legalLabel.center.y - 80)
    }
}


What I am trying to achieve is somthing like this: https://developer.apple.com/ios/human-interface-guidelines/views/maps/

On the left-most iPhone X you see that the map exends behind the bar in the bottom, but the legal label is still visible on top of the bottom bar.


I am doing this in Swift 4, Xcode 9, and it is an iOS 11 app.


Thnak you 🙂

Accepted Reply

1. Constrain the bottom of your map view to the bottom of its superview (the view controller's view).

2. Constrain the bottom of your visual effect view to the bottom of its superview (the view controller's view).

3. Set the bottom safe area to include the visual effect view's height. Like this:


override func viewDidLoad() {
    super.viewDidLoad()
    additionalSafeAreaInsets = UIEdgeInsetsMake(0.0, 0.0, bottomView.bounds.height, 0.0)
   // If you need support for iOS 10, you should use bottomLayoutGuide instead.
}


Here's a screenshot of what this looks like (remove the space in the URL):


https ://postimg.org/image/8***2b52z/

Replies

1. Constrain the bottom of your map view to the bottom of its superview (the view controller's view).

2. Constrain the bottom of your visual effect view to the bottom of its superview (the view controller's view).

3. Set the bottom safe area to include the visual effect view's height. Like this:


override func viewDidLoad() {
    super.viewDidLoad()
    additionalSafeAreaInsets = UIEdgeInsetsMake(0.0, 0.0, bottomView.bounds.height, 0.0)
   // If you need support for iOS 10, you should use bottomLayoutGuide instead.
}


Here's a screenshot of what this looks like (remove the space in the URL):


https ://postimg.org/image/8***2b52z/

Here's what I've tried to do, but it doesn't seem to be working. What am I doing wrong?


import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
    let mapView: MKMapView = {
        let map = MKMapView()
        map.translatesAutoresizingMaskIntoConstraints = false
        return map
    }()

    let blurredView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(mapView)
        view.addSubview(blurredView)
    
        let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.frame = blurredView.bounds
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        blurredView.addSubview(blurEffectView)
    
        setupConstraints()
    
        additionalSafeAreaInsets = UIEdgeInsetsMake(0.0, 0.0, blurredView.bounds.height, 0.0)
    }

    var mapViewTopAnchor: NSLayoutConstraint!
    var mapViewLeftAnchor: NSLayoutConstraint!
    var mapViewRightAnchor: NSLayoutConstraint!
    var mapViewBottomAnchor: NSLayoutConstraint!

    var blurredViewHeightAnchor: NSLayoutConstraint!
    var blurredViewLeftAnchor: NSLayoutConstraint!
    var blurredViewRightAnchor: NSLayoutConstraint!
    var blurredViewBottomAnchor: NSLayoutConstraint!

    func setupConstraints() {
        mapViewTopAnchor = mapView.topAnchor.constraint(equalTo: view.topAnchor)
        mapViewLeftAnchor = mapView.leftAnchor.constraint(equalTo: view.leftAnchor)
        mapViewRightAnchor = mapView.rightAnchor.constraint(equalTo: view.rightAnchor)
        mapViewBottomAnchor = mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        NSLayoutConstraint.activate([mapViewTopAnchor, mapViewLeftAnchor, mapViewRightAnchor, mapViewBottomAnchor])
    
        blurredViewHeightAnchor = blurredView.heightAnchor.constraint(equalToConstant: 100)
        blurredViewLeftAnchor = blurredView.leftAnchor.constraint(equalTo: view.leftAnchor)
        blurredViewRightAnchor = blurredView.rightAnchor.constraint(equalTo: view.rightAnchor)
        blurredViewBottomAnchor = blurredView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        NSLayoutConstraint.activate([blurredViewHeightAnchor, blurredViewLeftAnchor, blurredViewRightAnchor, blurredViewBottomAnchor])
    }
}



What I get from the above code, is this: https ://postimg.org/image/jkm2fsh7v/


The label isn't moved. I guess this has something to do with the constraints?

When you set additionalSafeAreaInsets, the height of your blur view is 0.0. You need to force the superview to adjust its layout before you set additionalSafeAreaInsets. Like this (add line 14):


override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(mapView)
    view.addSubview(blurredView)
  
    let blurEffect = UIBlurEffect(style: .light)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    blurEffectView.frame = blurredView.bounds
    blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    blurredView.addSubview(blurEffectView)
  
    setupConstraints()
  
    view.layoutIfNeeded()
    additionalSafeAreaInsets = UIEdgeInsetsMake(0.0, 0.0, blurredView.bounds.height, 0.0)
}

I just did that, but it still does not seem to work. The label is still placed behind the blurred view. I placed it just like you did there:


override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(mapView)
        view.addSubview(blurredView)
      
        let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.frame = blurredView.bounds
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        blurredView.addSubview(blurEffectView)
      
        setupConstraints()
        view.layoutIfNeeded()
      
        additionalSafeAreaInsets = UIEdgeInsetsMake(0.0, 0.0, blurEffectView.bounds.height, 0.0)
    }


I also tried to set a hard coded value in UIEdgeInsetsMake instead of blurEffectView.bounds.height, but still the label doesn't move. That must mean that the additionalSafeAreaInsets aren't being applied or something, right?

I'm not sure what could be wrong. I pasted the following in a new project and it works for me:


import UIKit
import MapKit


class ViewController: UIViewController, MKMapViewDelegate {
    let mapView: MKMapView = {
        let map = MKMapView()
        map.translatesAutoresizingMaskIntoConstraints = false
        return map
    }()

    let blurredView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(mapView)
        view.addSubview(blurredView)
     
        let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.frame = blurredView.bounds
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        blurredView.addSubview(blurEffectView)
     
        setupConstraints()
        view.layoutIfNeeded()
     
        print("height: \(blurredView.bounds.height)")
        additionalSafeAreaInsets = UIEdgeInsetsMake(0.0, 0.0, blurredView.bounds.height, 0.0)
        print("additionalSafeAreaInsets: \(additionalSafeAreaInsets)")
    }

    var mapViewTopAnchor: NSLayoutConstraint!
    var mapViewLeftAnchor: NSLayoutConstraint!
    var mapViewRightAnchor: NSLayoutConstraint!
    var mapViewBottomAnchor: NSLayoutConstraint!

    var blurredViewHeightAnchor: NSLayoutConstraint!
    var blurredViewLeftAnchor: NSLayoutConstraint!
    var blurredViewRightAnchor: NSLayoutConstraint!
    var blurredViewBottomAnchor: NSLayoutConstraint!

    func setupConstraints() {
        mapViewTopAnchor = mapView.topAnchor.constraint(equalTo: view.topAnchor)
        mapViewLeftAnchor = mapView.leftAnchor.constraint(equalTo: view.leftAnchor)
        mapViewRightAnchor = mapView.rightAnchor.constraint(equalTo: view.rightAnchor)
        mapViewBottomAnchor = mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        NSLayoutConstraint.activate([mapViewTopAnchor, mapViewLeftAnchor, mapViewRightAnchor, mapViewBottomAnchor])
     
        blurredViewHeightAnchor = blurredView.heightAnchor.constraint(equalToConstant: 100)
        blurredViewLeftAnchor = blurredView.leftAnchor.constraint(equalTo: view.leftAnchor)
        blurredViewRightAnchor = blurredView.rightAnchor.constraint(equalTo: view.rightAnchor)
        blurredViewBottomAnchor = blurredView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        NSLayoutConstraint.activate([blurredViewHeightAnchor, blurredViewLeftAnchor, blurredViewRightAnchor, blurredViewBottomAnchor])
    }
}


Screenshot: https: //postimg.org/image/1ibzeggam3/


The output from the print statements:


height: 100.0
additionalSafeAreaInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 100.0, right: 0.0)


Check what output you get.

I literally copied the above code, and replaced everything in my previous ViewController. Still the same result, no change in the label. Could you maybe upload your project that this works in, so that i can look for differences?


Also, i checked the output. I get exactly the same output as you. Really weird...

The legal label is there for a reason, tampering or moving that could be a problem in the real world, which could turn into a problem for Apple or you if something goes wrong on the end user end of things like driving into a lake on the next right turn in two meters before tim hortons on the left after good will...

I agree, but I'm not changing anything about the legal button itself, just moving it so that users can actually see it and click it. Also, if Apple has done it here: https://developer.apple.com/ios/human-interface-guidelines/views/maps/, in their own Human Interface Guidelines for iOS, then I think it is allowed, and completely okay. Besides, the map is not there in my app to guide the user.

I got it working.


There seems to be an issue with the simulator in XCode 9.1 (I think I tried it in XCode 9 too), where the legal label doesn't move. I tried building the app on my iPhone, and it worked like a charm. Your initial suggestion worked great. Maybe the issue with the simulator is related to the OpenGL problems that these versions of XCode has, who knows.


Thank you so much for your help! 🙂

Apple hasn't done anything in their HIG, what you're seeing are two different maps at two different zoom levels, if the user wants to touch the legal button they will move the map or just touch it...

I'm only talking about the left-most iPhone X. You see the legal label isn't covered by the bottom bar, though the map extends behind, and shines through. Besides, the safe insets are there to make sure that content like the legal-label doesn't get covered up behind overlapping views, if I'm not mistaken. Essentially I'm just telling the mapview what part of the screen that will be visible, and it adjusts the legal label to be within that. I'm not sure what you mean by: "if the user wants to touch the legal button they will move the map ...", becuase it is my understanding, that the legal button should be visible at all times.


Please correct me if I'm wrong

You're welcome! 😉 You might want to file a bug report if you are having a problem with Xcode 9.1: https://developer.apple.com/bug-reporting/

Yes it is not covered because the map is zoomed out and not obstructed by land mass. Instead of trying to move the label, trying changing the color of the label using the appearnce protocols vs trying to move the label, then Apple makes changes to the maps API in the future for the label position then you end up with buggy code.

The OP is trying to keep the legal label from being hidden under the blur view outside the map view, not obscured by map elements inside the map view.


I personally think the safe area adjustment is more future proof than trying to manually move or change the label. additionalSafeAreaInsets is a public API, and the map view should always respect the safe area. For example, if Apple decides to change the label to an image button or add more elements at the bottom, the UI should still look OK.

I did not have this issue with Xcode 9.0, but I am now seeing it with Xcode 9.0.1. I was hoping the Xcode 9.1 beta would fix it... downloading beta 2 now to see.


EDIT: This is still an issue with Xcode 9.1 beta 2. It affects the compass that shows in the top right when rotating the map as well (I set "additionalSafeAreaInsets" on the top too).


I don't see this issue on a real device running iOS 11, though.