xib 文件中的安全区域布局指南 - iOS 10
Safe area layout guides in xib files - iOS 10
我开始针对 iPhone X 调整我的应用程序,并在 Interface Builder 中发现了一个问题。
根据 Apple 官方视频,安全区域布局指南应该是向后兼容的。我发现它在故事板中工作得很好。
但是在我的 XIB 文件中,iOS 10.
中没有遵守安全区域布局指南
它们在新的 OS 版本中运行良好,但 iOS 10 设备似乎简单地将安全区域距离假定为零(忽略状态栏大小)。
我是否缺少任何必需的配置?它是 Xcode 错误吗?如果是,是否有任何已知的解决方法?
这里是测试项目中问题的截图(左iOS10,右iOS11):
安全区域布局和向后兼容性存在一些问题。请参阅我对 here 的评论。
您或许可以使用其他约束来解决这些问题,例如 1000 优先级 >= 20.0 到 superview.top 和 750 优先级 == safearea.top。如果您始终显示状态栏,应该可以解决问题。
更好的方法可能是将 storyboards/xibs 用于 iOS 11 之前和 iOS-11 及更高版本,尤其是当您 运行 遇到的问题多于这个。更可取的原因是因为 iOS 11 之前你应该将约束布局到 top/bottom 布局指南,但是对于 iOS 11 你应该将它们布局到安全区域。布局指南不见了。为 pre-iOS 11 布局指南在风格上比仅仅偏移 20 像素要好,即使结果是相同的 IFF 你总是显示状态栏。
如果采用这种方法,您需要将每个文件设置为正确的部署目标(iOS 11,或更早的版本),这样 Xcode 就不会' 给你警告,并允许你使用布局指南或安全区域,视情况而定。在您的代码中,在 运行 时检查 iOS 11,然后加载适当的 storyboard/xibs.
这种方法的缺点是维护,(您将有两套视图控制器来维护和保持同步),但是一旦您的应用仅支持 iOS 11+ 或 Apple 修复了落后问题兼容性布局引导约束生成,可以去掉pre-iOS11版本。
顺便问一下,你是如何显示你看到这个的控制器的?它只是根视图控制器还是您提供了它,或者..?我注意到的问题与推送视图控制器有关,因此您可能遇到了不同的情况。
找到了最简单的解决方案 - 只需禁用 safe area
并使用 topLayoutGuide
和 bottomLayoutGuide
+ 为 iPhone X 添加修复。也许这不是一个很好的解决方案,但需要尽可能减少努力
我最终删除了 xib 文件中对安全区域的约束。
相反,我为有问题的 UIView 创建了一个出口,并从代码中将它连接起来,在 viewDidLayoutSubviews 中。
let constraint = alert.viewContents.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 0)
constraint.priority = 998
constraint.isActive = true
这会将一个小 "alert" 绑定到屏幕顶部,但确保警报中的内容视图始终位于顶部安全区域(iOS11ish)/topLayoutGuide(iOS10ish)
简单且一次性的解决方案。如果有什么问题,我会回来的。
目前,向后兼容性效果不佳。
我的解决方案是在界面生成器中创建 2 个约束并根据您使用的 ios 版本删除一个:
- 对于 ios 11:
view.top == safe area.top
- 早期版本:
view.top == superview.top + 20
将它们分别添加为 myConstraintSAFEAREA
和 myConstraintSUPERVIEW
作为出口。那么:
override func viewDidLoad() {
if #available(iOS 11.0, *) {
view.removeConstraint(myConstraintSUPERVIEW)
} else {
view.removeConstraint(myConstraintSAFEAREA)
}
}
对我来说,让它在两个版本上工作的简单修复是
if #available(iOS 11, *) {}
else {
self.edgesForExtendedLayout = []
}
来自文档:"In iOS 10 and earlier, use this property to report which edges of your view controller extend underneath navigation bars or other system-provided views. "。
因此,将它们设置为空数组可确保视图控制器不会延伸到导航栏下方。
文档可用here
这也有效:
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 11.0, *) {}
else {
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height - 80).isActive = true
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 20).isActive = true
}
}
我已将此页面的一些答案合并到此中,效果很好(仅适用于顶部布局指南,如问题中所要求):
- 确保在故事板或 xib 文件中使用安全区域
- 将您的视图限制在安全区域
- 每个
view
有一个 constraint
附加到 SafeArea.top
- 为
view
创建一个 IBOutlet
- 为
constraint
创建一个 IBOutler
在 viewDidLoad:
上的 ViewController 内
if (@available(iOS 11.0, *)) {}
else {
// For each view and constraint do:
[self.view.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES;
self.constraint.active = NO;
}
编辑:
这是我最终在我们的代码库中使用的改进版本。只需 copy/paste 下面的代码并将每个视图和约束连接到它们的 IBOutletCollection。
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *constraintsAttachedToSafeAreaTop;
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *viewsAttachedToSafeAreaTop;
if (@available(iOS 11.0, *)) {}
else {
for (UIView *viewAttachedToSafeAreaTop in self.viewsAttachedToSafeAreaTop) {
[viewAttachedToSafeAreaTop.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES;
}
for (NSLayoutConstraint *constraintAttachedToSafeAreaTop in self.constraintsAttachedToSafeAreaTop) {
constraintAttachedToSafeAreaTop.active = NO;
}
}
The count of each IBOutletCollection should be equal. e.g. for each view
there should be its associated constraint
我添加了一个 NSLayoutConstraint 子类来解决这个问题 (IBAdjustableConstraint
),带有一个 @IBInspectable
变量,看起来像这样。
class IBAdjustableConstraint: NSLayoutConstraint {
@IBInspectable var safeAreaAdjustedConstant: CGFloat = 0 {
didSet {
if OS.TenOrBelow {
constant += safeAreaAdjustedConstantLegacy
}
}
}
}
和OS.TenOrBelow
struct OS {
static let TenOrBelow = UIDevice.current.systemVersion.compare("10.9", options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
}
只需在 IB 中将其设置为约束的子类,您就可以进行 < iOS11 特定更改。希望这对某人有所帮助。
我用的就是这个,添加顶部安全区布局,连接插座
@IBOutlet weak var topConstraint : NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
if !DeviceType.IS_IPHONE_X {
if #available(iOS 11, *) {
}
else{
topConstraint.constant = 20
}
}
}
我开始针对 iPhone X 调整我的应用程序,并在 Interface Builder 中发现了一个问题。 根据 Apple 官方视频,安全区域布局指南应该是向后兼容的。我发现它在故事板中工作得很好。
但是在我的 XIB 文件中,iOS 10.
中没有遵守安全区域布局指南它们在新的 OS 版本中运行良好,但 iOS 10 设备似乎简单地将安全区域距离假定为零(忽略状态栏大小)。
我是否缺少任何必需的配置?它是 Xcode 错误吗?如果是,是否有任何已知的解决方法?
这里是测试项目中问题的截图(左iOS10,右iOS11):
安全区域布局和向后兼容性存在一些问题。请参阅我对 here 的评论。
您或许可以使用其他约束来解决这些问题,例如 1000 优先级 >= 20.0 到 superview.top 和 750 优先级 == safearea.top。如果您始终显示状态栏,应该可以解决问题。
更好的方法可能是将 storyboards/xibs 用于 iOS 11 之前和 iOS-11 及更高版本,尤其是当您 运行 遇到的问题多于这个。更可取的原因是因为 iOS 11 之前你应该将约束布局到 top/bottom 布局指南,但是对于 iOS 11 你应该将它们布局到安全区域。布局指南不见了。为 pre-iOS 11 布局指南在风格上比仅仅偏移 20 像素要好,即使结果是相同的 IFF 你总是显示状态栏。
如果采用这种方法,您需要将每个文件设置为正确的部署目标(iOS 11,或更早的版本),这样 Xcode 就不会' 给你警告,并允许你使用布局指南或安全区域,视情况而定。在您的代码中,在 运行 时检查 iOS 11,然后加载适当的 storyboard/xibs.
这种方法的缺点是维护,(您将有两套视图控制器来维护和保持同步),但是一旦您的应用仅支持 iOS 11+ 或 Apple 修复了落后问题兼容性布局引导约束生成,可以去掉pre-iOS11版本。
顺便问一下,你是如何显示你看到这个的控制器的?它只是根视图控制器还是您提供了它,或者..?我注意到的问题与推送视图控制器有关,因此您可能遇到了不同的情况。
找到了最简单的解决方案 - 只需禁用 safe area
并使用 topLayoutGuide
和 bottomLayoutGuide
+ 为 iPhone X 添加修复。也许这不是一个很好的解决方案,但需要尽可能减少努力
我最终删除了 xib 文件中对安全区域的约束。 相反,我为有问题的 UIView 创建了一个出口,并从代码中将它连接起来,在 viewDidLayoutSubviews 中。
let constraint = alert.viewContents.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 0)
constraint.priority = 998
constraint.isActive = true
这会将一个小 "alert" 绑定到屏幕顶部,但确保警报中的内容视图始终位于顶部安全区域(iOS11ish)/topLayoutGuide(iOS10ish)
简单且一次性的解决方案。如果有什么问题,我会回来的。
目前,向后兼容性效果不佳。
我的解决方案是在界面生成器中创建 2 个约束并根据您使用的 ios 版本删除一个:
- 对于 ios 11:
view.top == safe area.top
- 早期版本:
view.top == superview.top + 20
将它们分别添加为 myConstraintSAFEAREA
和 myConstraintSUPERVIEW
作为出口。那么:
override func viewDidLoad() {
if #available(iOS 11.0, *) {
view.removeConstraint(myConstraintSUPERVIEW)
} else {
view.removeConstraint(myConstraintSAFEAREA)
}
}
对我来说,让它在两个版本上工作的简单修复是
if #available(iOS 11, *) {}
else {
self.edgesForExtendedLayout = []
}
来自文档:"In iOS 10 and earlier, use this property to report which edges of your view controller extend underneath navigation bars or other system-provided views. "。 因此,将它们设置为空数组可确保视图控制器不会延伸到导航栏下方。
文档可用here
这也有效:
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 11.0, *) {}
else {
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height - 80).isActive = true
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 20).isActive = true
}
}
我已将此页面的一些答案合并到此中,效果很好(仅适用于顶部布局指南,如问题中所要求):
- 确保在故事板或 xib 文件中使用安全区域
- 将您的视图限制在安全区域
- 每个
view
有一个constraint
附加到 SafeArea.top- 为
view
创建一个 IBOutlet
- 为
constraint
创建一个 IBOutler
- 为
在
上的 ViewController 内viewDidLoad:
if (@available(iOS 11.0, *)) {} else { // For each view and constraint do: [self.view.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES; self.constraint.active = NO; }
编辑:
这是我最终在我们的代码库中使用的改进版本。只需 copy/paste 下面的代码并将每个视图和约束连接到它们的 IBOutletCollection。
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *constraintsAttachedToSafeAreaTop;
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *viewsAttachedToSafeAreaTop;
if (@available(iOS 11.0, *)) {}
else {
for (UIView *viewAttachedToSafeAreaTop in self.viewsAttachedToSafeAreaTop) {
[viewAttachedToSafeAreaTop.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES;
}
for (NSLayoutConstraint *constraintAttachedToSafeAreaTop in self.constraintsAttachedToSafeAreaTop) {
constraintAttachedToSafeAreaTop.active = NO;
}
}
The count of each IBOutletCollection should be equal. e.g. for each view there should be its associated constraint
我添加了一个 NSLayoutConstraint 子类来解决这个问题 (IBAdjustableConstraint
),带有一个 @IBInspectable
变量,看起来像这样。
class IBAdjustableConstraint: NSLayoutConstraint {
@IBInspectable var safeAreaAdjustedConstant: CGFloat = 0 {
didSet {
if OS.TenOrBelow {
constant += safeAreaAdjustedConstantLegacy
}
}
}
}
和OS.TenOrBelow
struct OS {
static let TenOrBelow = UIDevice.current.systemVersion.compare("10.9", options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
}
只需在 IB 中将其设置为约束的子类,您就可以进行 < iOS11 特定更改。希望这对某人有所帮助。
我用的就是这个,添加顶部安全区布局,连接插座
@IBOutlet weak var topConstraint : NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
if !DeviceType.IS_IPHONE_X {
if #available(iOS 11, *) {
}
else{
topConstraint.constant = 20
}
}
}