After iOS 7, Apple introduced two properties topLayoutGuide and bottomLayoutGuide to UIViewController to describe the highest position that does not want to be obscured by the transparent status bar or navigation bar (Status bar, Navigation Bar, Toolbar, TAB Bar, etc.) The value of this property is a length property (toplayoutguide.length). This value can be determined by the current ViewController or NavigationController or TabbarController.

  • A separate ViewController, not contained in any other ViewController. If the status bar is visible, topLayoutGuide represents the bottom of the status bar, otherwise the top edge of the ViewController.
  • Viewcontrollers included with other viewControllers do not determine what this property means. Instead, the container ViewController determines what this property means:
    • If the Navigation Bar is visible, topLayoutGuide indicates the bottom of the Navigation Bar.
    • If the status bar is visible, topLayoutGuide indicates the bottom of the status bar.
    • If neither is visible, the upper edge of the ViewController. This part is easier to understand, but it’s the bottom of any bar at the top of the screen that blocks content.

IOS 11 began to deprecate both properties and introduced the concept of Safe Area. Apple’s advice: Don’t place Control outside of the Safe Area

    // These objects may be used as layout items in the NSLayoutConstraint API
    @available(iOS, introduced: 7.0, deprecated: 11.0)
    open var topLayoutGuide: UILayoutSupport { get }

    @available(iOS, introduced: 7.0, deprecated: 11.0)
    open var bottomLayoutGuide: UILayoutSupport { get }
Copy the code

Today, take a look at the new API introduced in iOS 11.

Safe area in UIView

The topLayoutGuide and bottonLayoutGuide properties of UIViewController in iOS 11 have been replaced by the Safe Area properties of UIView.

    @available(iOS 11.0, *)
    open var safeAreaInsets: UIEdgeInsets { get }

    @available(iOS 11.0, *)
    open func safeAreaInsetsDidChange(a)
Copy the code

safeAreaInsets

This property represents the spacing relative to the four edges of the screen, not just the top but also the bottom. That doesn’t sound like much, so let’s take a look at what this looks like on the iPhone X and iPhone 8.

Nothing, just create a new project and drag an orange View in the UIViewController in main. storyboard and set the constraint to:

Print in viewDidLoad of viewController.swift

    override func viewDidLoad(a) {
        super.viewDidLoad()
        print(view.safeAreaInsets)
    }
// For both iPhone 8 and iPhone X, the output is
// UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
Copy the code


As you can see from this comparison, the iPhone X has both top and bottom Safe areas and left and right Safe areas.

** Let’s look at this example: ** Drag two custom views with a Label that displays a lot of words. Then set the constraints on the two views to:

let view1 = MyView(a)let view2 = MyView()
view.addSubview(view1)
view.addSubview(view2)
let screenW = UIScreen.main.bounds.size.width
let screenH = UIScreen.main.bounds.size.height

view1.frame = CGRect(
	x: 0,
	y: 0,
	width:screenW,
	height: 200)

view2.frame = CGRect(
	x: 0,
	y: screenH - 200,
	width:screenW,
	height: 200)
Copy the code

As you can see, the subview is blocked by the bangs at the top and the home indicator at the bottom. We can use frame layout or Auto Layout to optimize this place:

let insets = UIApplication.shared.delegate?.window??.safeAreaInsets ?? UIEdgeInsets.zero
            
view1.frame = CGRect(
	x: insets.left,
	y: insets.top,
	width:view.bounds.width - insets.left - insets.right,
	height: 200)

view2.frame = CGRect(
	x: insets.left,
	y: screenH - insets.bottom - 200,
	width:view.bounds.width - insets.left - insets.right,
	height: 200)
Copy the code

There is also a better way to change the layout of the Label directly in the custom View:

override func layoutSubviews(a) {
	super.layoutSubviews()
	if #available(iOS 11.0, *) {
		label.frame = safeAreaLayoutGuide.layoutFrame
	}
}
Copy the code

So you can use safe Areas not just in ViewController.

Safe areas in UIViewController

UIViewController has a new property in iOS 11

@available(iOS 11.0, *)
open var additionalSafeAreaInsets: UIEdgeInsets
Copy the code

When the view Controller’s child view overrides the view of the embedded child View Controller. For example, additionalSafeAreaInsets exist in UINavigationController and UITabbarController, as bar always is translucent

