问题
How to work with IB Constraints and NSLayoutConstraint?
I want to programmatically fine-tune my Interface Builder Storyboard layout, as I can't achieve desired layout with constraints in Interface Builder alone.
The iPhone 4S and iPhone 6 Plus are particularly challenging for my iOS 8 layouts.
Questions:
How do I find screen dimensions when the UIViewController appears?
How do I detect orientation changes & resulting screen dimensions in iOS 8?
How do I modify layout constraints programmatically?
How do I deal with IB size classes programmatically?
Maybe by "weighting" components in IB, I could compensate for layout problems with iPhone 6 Plus size class. But it seems potentially more challenging than making a few tweaks programmatically.
iOS 8 Supported Devicea are a Quagmire of Divergent Screen Sizes
The iPhone 4S, with its ridiculously tiny screen, is still required to be supported by iOS 8 apps. It's quite a squeeze, and challenging to an almost absurd degree for apps better suited to larger iPhone screens to be satisfactory on the 4S.
And iPhone 6 Plus is virtually an iPad "mini-mini", except for its tighter aspect ratio. The relatively gargantuan iPhone 6 plus requires a more even/spacious component distribution than other iPhones. Yet Interface Builder size classes don't cover the iPhone 6 Plus very well (so far).
回答1:
Here's one approach with iOS 8/Swift code example
• Use IBOutlets and modify outlets constraints in place:
The best way I've found to modify constraint-based layouts programmatically is to make an IBOutlet for each constraint I want to modify in place, rather than incur overhead writing code to locate/add/remove/replace constraints programmatically, particularly when size classes are used in the Interface Builder layout. Replacing constraints is a major hassle when "size classes" are used (Google its definition). You really have to know what you're doing to make constraint replacement work right, plus, and there's considerably higher overhead.
• Avoid using multiplier property of IB layout constraints:
Simplify complex layouts by positioning larger placeholder/container views, as necessary, staging the scene down to toward more detailed view. Strategically design constraints so you only have to tweak the fewest constraints possible programmatically via their outlet at runtime for optimal layout. Do so using NSLayoutConstraint's constant property alone, if possible, rather than tampering with NSLayoutConstraint's multiplier property, because constant can be modified at runtime, but multiplier is readonly, requiring constraint replacement to modify it. If you are using the multiplier property because you need the constraints to adjust proportionally to the size or positions of other views, you can do the multiplication in your code and apply it to constant at runtime, rather than rely on NSLayoutConstraint multiplier property.
• Use a single size classed constant per IB constraint:
If you're using size classes, in Interface Builder, be sure to define individual constraints, each with only a single size-class specific constant so that you assign an outlet to each individual size-classed constraint, rather trying than aggregating constants for multiple size classes into a single constraint. Though Interface Builder will let you add multiple constants to one constraint definition, NSLayoutConstraint itself only provides only a single constant property. This implies that multiple NSLayoutConstraint instances will result at runtime from a single visible Interface Builder constraint definition, if it has multiple size class constants attached to it. It gets trickier than you might assume to try to locate un-outleted constraints at runtime and tweak them without running into problems.
• Beware of runtime issues messing w/IB constraints programmatically:
If you do try to programmatically locate/modify/remove/replace constraints at runtime, when size classes are involved, brace yourself for time-consuming confusing conflicts and errors. I've found iOS brings 'ghost' constraints into the picture that are unexpected using black-box logic, thwarting attempts to make layout changes. Conflict warnings may be generated by iOS, sometimes even with constraints that you programmatically deleted (e.g. why are those constraints still in iOS's processing logic??!!!) iOS will sometimes be able to break and fix constraint conflicts at runtime, but even if a conflict is broken by iOS, and it all winds up looking good, such conflicts (that produce Xcode console error messages) are explicitly considered user coding bugs by Apple.
Swift/iOS 8 code example:
@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
}
}
回答2:
This example adjusts a Center Y constraint that shifts a date picker upwards in an amount that's a proportion of the screen size when the device is in portrait mode, but restores it to the center in landscape mode.
@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()
}
来源:https://stackoverflow.com/questions/28160421/how-to-handle-nslayoutconstraints-with-screen-size-orientation-changes-in-ios-8