66

是否启用与 11 以下 iOS 兼容的安全区域布局指南?

在此处输入图像描述

4

16 回答 16

55

我设法使用新的安全区域布局指南并保持与 iOS 9 和 iOS 10 的向后兼容性:( 编辑:正如@NickEntin 的评论中所指出的,这个实现将假定存在一个状态栏,它不会在 iPhone X 的横向上是正确的。导致顶部有很大的空间(20 点)。但是它会运行得很好。

例如,如果您希望视图位于状态栏下方 10 点(iPhone X 上的传感器外壳下方 10 点):

  1. File Inspector在您的 XIB中,通过选中转到并启用保险箱Use Safe Area Layout Guides
  2. 创建一个从视图顶部到主视图顶部的约束,具有>=(大于或等于)约束、常量30(30,因为我们希望状态栏间距为 10 点,高度为 20 点)和优先级High(750)。
  3. 创建从视图顶部到安全区域顶部的约束,具有=(相等)约束、常量10和优先级Low(250)。

对于底部的视图(以及安全区域的前导/尾随或左/右)也可以这样做:

  1. File Inspector在您的 XIB中,通过选中转到并启用保险箱Use Safe Area Layout Guides
  2. 创建从视图底部到主视图底部的约束,具有>=(大于或等于)约束、常量10和优先级High(750)。
  3. 创建从视图底部到安全区域底部的约束,具有=(相等)约束、常量10和优先级Low(250)。
于 2017-10-17T08:12:37.623 回答
48

仅当您使用情节提要时,iOS 9 和 iOS 10 的安全区域的向后兼容性才有效。如果您使用的是 xibs,则没有可供参考的布局指南。 https://forums.developer.apple.com/thread/87329

解决方法似乎是

(a) 将您的 xib 迁移到情节提要中,或

(b) 以编程方式添加一些额外的约束。

如果 (a) 不是一个真正的选项,手动方法将是这样的:

假设您在 xib 中有一个要保留在安全区域内的视图(即在任何状态栏或导航栏下方)。

  1. 在您的 xib 中在您的视图和 iOS 11 的安全区域之间添加约束。将顶部约束分配给优先级 750。

    添加顶部约束

  2. 在您的视图控制器中,添加一个属性:

    @property (nonatomic, strong) NSLayoutConstraint *topLayoutConstraint;
    

    然后在 viewDidLayoutSubviews 中:

    - (void)viewDidLayoutSubviews {
        [super viewDidLayoutSubviews];
    
        if (@available(iOS 11, *)) {
            // safe area constraints already set
        }
        else {
            if (!self.topLayoutConstraint) {
                self.topLayoutConstraint = [self.<yourview>.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor];
                [self.topLayoutConstraint setActive:YES];
            }
        }
    }
    

    新约束只会为 iOS 9 和 iOS 10 创建,默认优先级为 1000,并覆盖 xib 中的优先级。

  3. 如果您需要避开主指示器,请重复底部约束。

斯威夫特 4 版本:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if #available(iOS 11, *) {
        // safe area constraints already set
    } else {
        if topLayoutConstraint == nil {
            topLayoutConstraint = <yourview>.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor)
            topLayoutConstraint?.isActive = true
        }
    }
}
于 2017-11-04T04:59:24.430 回答
19

我在 Xcode 9 GM 中观察到的 iOS 11 的安全区域约束肯定至少存在一个向后兼容性问题——具有安全区域约束的推送视图控制器。

如果您的导航栏被隐藏并且您推送了一个安全区域顶部受限视图,则推送的视图将与 iOS 9 和 10 上的状态栏重叠。

如果导航栏可见,并且禁用了“顶部栏下方”,则推送的视图仍将在导航栏下方向上滑动以到达屏幕顶部。导航栏放置正确。

在 iOS 11 上,两种情况下的布局都是正确的。

这是一个简单的例子:http ://www.filedropper.com/foobar

这是一个隐藏导航栏的视频(左侧是 iOS 10.3,右侧是 iOS 11):https ://vimeo.com/234174841/1e27a96a87

这是导航栏可见的版本(在笔尖中启用): https ://vimeo.com/234316256/f022132d57

我将其归档为 Radar #34477706。

感谢@Sander指出导航栏可见案例。

于 2017-09-17T06:03:32.407 回答
7

斯威夫特 5

我只是这样做。它很简单并且非常接近真实的东西(只是添加了一个'r')。

extension UIView {
    var saferAreaLayoutGuide: UILayoutGuide {
        get {
            if #available(iOS 11.0, *) {
                return self.safeAreaLayoutGuide
            } else {
                return self.layoutMarginsGuide
            }
        }
    }
}

像这样使用:

button.topAnchor.constraint(equalTo: view.saferAreaLayoutGuide.topAnchor, constant: 16)
于 2019-09-26T14:01:53.953 回答
7

是的,您的项目/应用程序可以在 iOS 11 之前的 iOS 版本中运行,没有任何问题。在 iOS 11 之前的版本中,它将安全区域布局替换/考虑为正常的自动布局,并遵循顶部和底部布局指南的规则。

