如何在iOS 8 / Swift中处理屏幕尺寸/方向变化的NSLayoutConstraints?

如何使用IB约束和NSLayoutConstraint

我想以编程方式微调我的Interface Builder Storyboard布局,因为我无法单独使用Interface Builder中的约束来实现所需的布局。

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


问题:

  • 如何在UIViewController出现时找到屏幕尺寸

  • 如何在iOS 8中检测方向更改和生成的屏幕尺寸?

  • 如何以编程方式修改布局约束

  • 如何以编程方式处理IB 大小类


也许通过IB中的“加权”组件,我可以补偿iPhone 6 Plus尺寸等级的布局问题。 但似乎比以编程方式进行一些调整更具挑战性。

iOS 8支持的Devicea是分散屏幕大小的泥潭

iOS 4应用程序仍然需要支持iPhone 4S,其屏幕非常小巧 。 对于更适合大型iPhone屏幕的应用程序来说,4S上的应用程序令人满意,这对于几乎荒谬的程度来说非常紧张。

iPhone 6 Plus实际上是iPad“mini-mini”,除了更紧凑的宽高比。 相对庞大的iPhone 6 plus需要比其他iPhone更均匀/更宽敞的组件分布。 然而,Interface Builder大小类并不能很好地覆盖iPhone 6 Plus(到目前为止)。

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

•使用IBOutlets并修改出口约束

我发现以编程方式修改基于约束的布局的最佳方法是为我想要修改的每个约束创建一个IBOutlet ,而不是产生开销编写代码以编程方式定位/添加/删除/替换约束,特别是在大小类时用于Interface Builder布局。 当使用“ 大小类 ”时(谷歌的定义), 替换约束是一个主要的麻烦。 你真的必须知道你正在做什么才能使约束替换工作正确,而且,开销要高得多。

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

通过根据需要定位更大的占位符/容器视图来简化复杂布局,将场景向下移动到更详细的视图。 策略性地设计约束,因此您只需在运行时通过其出口以编程方式调整最少的约束,以实现最佳布局。 如果可能的话,单独使用NSLayoutConstraint常量属性,而不是篡改NSLayoutConstraint乘数属性,因为常量可以在运行时修改,但乘数是只读的,需要约束替换来修改它。 如果您正在使用乘数属性,因为您需要约束来按比例调整其他视图的大小或位置,您可以在代码中进行乘法并在运行时将其应用于常量 ,而不是依赖于NSLayoutConstraint 乘数属性。

•根据IB约束使用单一大小的分类常数

如果你正在使用大小类,在Interface Builder中,一定要定义单独的约束,每个约束只有一个大小类特定的常量,这样你就可以为每个单独的大小类约束分配一个出口,而不是为多个聚合常量而不是将大小类分成单个约束。 虽然Interface Builder允许您将多个常量添加到一个约束定义中,但NSLayoutConstraint本身仅提供单个 常量属性。 这意味着多个NSLayoutConstraint实例将在运行时从单个可见的Interface Builder约束定义产生,如果它附加了多个大小类常量。 它比你想象的更难以尝试在运行时找到未过时的约束并调整它们而不会遇到问题。

•注意以编程方式解决与IB约束混乱的运行时问题

如果您尝试以编程方式在运行时定位/修改/删除/替换约束,则在涉及大小类时,请自行解决耗时的混淆冲突和错误。 我发现iOS在图片中带来了“鬼”约束,这些约束是使用黑盒逻辑出乎意料的,阻碍了进行布局更改的尝试。 冲突警告可能由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() }