The label layout on the custom View is compatible with the Safe Area.

// UIView
@available(iOS 11.0, *)
open func safeAreaInsetsDidChange(a)

//UIViewController
@available(iOS 11.0, *)
open func viewSafeAreaInsetsDidChange(a)
Copy the code

These two methods are called when the safe Area insets of UIView and UIViewController change, and you can override this method if you need to do something. It’s kind of like KVO.

Simulate the safe Area on the iPhone X

Additional Safe Area Insets can also be used to test whether your app supports the iPhone X. This is useful when you don’t have the iPhone X and can’t easily use the emulator.

/ / vertical screen additionalSafeAreaInsets. Top = 24.0 additionalSafeAreaInsets. Bottom = 34.0 / / vertical screen, The status bar is hidden additionalSafeAreaInsets. Top = 44.0 additionalSafeAreaInsets. Bottom = 34.0 / / landscape AdditionalSafeAreaInsets. Left = 44.0 additionalSafeAreaInsets. Bottom = 21.0 additionalSafeAreaInsets. Right = 44.0Copy the code

Safe area in UIScrollView

I’m going to put a label on the Scroll view. Set the scroll constraint to:

        scrollView.snp.makeConstraints { (make) in
            make.edges.equalToSuperview()
        }
Copy the code

7 introduced in iOS UIViewController automaticallyAdjustsScrollViewInsets attributes in iOS11 abandoned away. Instead, the UIScrollView contentInsetAdjustmentBehavior

@available(iOS 11.0*),public enum UIScrollViewContentInsetAdjustmentBehavior : Int {    
    case automatic          //default value
    case scrollableAxes
    case never
    case always
}

@available(iOS 11.0, *)
open var contentInsetAdjustmentBehavior: UIScrollViewContentInsetAdjustmentBehavior

Copy the code

Content Insets Adjustment Behavior

Never Does not make adjustments.

ScrollableAxes Content Insets will only be adjusted for the scrolling direction of scrollView.

Always Content Insets are adjusted for both directions.

Automatic is the default. “Always” is the same as “always” when the following conditions are met

  • Can roll horizontally, can’t roll vertically
  • Scroll View is the first view of the current View Controller
  • This controller is managed by a Navigation Controller or TAB Bar Controller
  • automaticallyAdjustsScrollViewInsetsTo true

In other cases automoatc is the same as scrollableAxes

Adjusted Content Insets

AdjustedContentInset added to UIScrollView in iOS 11

@available(iOS 11.0, *)
open var adjustedContentInset: UIEdgeInsets { get }
Copy the code

What’s the difference between adjustedContentInset and contentInset?

Add a ScrollView to the view Controller with navigation and TAB bar and print two values:

//iOS 10
//contentInset = UIEdgeInsets(top: 64.0, left: 0.0, bottom: 49.0, right: 0.0)
//iOS 11
//contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
//adjustedContentInset = UIEdgeInsets(top: 64.0, left: 0.0, bottom: 49.0, right: 0.0)
Copy the code

Then set:

// Add 10 to all four directions of the Scroll View
scrollView.contentInset = UIEdgeInsets(top: 10.left: 10, bottom: 10.right: 10)
Copy the code

Print:

//iOS 10
//contentInset = UIEdgeInsets(top: 74.0, left: 10.0, bottom: 59.0, right: 10.0)
//iOS 11
//contentInset = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
//adjustedContentInset = uiedGeInSet (top: 74.0, Left: 10.0, Right: 10.0)
Copy the code

Thus, in iOS 11, the actual Scroll View ContentInset can be obtained through adjustedContentInset. That means if you want to adapt to iOS 10. This part of the logic is different.

The system also provides two methods to listen for this property to change

//UIScrollView
@available(iOS 11.0, *)
open func adjustedContentInsetDidChange(a)

//UIScrollViewDelegate
@available(iOS 11.0*),optional public func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView)
Copy the code

Safe area in UITableView

Now let’s look at the safe Area in UITableView. So let’s just add a TableView with a custom header and a custom cell. Set the border to the border of self.view. That is

tableView.snp.makeConstraints { (make) in
	make.edges.equalToSuperview()
}
Copy the code

or

tableView.frame = view.bounds
Copy the code

