如何在程序中使用安全区域布局?

138

因为我不使用故事板来创建我的视图,所以我想知道是否可以在编程上或其他方式中使用“使用安全区域指南”选项。

我尝试将我的视图锚定到

view.safeAreaLayoutGuide

但它们仍然会重叠在iPhone X模拟器的顶部缺口上。


1
根据https://developer.apple.com/documentation/uikit/uiview/positioning_content_relative_to_the_safe_area,没有关于此的文档化布尔属性。 - dvp.petrov
view.safeAreaInsets 怎么样?你试过了吗? - Karthikeyan Bose
@KarthikeyanBose 是的,不幸的是我试过了,但没有成功。 - Phillip
适用于我。代码长什么样? - user5306470
12个回答

210

这里是示例代码(来自:安全区域布局指南):
如果您使用代码创建约束,请使用UIView的safeAreaLayoutGuide属性获取相关的布局锚点。让我们在代码中重新创建上面的Interface Builder示例,以查看它的外观:

假设我们将绿色视图作为视图控制器中的属性:

private let greenView = UIView()

我们可能会有一个函数在viewDidLoad中被调用,用于设置视图和约束:

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

像往常一样,使用根视图的layoutMarginsGuide创建前导和尾随边距约束:

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

现在,除非你的目标是iOS 11及更高版本,否则你需要使用 #available 包装安全区域布局指南约束,并在早期版本的iOS中退回到顶部和底部布局指南:

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

结果:

在此输入图片描述

在此输入图片描述


这是苹果开发者官方文档,介绍了关于安全区域布局指南


安全区域用于处理iPhone X的用户界面设计。以下是基本指南,说明如何使用安全区域布局进行iPhone-X用户界面设计。


3
能否也用Objective-C给出这个?看起来正是我需要的。 - Tom Hammond
7
@TomHammond,这是给你的 Objective-C 代码示例:https://dev59.com/EFYN5IYBdhLWcg3wq5wo#47076040 - Krunal
2
@ZonilyJame 关于你的问题,SaFeAreaLayout是iOS特定的框架(不是针对iPhoneX设备),它在iOS 11中替换了顶部和底部布局指南,因此我们必须为iOS设置条件,而不是设备。SafeAreaLayout会处理所有类型设备(包括iPhone-X和其他设备)的设计。如果你还有任何疑问或困惑,可以继续向我提问,我会给出更多详细信息。 - Krunal
3
太棒了。谢谢 :) - Rajamohan S
1
为什么这被认为是正确的答案?我试图建立一个具体的位置 - 结果完全随机 - 控件被放置在不可预测的位置! - Vyachaslav Gerchicov
显示剩余6条评论

107

实际上我正在使用一个扩展程序并控制它是否为iOS 11。

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

这是一个非常简单的方法,而且很有效。感谢您提供这个想法。 - alper_k
1
这是一种简单而又好用的方法!请注意使用 self.safeAreaLayoutGuide 而不是 self.layoutMarginsGuide。在此答案中使用的安全区域指南对我来说可以正确地保持在安全区域内!我建议更改的一件事是使用 leadingAnchortrailingAnchor 而不是 leftAnchorrightAnchor。太棒了! - sudoExclaimationExclaimation
虽然这段代码已经不再相关,但是记住有一个安全区域布局指南还是很好的。谢谢。 - Dan Rosenstark

24

SafeAreaLayoutGuideUIView的属性。

safeAreaLayoutGuide的顶部指示视图的未遮挡的上边缘(例如,如果存在状态栏或导航栏,则不在其后面)。其他边缘同理。

使用safeAreaLayoutGuide可以避免我们的对象从圆角、导航栏、选项卡栏、工具栏和其他祖先视图中裁剪/重叠。

我们可以创建safeAreaLayoutGuide对象并设置相应的约束。

纵向和横向的约束如下:

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


4
除非你非常清楚自己在做什么,否则永远不要在 viewDidAppear 中设置约束条件。viewDidAppear 会被多次调用,因此每次调用时都会复制你的约束条件。请注意避免这种情况。 - Yevhen Dubinin
是的!编辑过的答案,你可以将其用作你的用例。 - Jack

18

如果你们像我一样使用SnapKit,那么解决方案就是将你的约束锚定到view.safeAreaLayoutGuide,像这样:

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

1
很棒的答案。为了让它更加简洁,你可以这样写:if #available(iOS 11.0, *) { make.edges.equalTo(view.safeAreaLayoutGuide.snp.margins) } - Don Miguel

11

我使用这个方法来替代在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]
                                          ]];
请同时检查Krunal的回答中有关iOS 11以下版本的选项。

请确保您已将yourView添加到superView中。在我的代码中,self.view是一个简单的例子。 - Tony TRAN

8

Swift 4.2 和 5.0。 假设您想要在 视图Bg 上添加前导尾随顶部底部约束。因此,您可以使用以下代码。

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

7

使用UIWindowUIViewsafeAreaInsets属性,其中包括.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]

3
如果您的视图不使用AutoLayout,那么这是正确的方法。 - Jonathan Cabrera

4
使用视图格式的约束条件,可以免费获得安全区域的尊重。
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


2
请在给出负评时提供评论,这样我们就可以学习到是否存在不适用该技术的情况。谢谢! - AtomicBoolean
这个可以,我也喜欢可视化格式的解决方案!谢谢!但是这对所有iOS版本都有效吗? - PaFi

4

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

它不起作用(Swift 4.2,iOS 12)。结果忽略状态栏。 - Vyachaslav Gerchicov

2

这个扩展可以帮助您将UIVIew限制在其父视图和父视图+安全区域内:

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
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接