iOS 11 安全区域布局指南的向后兼容性

66

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

输入图片描述


6
“安全区域布局指南”的第一个谷歌搜索结果是https://useyourloaf.com/blog/safe-area-layout-guide/,其中声称*“即使您仍然针对iOS 10及更早版本,您也可以在Interface Builder中切换到使用安全区域布局指南。”* - Martin R
3
为什么要给这个问题打上 [iphone-x] 的标签? - Martin R
2
我认为我知道为什么在iPhoneX上使用tabBar时,没有安全区域Insets会出现问题。 - Yuji
4
如果你的部署目标低于iOS 9.0,Xcode会报错显示安全区域布局指南不被支持。 - rmaddy
3
有没有办法同时支持安全区域并且支持 iOS 8?我在考虑是否要在所有屏幕上通过编程的方式添加。 - Pushparaj
2
不幸的是,即使苹果声称Storyboard上的安全区域向后兼容,它实际上并不是这样,在iOS 9和10设备上会破坏你的布局。这个问题有一个radr bug。基本上,启用安全区域后,在iOS 9和10上所有视图的顶部都会有20pt的空白空间。 - Anjan Biswas
16个回答

56
我成功使用了新的安全区域布局指南,并保持向后兼容iOS 9和iOS 10:(编辑:正如@NickEntin在评论中指出的那样,此实现将假定状态栏存在,而这在iPhone X上的横向模式下并不正确。结果是距顶部太远了(20个点)。但它可以完美运行。 例如,如果您希望视图在状态栏下面10个点(在iPhone X上也要在传感器外壳下方10个点):
  1. 在XIB中,转到“文件检查器”并通过勾选“使用安全区域布局指南”启用安全区域。
  2. 创建一个从视图顶部到主视图顶部的约束条件,具有>=(大于或等于)约束条件、常量30(因为我们希望距离20个点高的状态栏有10个点间距)和优先级High(750)。
  3. 创建一个从视图顶部到安全区域顶部的约束条件,具有=(相等)约束条件、常量10和优先级Low(250)。
同样的操作也适用于底部(以及左/右与安全区域的边缘)的视图:
  1. 在XIB中,转到“文件检查器”并通过勾选“使用安全区域布局指南”启用安全区域。
  2. 创建一个从视图底部到主视图底部的约束条件,具有>=(大于或等于)约束条件、常量10和优先级High(750)。
  3. 创建一个从视图底部到安全区域底部的约束条件,具有=(相等)约束条件、常量10和优先级Low(250)。

1
有趣的是,你是否注意到在iPhoneX的横屏模式下,即使应用程序想要显示状态栏,也没有状态栏呢? 如果我的观察是正确的,那么更高优先级的约束条件将覆盖安全区域的0来覆盖状态栏,这在iPhoneX上看起来不太好。 - Nick Entin
1
@NickEntin 感谢您的注意。据我所知,在 iPhone X 的横屏模式下状态栏确实总是隐藏的,而此实现并未处理此问题(表现得好像状态栏实际上存在)。 - thijsonline
2
安全布局是Xcode 9中的主视图。我们要如何应用第二点?在这里的第2和第3点中,我们不会使用相同视图创建约束吗? - NightFury

48

仅当您使用storyboard时,iOS 9和iOS 10的Safe Areas向后兼容才有效。如果您使用xib,则没有布局指南可供回退。

https://forums.developer.apple.com/thread/87329

解决方法似乎为:

(a) 将您的xib迁移到storyboard中;或者

(b) 程序上添加一些额外的约束。

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

假设您在xib中有一个视图,您希望将其保留在安全区域内(即低于任何状态栏或导航栏)。

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

    add a top constraint

  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. 如果您需要避免主页指示器,请对底部约束重复此操作。

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

1
@RawMean #available(iOS 11, *) 将检查 iOS 11 及更高版本。 - Politta
感谢您的精彩解释!受到您的启发,我创建了一个NSLayoutConstraint子类,使其更加方便使用,因为在每个可能使用该xib的视图控制器中都不需要实现任何内容。它位于此帖子的最后:https://dev59.com/NFYO5IYBdhLWcg3wMO5y#52145680 - iVentis
谢谢。为什么你把这个放在viewDidLayoutSubviews里而不是viewDidLoad里? - user2378197
@user2378197 因为在layoutSubviews中可能会有适用于<yourview>的布局代码,所以应该首先处理它们。我认为根据你所做的事情,viewDidLoad也可以使用。 - Dobster
重申user2378197的评论 - 我将其放在viewDidLoad中,没有引用它,它可以正常工作。 - zenchemical

19
在使用 iOS 11 的安全区域约束时,我注意到了至少一个向后兼容的问题——使用安全区域约束推送视图控制器。如果导航栏被隐藏并且您推入了一个顶部有安全区域约束的视图,则在 iOS 9 和 10 上,推入的视图将重叠到状态栏上。如果导航栏可见,并且“在顶部栏下”被禁用,则推入的视图仍会从底部滑动到屏幕顶部,以达到正确的位置。在 iOS 11 上,这两种情况下的布局都是正确的。以下是一个简单的示例:http://www.filedropper.com/foobar。这里是一个视频,其中导航栏被隐藏(iOS 10.3 在左侧,iOS 11 在右侧):https://vimeo.com/234174841/1e27a96a87。这里是一个版本,其中导航栏是可见的(在 nib 中启用):https://vimeo.com/234316256/f022132d57。我已将此问题提交到 Radar #34477706。感谢@Sander指出了可见导航栏的情况。

2
如果导航栏没有隐藏,则该错误是相同的。在iOS 9和10版本中,推送视图位于导航栏下方。或者这种情况应该以不同的方式处理? - Sander
3
关于解决方法,我想到了几个,虽然都不是很好,但可以考虑:1)您可以为iOS 11之前的版本使用单独的nibs/storyboards。根据您的应用程序复杂性而定,这可能会非常麻烦或超级简单。或者2)如果导航栏被隐藏,那么对于那些具有状态栏但没有导航栏的视图,可以将顶部约束设置为">=20"以适应superview(1000优先级),以及安全区域(750优先级)。这样可以解决问题,而且未来也不应该会出现问题。对于导航栏可见的情况,则比较棘手。我仍在寻找一个好的解决方案。 - clarus
嗨@clarus,感谢提供信息!你有收到任何来自苹果的回应吗? - ernewston
目前还没有任何消息,但通常他们在解决问题并需要验证之后才会回复。这可能需要几个月的时间。 - clarus
3
是的,没错。这太荒谬了。我不得不使用许多变通方法才能使事情在iOS 9和10中顺利播放。这是苹果在表达"是啊,很遗憾......但我们不关心iOS 10或9"的方式。 :) - Anjan Biswas
显示剩余2条评论