A custom header has an Lable attached to it, and a custom cell has a label attached to it. If you landscape the screen, you will notice that the layout of the cell and header automatically leaves a distance beyond the safe area. The cell is still the same size, but the contnt View of the cell has a corresponding distance. This is what UITableView’s new property management is:

@available(iOS 11.0, *)
open var insetsContentViewsToSafeArea: Bool
Copy the code

InsetsContentViewsToSafeArea default value is true, it is set to no after:

The size of the content view of the footer and cell is the same as the size of the cell. In iOS 11, you don’t need to change the layout of the header/footer/cell, and the safe area is automatically adapted

Note that Xcode 9 uses IB to drag out tableViews with safe Area borders by default. So when you actually run your TableView, it’s inside the Safe Area.

Safe area in UICollectionView

Let’s make the same collection View and see what happens in the collection View:

This is a used UICollectionViewFlowLayout collection view. The slide direction is vertical. The cell is transparent and the content view of the cell is white. These are all the same as the table View above. Header (UICollectionReusableView) has no concept of a Content view, so it gives itself a red background.

As you can see from the screenshot, the Collection View does not add safe Area spacing to the Header cell footer by default. The only way to adjust the layout to fit is to associate the header/ footer/cell child view with its Safe Area. It’s the same as dragging a table View in IB.

Now let’s try rearranging the layout to look more like the Collection View:

It can be seen from the screenshot that under the horizontal screen, the left and right cells are blocked by Liu Hai. In this case, we can solve this problem by modifying the Section Insets to fit the Safe Area. But another 11 of iOS, UICollectionViewFlowLayout provides a new attribute sectionInsetReference’ll do it for you.

@available(iOS 11.0*),public enum UICollectionViewFlowLayoutSectionInsetReference : Int {

    case fromContentInset

    case fromSafeArea

    case fromLayoutMargins
}

/// The reference boundary that the section insets will be defined as relative to. Defaults to `.fromContentInset`.
/// NOTE: Content inset will always be respected at a minimum. For example, if the sectionInsetReference equals `.fromSafeArea`, but the adjusted content inset is greater that the combination of the safe area and section insets, then section content will be aligned with the content inset instead.
@available(iOS 11.0, *)
open var sectionInsetReference: UICollectionViewFlowLayoutSectionInsetReference
Copy the code

As you can see, the default is.fromContentinset and we’ll modify it separately to see what that looks like.

So in this case the section Content insets are equal to the original size plus the safe Area insets.

Just like with.fromLayoutMargins, the Layout margins of the Colection View will be added to the Section Content Insets.

Safe Area in IB

The previous examples were all about what to do with a code layout. But a lot of people are still used to writing UI in Interface Builder. The safe area in Storyboards is backwards compatible, which means you can use it for layout even in target iOS10 and below. All you need to do is check the Use Safe Area Layout Guide for each Stroyboard. In practice, I think iOS9 is going to do that for the rest of its life.

Knowledge: After using IB set constraint, look for the relative superview or topLayoutGuide/bottomLayoutGuide, included in the Xcode 9 checked Use Safe Area Layout Guide, The default should be relative to the Safe Area.

conclusion

  1. The first step in adapting to the iPhone X is to understand what the Safe Area is all about. blindif iPhoneX{}It will only cause more trouble for the working code later.
  2. If you only need to adapt to iOS9, storyboards before iOS9 only need to do one thing.
  3. Xcode9 with IB you can see that safe area is everywhere. It’s easy to understand. Add a Safe Area to each View. The size of the safe area is the same as the size of the View. The layout on this View only needs to be relative to the Safe Area. The safe area of each View can be obtained from iOS 11 API safeAreaInsets or safeAreaLayoutGuide.
  4. Added additionalSafeAreaInsets to UIViewController to manage additional situations with tabbars or navigation bars.
  5. For UIScrollView, UITableView, UICollectionView these three controls, the system and do most of the things.
    • ScrollView only need to set up contentInsetAdjustmentBehavior can easily fit iPhoneX
    • TableView only needs to be done relative to the safe Area when setting constraints such as cell header and footer
    • Modify the collection ViewsectionInsetReferenceMost things can be done for.SafeArea.
  6. In general, the safe area can be considered as a virtual view on all the views. The virtual view size depends on the location of the view, etc. Custom controls are laid out for the safe Area virtual view as much as possible.

Reference articles may require ladders