137

Since I don't use storyboards to create my views, I was wondering if there's the "Use Safe Area Guides" option programmatically or something like that.

I've tried to anchor my views to

view.safeAreaLayoutGuide

but they keep overlapping the top notch in the iPhone X simulator.

craft
  • 2,017
  • 1
  • 21
  • 30
Phillip
  • 4,276
  • 7
  • 42
  • 74

12 Answers12

209

Here is sample code (Ref from: Safe Area Layout Guide):
If you create your constraints in code use the safeAreaLayoutGuide property of UIView to get the relevant layout anchors. Let’s recreate the above Interface Builder example in code to see how it looks:

Assuming we have the green view as a property in our view controller:

private let greenView = UIView()

We might have a function to set up the views and constraints called from viewDidLoad:

private func setupView() {
  greenView.translatesAutoresizingMaskIntoConstraints = false
  greenView.backgroundColor = .green
  view.addSubview(greenView)
}

Create the leading and trailing margin constraints as always using the layoutMarginsGuide of the root view:

 let margins = view.layoutMarginsGuide
 NSLayoutConstraint.activate([
    greenView.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
    greenView.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
 ])

Now, unless you are targeting iOS 11 and later, you will need to wrap the safe area layout guide constraints with #available and fall back to top and bottom layout guides for earlier iOS versions:

if #available(iOS 11, *) {
  let guide = view.safeAreaLayoutGuide
  NSLayoutConstraint.activate([
   greenView.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0),
   guide.bottomAnchor.constraintEqualToSystemSpacingBelow(greenView.bottomAnchor, multiplier: 1.0)
   ])
} else {
   let standardSpacing: CGFloat = 8.0
   NSLayoutConstraint.activate([
   greenView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: standardSpacing),
   bottomLayoutGuide.topAnchor.constraint(equalTo: greenView.bottomAnchor, constant: standardSpacing)
   ])
}

Result:

enter image description here

enter image description here


Here is Apple Developer Official Documentation for Safe Area Layout Guide


Safe Area is required to handle user interface design for iPhone-X. Here is basic guideline for How to design user interface for iPhone-X using Safe Area Layout

Elijah
  • 8,381
  • 2
  • 55
  • 49
Krunal
  • 77,632
  • 48
  • 245
  • 261
  • 2
    Would it be possible to give this in objective-C as well? It looks like just what I need – Tom Hammond Nov 20 '17 at 16:28
  • 6
    @TomHammond Here is in Objective-C for you https://stackoverflow.com/a/47076040/5638630 – Krunal Nov 20 '17 at 16:37
  • Should we really be checking via OS and not check via Device? By that I mean this code `#available(iOS 11, *)` – Zonily Jame Dec 11 '17 at 03:13
  • @ZonilyJame `#available(iOS 11, *)` (this code) condition executes it block for iOS 11 onward. In this answer: It separates 'SafeAreaLayout' with 'Top-Bottom Layout' as 'SafeAreaLayoutGuide' is added from iOS 11 (and Top-Bottom Layout Guide' is deprecated from the same version of iOS) and if your project is supporting iOS verisons below 11, then this conditional block is essential. – Krunal Dec 11 '17 at 04:47
  • 2
    @ZonilyJame Now about your query - SaFeAreaLayout is iOS specific framework (not device iPhoneX specific), It is replacing Top-Bottom Layout guide in iOS 11 hence we must use/set condition for iOS not for device. SafeAreaLayout takes care of designs for all types of devices (iPhone-X and others). You can ask me for more details, if you still have any query/confusion. – Krunal Dec 11 '17 at 04:47
  • Oh, so this would mean the `SafeAreaLayout` would be modular for lower end devices as long as the os is iOS 11? – Zonily Jame Dec 11 '17 at 04:53
  • What does the line with "guide.bottomAnchor.constraintEqualToSystemSpacingBelow" do? I don't undrstand that one bit. – Henning Mar 30 '18 at 15:32
  • It tells the layout system that the distance between the bottom of the safe area and the bottom of the target view should be equal to a value determined by the system on which it is running, since the 'safe area' will vary from one model of device to another - most notably on iPhone X. – Ash Apr 27 '18 at 07:28
  • 1
    why is it accepted as correct answer? I tried to setup a concrete position - the result is fully random - the control is placed at unpredictable position! – Vyachaslav Gerchicov Jan 31 '19 at 14:01
  • What worked for me was the answer below: `self.view.safeAreaLayoutGuide` –  Jun 24 '20 at 17:48
106

I'm actually using an extension for it and controlling if it is iOS 11 or not.

extension UIView {

  var safeTopAnchor: NSLayoutYAxisAnchor {
    if #available(iOS 11.0, *) {
      return safeAreaLayoutGuide.topAnchor
    }
    return topAnchor
  }

  var safeLeftAnchor: NSLayoutXAxisAnchor {
    if #available(iOS 11.0, *){
      return safeAreaLayoutGuide.leftAnchor
    }
    return leftAnchor
  }

  var safeRightAnchor: NSLayoutXAxisAnchor {
    if #available(iOS 11.0, *){
      return safeAreaLayoutGuide.rightAnchor
    }
    return rightAnchor
  }

  var safeBottomAnchor: NSLayoutYAxisAnchor {
    if #available(iOS 11.0, *) {
      return safeAreaLayoutGuide.bottomAnchor
    }
    return bottomAnchor
  }
}
Nat
  • 12,032
  • 9
  • 56
  • 103
