如何在 iOS 8/Swift 中处理屏幕 size/orientation 变化的 NSLayoutConstraints?

How to handle NSLayoutConstraints with screen size/orientation changes in iOS 8/Swift?

如何使用 IB 约束和 NSLayoutConstraint?

我想以编程方式微调我的 Interface Builder 故事板布局,因为我无法单独在 Interface Builder 中通过约束实现所需的布局。

iPhone 4S 和 iPhone 6 Plus 对我的 iOS 8 布局特别具有挑战性。


问题:


也许通过 IB 中的 "weighting" 个组件,我可以用 iPhone 6 Plus 尺寸 class 来弥补布局问题。但这似乎比以编程方式进行一些调整更具挑战性。

iOS 8 种支持的设备是不同屏幕尺寸的泥潭

iPhone 4S,小得可笑 屏幕,仍然需要 iOS 8 个应用程序支持。对于更适合更大 iPhone 屏幕的应用程序来说,要在 4S 上令人满意,这是一个相当大的挑战,而且挑战几乎荒谬。

而 iPhone 6 Plus 实际上是 iPad "mini-mini",除了更紧凑的宽高比。相对庞大的 iPhone 6 plus 比其他 iPhone 需要更多的 even/spacious 组件分布。然而,Interface Builder 大小 classes 并没有很好地覆盖 iPhone 6 Plus(到目前为止)。

这是一种方法 iOS 8/Swift 代码示例

• 使用 IBOutlets 并就地修改出口约束

我发现以编程方式修改基于约束的布局的最佳方法是为每个我想就地修改的约束制作一个 IBOutlet,而不是编写代码的开销以编程方式限制 locate/add/remove/replace,特别是在 Interface Builder 布局中使用 size classes 时。当使用“size classes”(Google 它的定义)时,替换 约束是一个主要的麻烦。你真的必须知道你在做什么才能使约束替换正常工作,另外,还有相当高的开销。

• 避免使用 IB 布局约束的乘数 属性:

通过放置更大的 placeholder/container 视图来简化复杂的布局,如有必要,将场景向下过渡到更详细的视图。策略性地设计约束,因此您只需在运行时通过它们的出口以编程方式调整尽可能少的约束以获得最佳布局。如果可能,单独使用 NSLayoutConstraintconstant 属性,而不是篡改 NSLayoutConstraintmultiplier属性,因为constant可以在运行时修改,但是multiplier 是只读的,需要约束替换才能修改它。如果您使用 multiplier 属性 因为您需要约束按比例调整其他视图的大小或位置,您可以在代码中进行乘法并将其应用于constant 在运行时,而不是依赖 NSLayoutConstraint multiplier 属性。

• 每个 IB 约束使用单一大小 classed 常量:

如果您使用大小 classes,请务必在 Interface Builder 中定义 个人 约束,每个约束只有一个 single size-class specific constant 以便您为 each 个人分配一个插座 size-classed约束,而不是尝试将多个大小 classes 的常量聚合到一个约束中。尽管 Interface Builder 允许您将 multiple 常量添加到一个约束定义中,但 NSLayoutConstraint 本身仅提供一个 single 不变 属性。这意味着多个 NSLayoutConstraint 实例将在运行时从单个可见的 Interface Builder 约束定义中产生,如果它附加了多个大小 class 常量。尝试在运行时定位未输出的约束并在不 运行 出现问题的情况下对其进行调整,这比您想象的要棘手。

• 当心运行时问题以编程方式扰乱 w/IB 约束:

如果您 尝试在运行时以编程方式 locate/modify/remove/replace 约束,当涉及大小 classes 时,请为耗时的混乱冲突和错误。我发现 iOS 给图片带来了 'ghost' 限制,这些限制是使用黑盒逻辑时意想不到的,阻碍了布局更改的尝试。 iOS 可能会生成冲突警告,有时甚至会产生您以编程方式删除的约束( 例如,为什么这些约束仍在 iOS 的处理逻辑中??!!!) iOS 有时能够在运行时打破和修复约束冲突,但即使冲突被 iOS 打破,并且一切看起来都很好,这样的冲突(产生 Xcode 控制台错误消息)被 Apple 明确视为用户编码错误。

Swift/iOS 8 代码示例:

@IBOutlet var keypadPlaceholder      : UIView!
@IBOutlet var textualDatePlaceholder : UIView!
@IBOutlet var keypadVconstraint      : NSLayoutConstraint!
@IBOutlet var textualDateVConstraint : NSLayoutConstraint!
@IBOutlet var datePickerVConstraint  : NSLayoutConstraint!

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    adjustViewLayout(size)
}
override func viewWillAppear(animated: Bool) {
    adjustViewLayout(UIScreen.mainScreen().bounds.size)
}
func adjustViewLayout(size: CGSize) {
    println("height: \(size.height), width: \(size.width)")

    switch(size.width, size.height) {
    case (480, 320):                        // iPhone 4S in landscape
        keypadPlaceholder.hidden = false
        textualDatePlaceholder.hidden = false
    case (320, 480):                        // iPhone 4S in portrait
        keypadPlaceholder.hidden = true
        textualDatePlaceholder.hidden = true
    case (414, 736):                        // iPhone 6 Plus in portrait
        textualDateVConstraint.constant = 105
        datePickerVConstraint.constant = 50
        keypadVconstraint.constant = 50
        view.setNeedsLayout()
    case (736, 414):                        // iphone 6 Plus in landscape
        textualDateVConstraint.constant = 80
        datePickerVConstraint.constant = 3
        keypadVconstraint.constant = 3
        view.setNeedsLayout()
    default:
        break
    }
}

此示例调整中心 Y 约束,当设备处于纵向模式时,将日期选择器向上移动与屏幕大小成比例的量,但在横向模式下将其恢复到中心。

@IBOutlet var datePickerYCenterConstraint      : NSLayoutConstraint!

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    adjustViewLayout(size)
}

override func viewWillAppear(animated: Bool) {
    adjustViewLayout(UIScreen.mainScreen().bounds.size)
}
func adjustViewLayout(size: CGSize) {

    // calculate picker's center using object heights (that don't change
    // as we adjust their positions)

    var dy = size.height / 2 - datePicker.frame.size.height / 2

    if (size.height > size.width) { // Device in portrait mode
        datePickeryCenterConstraint.constant = -0.5 * dy - 0.1 * dy
    } else { // Device in landscape mode
        datePickeryCenterConstraint.constant = 0 // reset to center
    }
    view.setNeedsLayout()
}