7
是的,您的项目/应用在iOS 11之前的版本中都可以正常工作。在iOS 11之前的版本中,它会将安全区域布局替换/视为普通自动布局,并遵循顶部和底部布局指南的规则。
我测试了我的现有项目,在两个平台(iOS 11和向后兼容的iOS 10)上都使用了'SafeAreaLayout'和没有使用'SafeAreaLayout'。它们都能正常工作。
请确保: - 如果您已经使用AutoLayout设计了您的项目/用户界面,则UI元素的约束相对于顶部和底部布局指南(而不是superview)。因此,通过单击(启用)SafeAreaLayout选项,将自动为Storyboard中的所有Interface Builders文件正确实现SafeArea布局。 - 如果您已经在SafeAreaLayout中设计了您的项目/用户界面,则它将自动遵循向后兼容的iOS中的顶部和底部布局指南。
下面是一个带有结果的示例快照,启用或禁用Safe Area布局不会影响现有设计。

安全区域布局: enter image description here

自动布局

enter image description here

简而言之,回答你的问题是:“启用适用于 iOS 11 之前的安全区域布局指南” 您可以在项目/应用中实现安全区域布局,并将其转换为顶部和底部布局,以便与之前的 iOS 版本良好兼容。

1
奇怪的是,在iOS 10中,安全区域指南对我不起作用...它只假定一个值为0,内容会重叠状态栏。在iOS 11中,它可以正常工作。有什么想法吗? - Tiago Lira
1
如果您正在使用Xcode 9,则安全区域布局会自动将安全区域转换为iOS 10及更早版本的顶部和底部布局。您能分享一下您的故事板布局的快照吗? - Krunal
15
我发现在Storyboard中转换效果良好,但在XIB文件中出现问题...... 它在iOS 10中无法正确处理状态栏的大小。 - Tiago Lira
3
在我的测试中,它在iOS 11上运行良好,在iOS 10上失败。似乎这个东西仍然有一些漏洞。我稍后会发布一个完整的问题,感谢您的帮助。 - Tiago Lira
如果使用导航栏和/或选项卡栏,则不为真。 - Jonny

7
如果您在没有故事板的情况下使用xib,则它们在iOS 10上没有布局指南。因此,将xib移动到故事板以实现向后兼容性。

7

Swift 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)

6

当我在我的 UIView 中使用 Navigation Bar 并且在 iOS 10 上获得了良好的结果时,我正在使用 Objective-C 这个。如果您在 xib 中使用 SafeArea,则可以在您的 viewDidLoad 中添加:

if (@available(iOS 11.0, *)) {}
else {
    self.edgesForExtendedLayout = UIRectEdgeNone;
}

大大地帮助了那些在iOS 9和10上只呈现在可见区域内的导航栏下方本应延伸的布局,从而在视图顶部提供了额外的边距。 - LordParsley
是的,你说得对。这个位置修复只适用于导航栏。如果没有导航栏它将不起作用。 - Johnny

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

注意:在进行此操作时不应使用edgesForExtendedLayout属性:

self.edgesForExtendedLayout = UIRectEdgeNone;

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


3

我找到了一种更方便的方法,您只需要继承固定在safeArea上的NSLayoutConstraint

这种方法有点专业技巧性,因为您需要从UIView中获取ViewController,但在我看来,这是一个简单且好的替代方法,直到苹果解决Xibs中安全区向后兼容性的问题。

继承:

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

Xib:

enter image description here


3
"安全区域布局指南"是向后兼容的,除非您在xib中使用它。使用Storyboard似乎没问题。
我通过访问视图顶部的第一个对象的"顶部布局约束"来解决了我的问题。
@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)

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