Alper
  • 1,415
  • 2
  • 13
  • 20
  • It's such a simple way to go and does the trick. Thanks for the idea. – alper_k Jul 10 '18 at 08:29
  • 1
    This is such a simple yet nice way to do it! Note the use of `self.safeAreaLayoutGuide` instead of `self.layoutMarginsGuide`. The safe one used in this answer worked correctly for me to stay within the safe area! One thing I would suggest changing would be to use `leadingAnchor` and `trailingAnchor` instead of `leftAnchor` and `rightAnchor`. Bravo! – sudoExclaimationExclaimation May 17 '19 at 07:27
  • While this code is no longer relevant, the fact that there's a safeAreaLayoutGuide is good to remember. Thanks. – Dan Rosenstark Apr 17 '23 at 15:44
24

SafeAreaLayoutGuide is UIView property,

The top of the safeAreaLayoutGuide indicates the unobscured top edge of the view (e.g, not behind the status bar or navigation bar, if present). Similarly for the other edges.

Use safeAreaLayoutGuide for avoid our objects clipping/overlapping from rounded corners, navigation bars, tab bars, toolbars, and other ancestor views.

We can create safeAreaLayoutGuide object & set object constraints respectively.

Constraints for Portrait + Landscape is -

Portrait image

Landscape image

        self.edgesForExtendedLayout = []//Optional our as per your view ladder

        let newView = UIView()
        newView.backgroundColor = .red
        self.view.addSubview(newView)
        newView.translatesAutoresizingMaskIntoConstraints = false
        if #available(iOS 11.0, *) {
            let guide = self.view.safeAreaLayoutGuide
            newView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
            newView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
            newView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
            newView.heightAnchor.constraint(equalToConstant: 100).isActive = true

        }
        else {
            NSLayoutConstraint(item: newView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
            NSLayoutConstraint(item: newView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0).isActive = true
            NSLayoutConstraint(item: newView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: 0).isActive = true

            newView.heightAnchor.constraint(equalToConstant: 100).isActive = true
        }

UILayoutGuide

safeAreaLayoutGuide

Jack
  • 13,571
  • 6
  • 76
  • 98
  • 3
    Never ever do setup constraints in the `viewDidAppear`, unless you are absolutely know what you are doing. `viewDidAppear` is called multiple times and so, your constraints will be duplicated every time it is called. – Yevhen Dubinin Nov 10 '17 at 02:29
  • Yes! , Edited answer, you can use as your use case. – Jack Nov 10 '17 at 02:35
18

For those of you who use SnapKit, just like me, the solution is anchoring your constraints to view.safeAreaLayoutGuide like so:

yourView.snp.makeConstraints { (make) in
    if #available(iOS 11.0, *) {
        //Bottom guide
        make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottomMargin)
        //Top guide
        make.top.equalTo(view.safeAreaLayoutGuide.snp.topMargin)
        //Leading guide
        make.leading.equalTo(view.safeAreaLayoutGuide.snp.leadingMargin)
        //Trailing guide
        make.trailing.equalTo(view.safeAreaLayoutGuide.snp.trailingMargin)

     } else {
        make.edges.equalToSuperview()
     }
}
Phillip
  • 4,276
  • 7
  • 42
  • 74
  • 1
    Great answer. To make it a bit more compact you could say: if #available(iOS 11.0, *) { make.edges.equalTo(view.safeAreaLayoutGuide.snp.margins) } – Don Miguel Feb 20 '20 at 13:05
11

I'm using this instead of add leading and trailing margin constraints to the layoutMarginsGuide:

UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
yourView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
                                           [safe.trailingAnchor constraintEqualToAnchor:yourView.trailingAnchor],
                                           [yourView.leadingAnchor constraintEqualToAnchor:safe.leadingAnchor],
                                           [yourView.topAnchor constraintEqualToAnchor:safe.topAnchor],
                                           [safe.bottomAnchor constraintEqualToAnchor:yourView.bottomAnchor]
                                          ]];

Please also check the option for lower version of ios 11 from Krunal's answer.

Tony TRAN
  • 2,118
  • 1
  • 15
  • 16
8

Swift 4.2 and 5.0. Suppose you want to add Leading, Trailing, Top and Bottom constraints on viewBg. So, you can use the below code.

let guide = self.view.safeAreaLayoutGuide
viewBg.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
viewBg.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
viewBg.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
viewBg.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
7

Use UIWindow or UIView's safeAreaInsets .bottom .top .left .right

// #available(iOS 11.0, *)
// height - UIApplication.shared.keyWindow!.safeAreaInsets.bottom

// On iPhoneX
// UIApplication.shared.keyWindow!.safeAreaInsets.top =  44
// UIApplication.shared.keyWindow!.safeAreaInsets.bottom = 34

