How do I use Safe Area Layout programmatically?
Asked Answered
M

12

139

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.

Marocain answered 20/9, 2017 at 8:30 Comment(4)
No documented bool property about this according to: developer.apple.com/documentation/uikit/uiview/…Daria
How about view.safeAreaInsets? Did you try this?Anastassia
@KarthikeyanBose yes I did with no luck unfortunately.Marocain
works for me. What does code look likeBaldachin
C
210

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

Cochise answered 20/9, 2017 at 9:30 Comment(10)
Would it be possible to give this in objective-C as well? It looks like just what I needHeterosporous
@TomHammond Here is in Objective-C for you https://mcmap.net/q/162487/-objective-c-how-to-create-self-view-inside-safe-area-programmaticallyCochise
Should we really be checking via OS and not check via Device? By that I mean this code #available(iOS 11, *)Parasitology
@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.Cochise
@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.Cochise
Oh, so this would mean the SafeAreaLayout would be modular for lower end devices as long as the os is iOS 11?Parasitology
What does the line with "guide.bottomAnchor.constraintEqualToSystemSpacingBelow" do? I don't undrstand that one bit.Meal
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.Ebony
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!Brno
What worked for me was the answer below: self.view.safeAreaLayoutGuideBaldachin
A
107

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
  }
}
Ahwaz answered 11/11, 2017 at 8:57 Comment(3)
It's such a simple way to go and does the trick. Thanks for the idea.Dillondillow
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!Foehn
While this code is no longer relevant, the fact that there's a safeAreaLayoutGuide is good to remember. Thanks.Myrwyn
J
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

Jat answered 11/10, 2017 at 9:46 Comment(2)
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.Lelandleler
Yes! , Edited answer, you can use as your use case.Jat
M
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()
     }
}
Marocain answered 3/11, 2017 at 11:3 Comment(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) }Derron
E
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.

Establishmentarian answered 10/10, 2017 at 8:4 Comment(1)
Make sure you already add yourView to the superView. It is self.view In my code as an simple example.Establishmentarian
L
9

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
Lantz answered 26/6, 2019 at 11:49 Comment(0)
C
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]
Cutup answered 30/4, 2018 at 13:7 Comment(1)
This is the way to go if your view is not using AutoLayout.Bisutun
A
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

Azygous answered 24/1, 2018 at 20:53 Comment(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!Azygous
This works, I also like the visual format solutions! Thanks! But does this work for all ios versions?Missive
D
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
Darell answered 7/9, 2018 at 18:43 Comment(1)
it doesn't work (Swift 4.2, iOS 12). The result ignores status barBrno
L
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
    }

}
Lafferty answered 18/5, 2020 at 13:20 Comment(0)
J
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-|
  """
...
Jolinejoliotcurie answered 31/7, 2018 at 13:28 Comment(0)
C
-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

Clitoris answered 6/10, 2022 at 19:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.