68

我开始为 iPhone X 调整我的应用程序,并在 Interface Builder 中发现了一个问题。根据苹果官方视频,安全区域布局指南应该是向后兼容的。我发现它在故事板中工作得很好。

但是在我的 XIB 文件中,安全区域布局指南在 iOS 10 中不受尊重。

它们适用于新的操作系统版本,但 iOS 10 设备似乎只是假设安全区域距离为零(忽略状态栏大小)。

我是否缺少任何必需的配置?是否是 Xcode 错误,如果是,有任何已知的解决方法吗?

这是一个测试项目中的问题截图(左 iOS 10,右 iOS 11):

屏幕顶部的安全区域对齐

4

9 回答 9

71

安全区域布局和向后兼容性存在一些问题。在这里查看我的评论。

您也许可以通过附加约束来解决问题,例如 superview.top 的 1000 优先级 >= 20.0 和 750 优先级 == safearea.top。如果您总是显示状态栏,那应该可以解决问题。

更好的方法可能是为 pre-iOS 11 和 iOS-11 及更高版本设置单独的故事板/xib,尤其是当您遇到比这更多的问题时。更可取的原因是因为在 iOS 11 之前,您应该将约束布局到顶部/底部布局指南,但对于 iOS 11,您应该将它们布置到安全区域。布局指南不见了。为 iOS 11 之前的布局指南布局在风格上比仅偏移至少 20 像素要好,即使结果将是相同的 IFF 您总是显示状态栏。

如果您采用这种方法,您需要将每个文件设置为将在其上使用的正确部署目标(iOS 11 或更早版本),以便 Xcode 不会给您警告并允许您使用布局指南或安全区域,视情况而定。在您的代码中,在运行时检查 iOS 11,然后加载适当的故事板/xib。

这种方法的缺点是维护,(您将有两组视图控制器来维护和保持同步),但是一旦您的应用程序仅支持 iOS 11+ 或 Apple 修复了向后兼容性布局指南约束生成,您可以获得摆脱 iOS 11 之前的版本。

顺便说一句,你是如何显示你看到这个的控制器的?它只是根视图控制器还是您展示了它,或者..?我注意到的问题与推送视图控制器有关,因此您可能遇到了不同的情况。

于 2017-09-21T03:26:17.243 回答
14

目前,向后兼容性不能很好地工作。

我的解决方案是在界面生成器中创建 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)
    }
}
于 2017-12-27T21:41:10.453 回答
6

对我来说,让它在两个版本上工作的简单修复是

    if #available(iOS 11, *) {}
    else {
        self.edgesForExtendedLayout = []
    }

来自文档:“在 iOS 10 及更早版本中,使用此属性来报告视图控制器的哪些边缘延伸到导航栏或其他系统提供的视图下方。”。因此,将它们设置为空数组可确保视图控制器不会延伸到导航栏下方。

文档可在此处获得

于 2018-07-09T14:26:52.803 回答
3

我已将此页面中的一些答案合并到此页面中,这就像一个魅力(仅适用于顶部布局指南,如问题中所要求的那样):

  1. 确保在故事板或 xib 文件中使用安全区域
  2. 将您的视图限制在安全区域
  3. 对于每个view附加constraint到 SafeArea.top
    • view
    • constraint
  4. 在 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;
    }
    

编辑:

这是我最终在我们的代码库中使用的改进版本。只需复制/粘贴下面的代码并将每个视图和约束连接到它们的 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;
    }
}

每个 IBOutletCollection 的计数应该相等。例如,对于每个视图都应该有其相关的约束

于 2018-07-26T20:54:56.037 回答
0

我最终删除了我在 xib 文件中对安全区域的约束。相反,我为有问题的 UIView 创建了一个出口,并从代码中像这样在 viewDidLayoutSubviews 中将其连接起来。

let constraint = alert.viewContents.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 0)
constraint.priority = 998
constraint.isActive = true

这会将一个小的“警报”与屏幕顶部联系起来,但确保警报中的内容视图始终低于顶部安全区域(iOS11ish)/topLayoutGuide(iOS10ish)

简单且一次性的解决方案。如果有什么东西坏了,我会回来的。

于 2017-11-09T11:18:04.523 回答
0

我添加了一个 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 特定的更改。希望这可以帮助某人。

于 2018-08-21T10:24:20.597 回答
0

这也有效:

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
    }
}
于 2018-07-12T20:40:42.920 回答
0

我用了这个,添加顶部安全区域布局并与插座连接

@IBOutlet weak var topConstraint : NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    if !DeviceType.IS_IPHONE_X {
        if #available(iOS 11, *)  {
        }
        else{
            topConstraint.constant = 20
        }
    }
}
于 2018-08-27T06:26:03.773 回答
-5

找到了最简单的解决方案 - 只需禁用safe area并使用topLayoutGuidebottomLayoutGuide+ 添加 iPhone X 的修复程序。也许这不是一个漂亮的解决方案,但需要尽可能少的努力

于 2017-10-24T09:37:53.720 回答