// Other devices
// UIApplication.shared.keyWindow!.safeAreaInsets.top =  0
// UIApplication.shared.keyWindow!.safeAreaInsets.bottom = 0

// example
let window = UIApplication.shared.keyWindow!
let viewWidth = window.frame.size.width
let viewHeight = window.frame.size.height - window.safeAreaInsets.bottom
let viewFrame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight)
let aView = UIView(frame: viewFrame)
aView.backgroundColor = .red
view.addSubview(aView)
aView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Warif Akhand Rishi
  • 23,920
  • 8
  • 80
  • 107
4

Use constraints with visual format and you get respect for the safe area for free.

class ViewController: UIViewController {

    var greenView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        greenView.backgroundColor = .green
        view.addSubview(greenView)
    }
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        greenView.translatesAutoresizingMaskIntoConstraints = false
        let views : [String:Any] = ["greenView":greenView]
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[greenView]-|", options: [], metrics: nil, views: views))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[greenView]-|", options: [], metrics: nil, views: views))
    }
}

result

AtomicBoolean
  • 1,070
  • 13
  • 19
  • 2
    Please provide a comment when down-voting, so we can all learn if there is a case where this technique doesn't apply. Thanks! – AtomicBoolean Apr 04 '18 at 12:24
  • This works, I also like the visual format solutions! Thanks! But does this work for all ios versions? – PaFi Apr 19 '18 at 21:14
4

Safe area extension For Objective-C

@implementation UIView (SafeArea)

- (NSLayoutAnchor *)safeTopAnchor{

    if (@available(iOS 11.0, *)){
        return self.safeAreaLayoutGuide.topAnchor;
    } else {
        return self.topAnchor;
    }

}


- (NSLayoutAnchor *)safeBottomAnchor{

    if (@available(iOS 11.0, *)) {
        return self.safeAreaLayoutGuide.bottomAnchor;
    } else {
        return self.bottomAnchor;
    }

}

@end
black_pearl
  • 2,549
  • 1
  • 23
  • 36
2

This extension helps you to constraint a UIVIew to its superview and superview+safeArea:

extension UIView {

    ///Constraints a view to its superview
    func constraintToSuperView() {
        guard let superview = superview else { return }
        translatesAutoresizingMaskIntoConstraints = false

        topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
        leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true
        bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
        rightAnchor.constraint(equalTo: superview.rightAnchor).isActive = true
    }

    ///Constraints a view to its superview safe area
    func constraintToSafeArea() {
        guard let superview = superview else { return }
        translatesAutoresizingMaskIntoConstraints = false

        topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor).isActive = true
        leftAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.leftAnchor).isActive = true
        bottomAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.bottomAnchor).isActive = true
        rightAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.rightAnchor).isActive = true
    }

}
Reimond Hill
  • 4,278
  • 40
  • 52
1

You can use view.safeAreaInsets as explained here https://www.raywenderlich.com/174078/auto-layout-visual-format-language-tutorial-2

code sample (taken from raywenderlich.com):

override func viewSafeAreaInsetsDidChange() {
  super.viewSafeAreaInsetsDidChange()

  if !allConstraints.isEmpty {
    NSLayoutConstraint.deactivate(allConstraints)
    allConstraints.removeAll()
  }

  let newInsets = view.safeAreaInsets
  let leftMargin = newInsets.left > 0 ? newInsets.left : Metrics.padding
  let rightMargin = newInsets.right > 0 ? newInsets.right : Metrics.padding
  let topMargin = newInsets.top > 0 ? newInsets.top : Metrics.padding
  let bottomMargin = newInsets.bottom > 0 ? newInsets.bottom : Metrics.padding

  let metrics = [
    "horizontalPadding": Metrics.padding,
    "iconImageViewWidth": Metrics.iconImageViewWidth,
    "topMargin": topMargin,
    "bottomMargin": bottomMargin,
    "leftMargin": leftMargin,
    "rightMargin": rightMargin]
}


let views: [String: Any] = [
  "iconImageView": iconImageView,
  "appNameLabel": appNameLabel,
  "skipButton": skipButton,
  "appImageView": appImageView,
  "welcomeLabel": welcomeLabel,
  "summaryLabel": summaryLabel,
  "pageControl": pageControl]

let iconVerticalConstraints = NSLayoutConstraint.constraints(
  withVisualFormat: "V:|-topMargin-[iconImageView(30)]",
  metrics: metrics,
  views: views)
allConstraints += iconVerticalConstraints

let topRowHorizontalFormat = """
  H:|-leftMargin-[iconImageView(iconImageViewWidth)]-[appNameLabel]-[skipButton]-rightMargin-|
  """
...
Serg
  • 341
  • 3
  • 4
-1
var someVeiw = UIView()
    func webView(_view: View, didFinish navigation: WKNavigation!) {
        self.someVeiw.frame = view.safeAreaLayoutGuide.layoutFrame
    }

iOS 10 + comes with a safeAreaLayoutGuide which is detected at runtime. Frame on accepts a CGRect , hence use the layoutFrame

vision.io
  • 19
  • 2