是否启用与 11 以下 iOS 兼容的安全区域布局指南?
16 回答
我设法使用新的安全区域布局指南并保持与 iOS 9 和 iOS 10 的向后兼容性:( 编辑:正如@NickEntin 的评论中所指出的,这个实现将假定存在一个状态栏,它不会在 iPhone X 的横向上是正确的。导致顶部有很大的空间(20 点)。但是它会运行得很好。
例如,如果您希望视图位于状态栏下方 10 点(iPhone X 上的传感器外壳下方 10 点):
File Inspector
在您的 XIB中,通过选中转到并启用保险箱Use Safe Area Layout Guides
。- 创建一个从视图顶部到主视图顶部的约束,具有
>=
(大于或等于)约束、常量30
(30,因为我们希望状态栏间距为 10 点,高度为 20 点)和优先级High
(750)。 - 创建从视图顶部到安全区域顶部的约束,具有
=
(相等)约束、常量10
和优先级Low
(250)。
对于底部的视图(以及安全区域的前导/尾随或左/右)也可以这样做:
File Inspector
在您的 XIB中,通过选中转到并启用保险箱Use Safe Area Layout Guides
。- 创建从视图底部到主视图底部的约束,具有
>=
(大于或等于)约束、常量10
和优先级High
(750)。 - 创建从视图底部到安全区域底部的约束,具有
=
(相等)约束、常量10
和优先级Low
(250)。
仅当您使用情节提要时,iOS 9 和 iOS 10 的安全区域的向后兼容性才有效。如果您使用的是 xibs,则没有可供参考的布局指南。 https://forums.developer.apple.com/thread/87329
解决方法似乎是
(a) 将您的 xib 迁移到情节提要中,或
(b) 以编程方式添加一些额外的约束。
如果 (a) 不是一个真正的选项,手动方法将是这样的:
假设您在 xib 中有一个要保留在安全区域内的视图(即在任何状态栏或导航栏下方)。
在您的 xib 中在您的视图和 iOS 11 的安全区域之间添加约束。将顶部约束分配给优先级 750。
在您的视图控制器中,添加一个属性:
@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 中的优先级。
如果您需要避开主指示器,请重复底部约束。
斯威夫特 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
}
}
}
我在 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指出导航栏可见案例。
斯威夫特 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)
是的,您的项目/应用程序可以在 iOS 11 之前的 iOS 版本中运行,没有任何问题。在 iOS 11 之前的版本中,它将安全区域布局替换/考虑为正常的自动布局,并遵循顶部和底部布局指南的规则。
我在两个平台(iOS 11 和落后的 iOS 10)上测试了使用和不使用“SafeAreaLayout”的现有项目。它工作正常。
只要确保:
如果您在 AutoLayout 中设计了您的项目/用户界面;您的 UIElement 的约束遵循/相对于顶部和底部布局指南(而不是超级视图)。因此,通过单击(启用)SafeAreaLayout 选项,将自动为情节提要中的所有 Interface Builders 文件正确实施 SafeArea 布局。
如果您在 SafeAreaLayout 中设计了您的项目/用户界面;然后它将自动遵循落后 iOS 中的顶部和底部布局指南。
这是带有结果的示例快照,通过启用或禁用安全区域布局,不会影响现有设计。
自动布局
简而言之,您的问题的答案是:“启用与 iOS 11 之前兼容的安全区域布局指南”
您可以在您的项目/应用程序中实现安全区域布局,通过将安全区域布局转换为顶部,它将与以前的 iOS 版本正常工作和底部布局。
如果您在没有情节提要的情况下使用 xib,那么它们在 ios 10 上没有布局指南。因此将 xib 移动到情节提要以具有向后兼容性。
当我的 UIView 中有一个导航栏并且在 iOS 10 中取得了良好的效果时,我在 Objective-C 中使用了它。如果您在 xib 中使用 SafeArea,那么您可以添加viewDidLoad
:
if (@available(iOS 11.0, *)) {}
else {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
对于 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
“安全区域布局指南”向后兼容。好吧,除非你在 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)
在 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];
}
我找到了一种更方便的方法,您只需要将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
}
}
西布:
当您有一个所有 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
})
}
}
我在 iOS 9 上与 WKWebView 和安全区域存在向后兼容性问题。由于某种原因,WKWebView 只是忽略了安全区域布局设置。
这是我对我的项目所做的
在我的情况下,mytopConstraint
和bottomConstraint
s 都是@IBOutlet
s。这也适用于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
这是我在 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
}
简单的 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)
])
}
}