我在两个平台(iOS 11 和落后的 iOS 10)上测试了使用和不使用“SafeAreaLayout”的现有项目。它工作正常。

只要确保:

  • 如果您在 AutoLayout 中设计了您的项目/用户界面;您的 UIElement 的约束遵循/相对于顶部和底部布局指南(而不是超级视图)。因此,通过单击(启用)SafeAreaLayout 选项,将自动为情节提要中的所有 Interface Builders 文件正确实施 SafeArea 布局。

  • 如果您在 SafeAreaLayout 中设计了您的项目/用户界面;然后它将自动遵循落后 iOS 中的顶部和底部布局指南。

这是带有结果的示例快照,通过启用或禁用安全区域布局,不会影响现有设计。

安全区域布局: 在此处输入图像描述

自动布局

在此处输入图像描述

简而言之,您的问题的答案是:“启用与 iOS 11 之前兼容的安全区域布局指南”

您可以在您的项目/应用程序中实现安全区域布局,通过将安全区域布局转换为顶部,它将与以前的 iOS 版本正常工作和底部布局。

于 2017-09-13T14:23:02.920 回答
7

如果您在没有情节提要的情况下使用 xib,那么它们在 ios 10 上没有布局指南。因此将 xib 移动到情节提要以具有向后兼容性。

于 2017-10-25T08:00:13.507 回答
6

当我的 UIView 中有一个导航栏并且在 iOS 10 中取得了良好的效果时,我在 Objective-C 中使用了它。如果您在 xib 中使用 SafeArea,那么您可以添加viewDidLoad

if (@available(iOS 11.0, *)) {}
else {
    self.edgesForExtendedLayout = UIRectEdgeNone;
}
于 2018-02-08T11:07:59.197 回答
4

对于 iOS 9:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    self.navigationController.navigationBar.translucent = NO;
}

如果您启用自动布局并将视图约束添加到安全区域,您可以在iOS 11+ 以上运行,但它可能不适用于iOS 9,并且您的视图可能会出现在导航栏下方。要解决此问题,您可以在' viewWillAppear:(BOOL)animated '方法中禁用半透明属性。

为了不破坏导航栏半透明属性的先前状态,您应该保留先前的值并在viewWillDisappear:(BOOL)animated中重新设置它

@interface YourViewController ()
@property (nonatomic, assign) BOOL translucentState;
@end

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    self.translucentState = self.navigationController.navigationBar.translucent;
    self.navigationController.navigationBar.translucent = NO;
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];

    self.navigationController.navigationBar.translucent = self.translucentState;
}

PS不应使用edgesForExtendedLayout来执行此操作:

self.edgesForExtendedLayout = UIRectEdgeNone;

查看 Apple 文档:https ://developer.apple.com/documentation/uikit/uiviewcontroller/1621515-edgesforextendedlayout

于 2019-10-23T13:26:28.557 回答
3

“安全区域布局指南”向后兼容。好吧,除非你在 xib 中使用它。使用情节提要似乎还可以。

我通过从视图顶部的第一个对象访问“顶部布局约束”解决了我的问题。

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topLayoutConstraint;

然后,我将常量值更改为该约束并刷新视图。例如,如果您使用导航栏(44 高度)加上状态栏(20 高度):

if (SYSTEM_VERSION_LESS_THAN(@"11.0")) {
    _topLayoutConstraint.constant = 64;
    [self.view layoutIfNeeded];
}

使用 SYSTEM_VERSION_LESS_THAN 定义如下:

#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
于 2017-11-17T10:12:04.023 回答
3

在 iPhone-X 上时,在 Objective-C 中用于顶部和底部边距

if (@available(iOS 11, *)) {

    NSLayoutConstraint *bottomConstraint   = [NSLayoutConstraint constraintWithItem:self.childView
                                                                              attribute:NSLayoutAttributeBottom
                                                                              relatedBy:NSLayoutRelationEqual
                                                                                 toItem:self.parentView.safeAreaLayoutGuide
                                                                              attribute:NSLayoutAttributeBottom
                                                                             multiplier:1.0
                                                                               constant:0];


    NSLayoutConstraint *topConstraint   = [NSLayoutConstraint constraintWithItem:self.childView
                                                                       attribute:NSLayoutAttributeTop
                                                                       relatedBy:NSLayoutRelationEqual
                                                                          toItem:self.parentView.safeAreaLayoutGuide
                                                                       attribute:NSLayoutAttributeTop
                                                                      multiplier:1.0
                                                                        constant:0];


} else {

    NSLayoutConstraint *bottomConstraint   = [NSLayoutConstraint constraintWithItem:self.childView
                                                                          attribute:NSLayoutAttributeBottom
                                                                          relatedBy:NSLayoutRelationEqual
                                                                             toItem:self.parentView
                                                                          attribute:NSLayoutAttributeBottom
                                                                         multiplier:1.0
                                                                           constant:0];


    NSLayoutConstraint *topConstraint   = [NSLayoutConstraint constraintWithItem:self.childView
                                                                       attribute:NSLayoutAttributeTop
                                                                       relatedBy:NSLayoutRelationEqual
                                                                          toItem:self.parentView
                                                                       attribute:NSLayoutAttributeTop
                                                                      multiplier:1.0
                                                                        constant:0];

}
于 2017-10-04T11:04:36.517 回答
3

