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 并使用 topLayoutGuidebottomLayoutGuide + 为 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

将它们分别添加为 myConstraintSAFEAREAmyConstraintSUPERVIEW 作为出口。那么:

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

我已将此页面的一些答案合并到此中,效果很好(仅适用于顶部布局指南,如问题中所要求):

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