I have a UIViewController
with four UIButtons
(2 x 2) on it that I laid out in interface builder that worked perfectly. I'm going to have a free and ad-supported version of my app, so I need to redo that scene to load based on whether the app is a paid or ad-supported version. Based on that, I'm attempting to use Visual Formatting Language to lay out the view. I'm getting incorrect values for my UIButton
heights despite accounting for them when I calculate them. I can't figure out my mistake (or omission?).
Here's a screenshot of my interface builder.
I do not have constraints on my buttons in interface builder, but I do have IBOutlets
wired to MyViewController
. MyViewController
is in a navigation controller and has a tab bar at the bottom.
I created a method called layoutButtons
that I call in viewDidLoad
just after super.viewDidLoad()
. Here it is:
func layoutButtons() {
// Configure layout constraints
// Remove interface builder constraints from storyboard
view.removeConstraints(view.constraints)
// create array to dump constraints into
var allConstraints = [NSLayoutConstraint]()
// determine screen size
let screenSize: CGRect = UIScreen.mainScreen().bounds
let navBarRect = navigationController!.navigationBar.frame
let navBarHeight = navBarRect.height
let tabBarRect = tabBarController!.tabBar.frame
let tabBarHeight: CGFloat = tabBarRect.height
// calculate button width based on screen size
// padding for left + middle + right = 8.0 + 8.0 + 8.0 = 24.0
let buttonWidth = (screenSize.width - 24.0) / 2
/*
My buttons are extending under the top & bottom layout guides despite accounting
for them when I set the buttonHeight.
*/
// padding for top + middle + bottom = 8.0 + 8.0 + 8.0 = 24.0
let buttonHeight = (screenSize.height - topLayoutGuide.length - bottomLayoutGuide.length - 24.0) / 2
// create dictionary of metrics
let metrics = ["buttonWidth": buttonWidth,
"buttonHeight": buttonHeight,
"navBarHeight": navBarHeight,
"tabBarHeight": tabBarHeight,
"bannerAdWidth": bannerAdWidth,
"bannerAdHeight": bannerAdHeight]
// create dictionary of views
var views: [String : AnyObject] = ["firstButton": firstButton,
"secondButton": secondButton,
"thirdButton": thirdButton,
"fourthButton": fourthButton,
"topLayoutGuide": topLayoutGuide,
"bottomLayoutGuide": bottomLayoutGuide]
let topRowHorizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[firstButton(buttonWidth)]-[secondButton(buttonWidth)]-|",
options: [.AlignAllCenterY],
metrics: metrics,
views: views)
allConstraints += topRowHorizontalConstraints
let bottomRowHorizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[thirdButton(buttonWidth)]-[fourthButton(buttonWidth)]-|",
options: [.AlignAllCenterY],
metrics: metrics,
views: views)
allConstraints += bottomRowHorizontalConstraints
let leftColumnVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[topLayoutGuide]-[firstButton(buttonHeight)]-[thirdButton(buttonHeight)]-[bottomLayoutGuide]|",
options: [],
metrics: metrics,
views: views)
allConstraints += leftColumnVerticalConstraints
let rightColumnVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[topLayoutGuide]-[secondButton(buttonHeight)]-[fourthButton(buttonHeight)]-[bottomLayoutGuide]|",
options: [],
metrics: metrics,
views: views)
allConstraints += rightColumnVerticalConstraints
NSLayoutConstraint.activateConstraints(allConstraints)
}
I've fiddled with my buttonHeight
variable, but every iteration I've tried results in the buttons extending under the topLayoutGuide
and bottomLayoutGuide
. Here's what it looks like at runtime:
I welcome any suggestions where to look for my mistake. Thank you for reading.
I originally followed a Ray Wenderlich's Visual Format Language tutorial and the tutorial's setup was similar to mine in that the subViews
were on a storyboard and wired to MyViewController
with IBOutlets
.
Out of desperation, I nuked the storyboard and created the UIButtons
in code. Along the way, I discovered my problem was I was setting the image on the button before it was laid out. The moral of the story is don't set images on UIButtons
until they're laid out!
Below is the code called from a method in my viewDidLoad
that lays out a 2 x 2 grid of buttons:
// Configure Buttons
firstButton.translatesAutoresizingMaskIntoConstraints = false
// code to customize button
secondButton.translatesAutoresizingMaskIntoConstraints = false
// code to customize button
thirdButton.translatesAutoresizingMaskIntoConstraints = false
// code to customize button
fourthButton.translatesAutoresizingMaskIntoConstraints = false
// code to customize button
// Add buttons to the subview
view.addSubview(firstButton)
view.addSubview(secondButton)
view.addSubview(thirdButton)
view.addSubview(fourthButton)
// create views dictionary
var allConstraints = [NSLayoutConstraint]()
let views: [String : AnyObject] = ["firstButton": firstButton,
"secondButton": secondButton,
"thirdButton": thirdButton,
"fourthButton": fourthButton,
"topLayoutGuide": topLayoutGuide,
"bottomLayoutGuide": bottomLayoutGuide]
// Calculate width and height of buttons
// 24.0 = left padding + middle padding + right padding
let buttonWidth = (screenSize.width - 24.0) / 2
let buttonHeight = (screenSize.height - topLayoutGuide.length - bottomLayoutGuide.length - 24.0) / 2
// Create a dictionary of metrics
let metrics = ["buttonWidth": buttonWidth,
"buttonHeight" : buttonHeight]
let topVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[topLayoutGuide]-[firstButton(buttonHeight)]",
options: [],
metrics: metrics,
views: views)
allConstraints += topVerticalConstraints
let topRowHorizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[firstButton(buttonWidth)]-[secondButton(==firstButton)]-|",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: metrics,
views: views)
allConstraints += topRowHorizontalConstraints
let bottomRowHorizontalContraints = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[thirdButton(buttonWidth)]-[fourthButton(==thirdButton)]-|",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: metrics,
views: views)
allConstraints += bottomRowHorizontalContraints
let leftColumnVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[firstButton]-[thirdButton(==firstButton)]-[bottomLayoutGuide]-|",
options: [],
metrics: metrics,
views: views)
allConstraints += leftColumnVerticalConstraints
let rightColumnVerticalConstriants = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[secondButton(buttonHeight)]-[fourthButton(==secondButton)]-[bottomLayoutGuide]-|",
options: [],
metrics: metrics,
views: views)
allConstraints += rightColumnVerticalConstriants
NSLayoutConstraint.activateConstraints(allConstraints)
// ** DON'T SET IMAGES ON THE BUTTONS UNTIL THE BUTTONS ARE LAID OUT!!!**
firstButton.imageView?.contentMode = UIViewContentMode.ScaleAspectFit
firstButton.setImage(UIImage(named: "first.png"), forState: .Normal)
secondButton.imageView?.contentMode = UIViewContentMode.ScaleAspectFit
secondButton.setImage(UIImage(named: "second.png"), forState: .Normal)
thirdButton.imageView?.contentMode = UIViewContentMode.ScaleAspectFit
thirdButton.setImage(UIImage(named: "third.png"), forState: .Normal)
fourthButton.imageView?.contentMode = UIViewContentMode.ScaleAspectFit
fourthButton.setImage(UIImage(named: "fourth.png"), forState: .Normal)