我找到了一种更方便的方法,您只需要将NSLayoutConstraint固定到您的safeArea.

这有点 hacky,因为您必须从 UIView 获取 ViewController 但在我看来,这是一个简单而好的选择,直到 Apple 最终修复 Xibs 中 safeArea 的向后兼容性。

子类:

class SafeAreaBackwardsCompatabilityConstraint: NSLayoutConstraint {
private weak var newConstraint: NSLayoutConstraint?

override var secondItem: AnyObject? {
    get {
        if #available(iOS 11.0, *) {}
        else {
            if let vc = (super.secondItem as? UIView)?.parentViewController, newConstraint == nil {
                newConstraint = (self.firstItem as? UIView)?.topAnchor.constraint(equalTo: vc.topLayoutGuide.bottomAnchor)
                newConstraint?.isActive = true
                newConstraint?.constant = self.constant
            }
        }
        return super.secondItem
    }
}

override var priority: UILayoutPriority {
    get {
        if #available(iOS 11.0, *) { return super.priority }
        else { return 750 }
    }
    set { super.priority = newValue }
}
}

private extension UIView {
var parentViewController: UIViewController? {
    var parentResponder: UIResponder? = self
    while parentResponder != nil {
        parentResponder = parentResponder!.next
        if let viewController = parentResponder as? UIViewController {
            return viewController
        }
    }
    return nil
}
}

西布:

在此处输入图像描述

于 2018-09-03T08:13:31.633 回答
2

当您有一个所有 ViewController 都扩展的通用 ViewController 时,另一种解决方案是将应调整的项目放在 IBOutletCollection 中,并以编程方式在该 GenericViewController 中调整它们。这是我的代码:

@IBOutlet var adjustTopSpaceViews: [UIView]?

override func viewDidLoad() {
    super.viewDidLoad()
    adjustViews()
    ....
}

func adjustViews() {
    guard let views = adjustTopSpaceViews,
        ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11 else {
            return
    }
    let statusBarHeight = UIApplication.shared.statusBarFrame.height
    for subview in views {
        subview.superview?.constraints.filter({ (constraint) -> Bool in
            return constraint.firstAttribute == .top
                && constraint.secondAttribute == .top
                && (constraint.firstItem as? UIView == subview || constraint.secondItem as? UIView == subview)
        }).forEach({ (constraint) in
            constraint.constant += (constraint.firstItem as? UIView == subview) ? statusBarHeight : -statusBarHeight
        })
    }
}
于 2018-04-27T16:25:47.293 回答
2

我在 iOS 9 上与 WKWebView 和安全区域存在向后兼容性问题。由于某种原因,WKWebView 只是忽略了安全区域布局设置。

于 2017-09-18T12:36:59.520 回答
2

这是我对我的项目所做的

在我的情况下,mytopConstraintbottomConstraints 都是@IBOutlets。这也适用于iOS 8.

我对顶部和底部约束的初始配置是针对普通 iPhone 的,这就是为什么我只编辑 iPhone X 的约束

    // iOS 11 Layout Fix. (For iPhone X)
    if #available(iOS 11, *) {
        self.topConstraint.constant = self.topConstraint.constant + self.view.safeAreaInsets.top

        self.bottomConstraint.constant = self.bottomConstraint.constant + self.view.safeAreaInsets.bottom
    }

.

注意: self.view是你的 superView 这就是我使用它的原因safeAreaInsets

于 2018-01-16T04:34:49.920 回答
1

这是我在 swift 4+ 中的 iOS 9 到 iOS 11+ 解决方案包装器

    let safeAreaTopAnchor:NSLayoutYAxisAnchor?
    if #available(iOS 11.0, *) {
        safeAreaTopAnchor = contentView.safeAreaLayoutGuide.topAnchor
    } else {
        // Fallback on earlier versions

        var parentViewController: UIViewController? {
            var parentVCResponder: UIResponder? = self
            while parentVCResponder != nil {
                parentVCResponder = parentVCResponder!.next
                if let viewController = parentVCResponder as? UIViewController {
                    return viewController
                }
            }
            return nil
        }

        safeAreaTopAnchor = parentViewController?.topLayoutGuide.bottomAnchor

    }
于 2019-03-05T03:30:36.163 回答
0

简单的 Swift 4 解决方案:

首先将安全区域的最高约束优先级设置为 750,然后:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    if #available(iOS 11, *) {
        // Safe area constraints already set.
    } else {
        NSLayoutConstraint.activate([
            self.yourView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor)
        ])
    }
}
于 2020-07-06T20:01:49.857 回答