Getting frame of programmatically created subviews - ios

I have a programmatically created subviews, view1. I am adding some UIButtons to it, but the measurements of these buttons depend on view1.frame.height. However, I can only seem to get the value in viewDidAppear, which obviously is not what I want. Moreover, if I put my addButtons() function in viewDidLayoutSubviews, I get an infinite loop. The results of putting addButtons() in various UIViewController functions is as follows (in comments):
override func viewDidLoad() {
super.viewDidLoad()
addView1()
}
override func viewDidLayoutSubviews(){
print(view1.frame.height) // 0.0 first time it is called, 74.0, which is right, the second time it is called, but I got into an infinite loop
//addButtons() -> infinite loop
}
override func viewWillAppear(animated: Bool) {
print(view1.frame.height) //0.0
}
override func viewDidAppear(animated: Bool) {
print(view1.frame.height) //74.0, the value I want, but obviously not where I want it
}
My addButtons() looks like this:
func addButtons(){
let mon = UIButton()
let tue = UIButton()
let wed = UIButton()
let thu = UIButton()
let fri = UIButton()
let sat = UIButton()
let sun = UIButton()
let buttonsArray = ["mon": mon, "tue": tue, "wed": wed, "thu": thu, "fri": fri, "sat": sat, "sun": sun]
let daysArray1 = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
var daysArray2 = ["mon": "M", "tue":"T", "wed": "W", "thu": "T", "fri": "F", "sat": "S", "sun": "S"]
let buttonSide = view1.frame.height * 0.6
let distance = (view1.frame.width - buttonSide * 7) / 8.0
var count = 0
for day in daysArray1{
let offSet = (buttonSide ) * CGFloat(count) + distance * CGFloat(count + 1)
let centerX = (buttonSide / 2) + offSet
buttonsArray[day]!.setTitle(daysArray2[day], forState: .Normal)
buttonsArray[day]!.translatesAutoresizingMaskIntoConstraints = false
buttonsArray[day]!.layer.cornerRadius = 5
view1.addSubview(buttonsArray[day]!)
view1.addConstraint(NSLayoutConstraint(item: buttonsArray[day]!, attribute: NSLayoutAttribute.CenterY, relatedBy: .Equal, toItem: view1, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0))
view1.addConstraint(NSLayoutConstraint(item: buttonsArray[day]!, attribute: NSLayoutAttribute.CenterX, relatedBy: .Equal, toItem: view1, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: centerX))
view1.addConstraint(NSLayoutConstraint(item: buttonsArray[day]!, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: buttonSide))
view1.addConstraint(NSLayoutConstraint(item: buttonsArray[day]!, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: buttonSide))
count += 1
}

Every time you add a button to your view the system calls viewDidLayoutSubviews which you keep adding buttons and the system keeps calling viewDidLayoutSubviews. You should add your buttons when viewDidLoad().
To match the height of the view you can:
Pin all buttons to Trailing and Leading edge of the view
Pin the first button's Top with view's Top Edge
Pin the last button's Bottom with view's Bottom
Pin each button's Top with the previous button's Bottom edge (given is not the first button)
Constraint all buttons .Height to match view's .Height with a multiplier of 0.6
This is an example I did using PureLayout
let someView = UIView(frame: CGRectZero)
view.addSubview(someView)
var prev: UIButton?
for i in 0..<6 {
let button = UIButton(frame: CGRectZero)
button.setBackgroundColor(UIColor.randomColor(), forUIControlState: .Normal)
someView.addSubview(button)
button.autoPinEdgeToSuperviewEdge(.Trailing)
button.autoPinEdgeToSuperviewEdge(.Leading)
button.autoMatchDimension(.Height, toDimension: .Height, ofView: someView, withMultiplier: 0.6)
if let previous = prev {
button.autoPinEdge(.Top, toEdge: .Bottom, ofView: previous)
} else {
button.autoPinEdgeToSuperviewEdge(.Top)
}
prev = button
}
someView.autoPinEdgeToSuperviewEdge(.Trailing)
someView.autoPinEdgeToSuperviewEdge(.Leading)
someView.autoPinEdgeToSuperviewEdge(.Top)
someView.autoSetDimension(.Height, toSize: 100)
UPDATE(equal horizontal spacing)
OK based your comment, I got that you also need equal spacing between buttons as well as the side edges of the parent container. where is a full working view controller class:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let wrapper = UIView(frame: CGRectZero)
view.addSubview(wrapper)
wrapper.backgroundColor = UIColor.redColor()
var previous: (button: UIButton, spacer: UIView)?
for day in ["M", "T", "W", "T", "F", "S", "S"] {
let button = UIButton(frame: CGRectZero)
let spacer = UIView(frame: CGRectZero)
button.setTitle(day, forState: .Normal)
button.backgroundColor = UIColor.grayColor()
button.translatesAutoresizingMaskIntoConstraints = false
spacer.translatesAutoresizingMaskIntoConstraints = false
wrapper.addSubview(button)
wrapper.addSubview(spacer)
//set height 60% of wrapper
wrapper.addConstraint(NSLayoutConstraint(
item: button,
attribute: .Height,
relatedBy: .Equal,
toItem: wrapper,
attribute: .Height,
multiplier: 0.6,
constant: 0))
//set width same has height
wrapper.addConstraint(NSLayoutConstraint(
item: button,
attribute: .Height,
relatedBy: .Equal,
toItem: button,
attribute: .Width,
multiplier: 1,
constant: 0))
//pin button leading with spacer trailing
wrapper.addConstraint(NSLayoutConstraint(
item: button,
attribute: .Leading,
relatedBy: .Equal,
toItem: spacer,
attribute: .Trailing,
multiplier: 1,
constant: 0))
//pin spacer trailing with button leading
wrapper.addConstraint(NSLayoutConstraint(
item: spacer,
attribute: .Trailing,
relatedBy: .Equal,
toItem: button,
attribute: .Leading,
multiplier: 1,
constant: 0))
//align button to center
wrapper.addConstraint(NSLayoutConstraint(
item: button,
attribute: .CenterY,
relatedBy: .Equal,
toItem: wrapper,
attribute: .CenterY,
multiplier: 1,
constant: 0))
if let previous = previous {
//pin spacer Leading with previous button trailing
wrapper.addConstraint(NSLayoutConstraint(
item: spacer,
attribute: .Leading,
relatedBy: .Equal,
toItem: previous.button,
attribute: .Trailing,
multiplier: 1,
constant: 0))
//pin previous button's Trailing with spacer Leading
wrapper.addConstraint(NSLayoutConstraint(
item: previous.button,
attribute: .Trailing,
relatedBy: .Equal,
toItem: spacer,
attribute: .Leading,
multiplier: 1,
constant: 0))
//set spacer's width same as previous spacer
wrapper.addConstraint(NSLayoutConstraint(
item: spacer,
attribute: .Width,
relatedBy: .Equal,
toItem: previous.spacer,
attribute: .Width,
multiplier: 1,
constant: 0))
} else {
//this is the first item, pin the spacer Leading to wrapper Leading
wrapper.addConstraint(NSLayoutConstraint(
item: spacer,
attribute: .Leading,
relatedBy: .Equal,
toItem: wrapper,
attribute: .Leading,
multiplier: 1,
constant: 0))
}
previous = (button: button, spacer: spacer)
}
// last spacer
if let previous = previous {
let spacer = UIView(frame: CGRectZero)
spacer.translatesAutoresizingMaskIntoConstraints = false
wrapper.addSubview(spacer)
//pin spacer Leading with previous button trailing
wrapper.addConstraint(NSLayoutConstraint(
item: spacer,
attribute: .Leading,
relatedBy: .Equal,
toItem: previous.button,
attribute: .Trailing,
multiplier: 1,
constant: 0))
//pin previous button's Trailing with spacer Leading
wrapper.addConstraint(NSLayoutConstraint(
item: previous.button,
attribute: .Trailing,
relatedBy: .Equal,
toItem: spacer,
attribute: .Leading,
multiplier: 1,
constant: 0))
//set spacer's width same as previous spacer
wrapper.addConstraint(NSLayoutConstraint(
item: spacer,
attribute: .Width,
relatedBy: .Equal,
toItem: previous.spacer,
attribute: .Width,
multiplier: 1,
constant: 0))
//pin spacer's Trailing with wrappper Trailing
wrapper.addConstraint(NSLayoutConstraint(
item: spacer,
attribute: .Trailing,
relatedBy: .Equal,
toItem: wrapper,
attribute: .Trailing,
multiplier: 1,
constant: 0))
}
wrapper.frame = CGRect(x: 0, y: 100, width: 320, height: 40)
}
}

Related

Dynamically positioning UILabel to bottom of content view with Swift?

I was trying to position my dynamically created UILabels to my contentView's bottom. I'm using HTML Parser called Fuzi to catch HTML tags and creating UILabels based on them;
func stringFromHTML( _ string: String?)
{
do{
let doc = try HTMLDocument(string: string!, encoding: String.Encoding.utf8)
if let root = doc.body {
for element in root.children {
if element.tag == "h2" {
// Create new label
let label = UILabel()
label.text = element.stringValue
label.numberOfLines = 0
label.font = UIFont(name: "Avenir Next", size: 17)
label.textColor = UIColor.black
self.contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
// Label constraints
let labelLeading = NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 20)
let labelTrailing = NSLayoutConstraint(item: label, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -20)
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: self.contentView.frame.origin.y)
self.contentView.addConstraints([labelLeading, labelTrailing, labelTop])
}
updateContentViewHeight()
}
}
}
catch{
print("html error\n",error)
}
}
func updateContentViewHeight(){
var totalContentHeight:CGFloat = 0.0
for i in self.contentView.subviews {
totalContentHeight += i.frame.height
}
let contentViewHeight:NSLayoutConstraint = NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: totalContentHeight-29)
self.contentView.addConstraint(contentViewHeight)
}
I tried calling my stringFromHTML function in viewDidAppear. But it positions my UILabels to far far away (they don't seem).
I want to position my labels to the bottom of the last label, see the image below;
Anyone can help? Thanks in advance!
Change
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: self.contentView.frame.origin.y)
To
if label != nil {
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: label[index], attribute: .top, multiplier: 1, constant: 2)
} else {
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: self.contentView.frame.origin.y)
}
Because you are basically forcing each label to be at the same y position.

swift ios adding constraints for several subviews with a loop

I am newcomer in swift and tried to learn how to add several subviews and their constraints using a loop. I tried to follow some answer to similar questions but it is not working. Could you help me with my code? Thank you in advance.
Here is my code:
let card = UIView()
let view2 = UIView()
view2.backgroundColor = UIColor.greenColor()
view2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view2)
let leftSideConstraint3 = NSLayoutConstraint(item: view2, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1, constant: 0.0)
let topConstraint3 = NSLayoutConstraint(item: view2, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0.0)
let widthConstraint3 = NSLayoutConstraint(item: view2, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0.0)
let heightConstraint3 = NSLayoutConstraint(item: view2, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1, constant: 0.0)
view.addConstraints([leftSideConstraint3, topConstraint3, heightConstraint3, widthConstraint3])
var cards = [UIView](count: 16, repeatedValue: card) // array with 16 cards
card.layer.borderWidth = 3
card.layer.borderColor = UIColor.blackColor().CGColor
card.backgroundColor = UIColor.orangeColor()
var columnCounter:Int = 0
var rowCounter:Int = 0
//Loop through each card in the array
for index in 0...cards.count-1 {
// place the card in the view and turn off translateAutoresizingMask
let thisCard = cards[index]
thisCard.translatesAutoresizingMaskIntoConstraints = false
view2.addSubview(thisCard)
//set the height and width constraints
let widthConstraint = NSLayoutConstraint(item: thisCard, attribute: .Width, relatedBy: .Equal, toItem: view2, attribute: .Width, multiplier: 0.25, constant: 0)
let heightConstraint = NSLayoutConstraint(item: thisCard, attribute: .Height, relatedBy: .Equal, toItem: view2, attribute: .Height, multiplier: 0.25, constant: 0)
view2.addConstraints([heightConstraint, widthConstraint])
//set the horizontal position
if (columnCounter > 0) {
// card is not in the first column
let cardOnTheLeft = cards[index-1]
let leftSideConstraint = NSLayoutConstraint(item: thisCard, attribute: .Left, relatedBy: .Equal, toItem: cardOnTheLeft, attribute: .Right, multiplier: 1, constant: 0)
//add constraint to the contentView
view2.addConstraint(leftSideConstraint)
} else {
//card is in the first column
let leftSideConstraint = NSLayoutConstraint(item: thisCard, attribute: .Left, relatedBy: .Equal, toItem: view2, attribute: .Left, multiplier: 1, constant: 0)
//add constraint to the contentView
view2.addConstraint(leftSideConstraint)
}
//set the vertical position
if (rowCounter > 0) {
// card is not in the first row
let cardOnTop = cards[index-4]
let topConstraint = NSLayoutConstraint(item: thisCard, attribute: .Top, relatedBy: .Equal, toItem: cardOnTop, attribute: .Bottom, multiplier: 1, constant: 0)
// add constraint to the contentView
view2.addConstraint(topConstraint)
} else {
//card is in the first row
let topConstraint = NSLayoutConstraint(item: thisCard, attribute: .Top, relatedBy: .Equal, toItem: view2, attribute: .Top, multiplier: 1, constant: 0)
//add constraint to the contentView
view2.addConstraint(topConstraint)
}
//increment the column counter
columnCounter = columnCounter+1
//if the column counter reaches the fifth column reset it and increase the row counter
if (columnCounter >= 4) {
columnCounter = 0
rowCounter = rowCounter+1
}
} // end of the loop
This creates an array with 16 references to the same card.
var cards = [UIView](count: 16, repeatedValue: card)
Replace it with:
var cards = (1...16).map { _ in UIView() }
which will result in an array with 16 unique UIViews.

Is iOS UILabel constraint from code with margin broken?

I'm trying to implement a popup layout like following:
This works fine in a storyboard with margins and everything. In storyboard it looks like this:
But if I make the same constraint in code I get this result:
The label has a light blue background and the view the label is inside has the dark blue background. The popup background has a border around itself. So basically the popup matches the child but the label inside the child overflows parent and grand parent BUT only because it has margins... If I remove margins it goes right to the border!
I've tryed making the exact same constraint just in code. I'm very open for alternative suggestions involving automatic adjusting width.
My code for creating popup:
func showPopup(caller: UIView) {
closePopups()
// setup view
currentPopup = UIView()
self.view.addSubview(currentPopup)
currentPopup.backgroundColor = UIColorFromHex(Constants.Colors.white, alpha: 1)
// setup constraints
currentPopup.translatesAutoresizingMaskIntoConstraints = false
// top constraint
let topSideConstraint = NSLayoutConstraint(item: currentPopup, attribute: .Top, relatedBy: .Equal, toItem: intoWordsBar.view, attribute: .Bottom, multiplier: 1.0, constant: 0)
self.view.addConstraint(topSideConstraint)
// setup child elements
var children = [PopupChildButton]()
let childOne = createChild("writing_strategy_1", parent: currentPopup, aboveChild: nil, hasBorder: true, feature: FeatureManager.BarFeature.WriteReadLetterName)
children.append(childOne)
let childTwo = createChild("writing_strategy_2", parent: currentPopup, aboveChild: children[0], hasBorder: true, feature: FeatureManager.BarFeature.WriteReadLetterSound)
children.append(childTwo)
let childThree = createChild("writing_strategy_3", parent: currentPopup, aboveChild: children[1], hasBorder: true, feature: FeatureManager.BarFeature.WriteReadWord)
children.append(childThree)
let childFour = createChild("writing_strategy_4", parent: currentPopup, aboveChild: children[2], hasBorder: false, feature: FeatureManager.BarFeature.WriteReadSentence)
children.append(childFour)
let parentSize = getWidth(caller)
//TODO MARK: <-- here working, need to add toggle function and graphics to childrens, documentation on methods, move to constructor class?
// setup rest of constraints
// add bottom constraint, equal to bottom of last child
let bottomSideConstraint = NSLayoutConstraint(item: currentPopup, attribute: .Bottom, relatedBy: .Equal, toItem: children[children.count-1], attribute: .Bottom, multiplier: 1.0, constant: 0)
self.view.addConstraint(bottomSideConstraint)
// left constraint
let leftSideConstraint = NSLayoutConstraint(item: currentPopup, attribute: .Left, relatedBy: .Equal, toItem: caller, attribute: .Right, multiplier: 1.0, constant: (-parentSize)/2)
self.view.addConstraint(leftSideConstraint)
// add border
currentPopup.addBorder(edges: [.All], colour: UIColorFromHex(Constants.Colors.dark_grey, alpha: 1), thickness: 1)
//TODO <-- last piece
//childOne.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
//childTwo.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
//childThree.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
//childFour.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
My code for creating child:
func createChild(text: String, parent: UIView, aboveChild: UIView?, hasBorder: Bool, feature: FeatureManager.BarFeature) -> PopupChildButton {
// setup child element
let childBtn = PopupChildButton()
childBtn.setRelatedFeature(feature)
// set the right background color
if intoWordsBar.getFeatureManager().isFeatureActive(feature) {
childBtn.backgroundColor = UIColorFromHex(Constants.Colors.light_blue, alpha: 1)
//childBtn.setImage(UIImage(named: "Checkmark"))
} else {
childBtn.backgroundColor = UIColorAndAlphaFromHex(Constants.Colors.transparent)//TODO Highlight implementation needs to be optimized, icon should be moved all the way to the left... somehow //TODO Add new checkmark icon
//childBtn.setImage(nil)
}
childBtn.translatesAutoresizingMaskIntoConstraints = false
parent.addSubview(childBtn)
// add constraints
// top constraint
if let aboveChild = aboveChild {
let topSideConstraint = NSLayoutConstraint(item: childBtn, attribute: .Top, relatedBy: .Equal, toItem: aboveChild, attribute: .Bottom, multiplier: 1.0, constant: 0)
parent.addConstraint(topSideConstraint)
} else {
let topSideConstraint = NSLayoutConstraint(item: childBtn, attribute: .Top, relatedBy: .Equal, toItem: parent, attribute: .Top, multiplier: 1.0, constant: 0)
parent.addConstraint(topSideConstraint)
}
// height constraint
let heightConstraint = NSLayoutConstraint(item: childBtn, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: CGFloat(Constants.Sizes.popupChildHeight))
parent.addConstraint(heightConstraint)
// left constraint
let leftSideConstraint = NSLayoutConstraint(item: parent, attribute: .Leading, relatedBy: .Equal, toItem: childBtn, attribute: .Leading, multiplier: 1.0, constant: 0)
parent.addConstraint(leftSideConstraint)
// right constraint
let rightSideConstraint = NSLayoutConstraint(item: parent, attribute: .Trailing, relatedBy: .Equal, toItem: childBtn, attribute: .Trailing, multiplier: 1.0, constant: 0)
parent.addConstraint(rightSideConstraint)
// add border
if hasBorder {
childBtn.addBorder(edges: .Bottom, colour: UIColorFromHex(Constants.Colors.dark_grey, alpha: 1), thickness: 1)
}
// create grandchildren
let label = UILabel()
// setup looks
label.textColor = UIColorFromHex(Constants.Colors.black, alpha: 1)
label.textAlignment = .Center
childBtn.backgroundColor = UIColorFromHex(Constants.Colors.dark_blue, alpha: 1)
label.backgroundColor = UIColorFromHex(Constants.Colors.light_blue, alpha: 1)
label.text = text.localized
label.translatesAutoresizingMaskIntoConstraints = false
childBtn.addSubview(label)
// add constraints
// left constraint label
let leftLabelConstraint = NSLayoutConstraint(item: label, attribute: .Left, relatedBy: .Equal, toItem: childBtn, attribute: .Left, multiplier: 1.0, constant: CGFloat(Constants.Sizes.popupMargin))
childBtn.addConstraint(leftLabelConstraint)
// right constraint label
let rightLabelConstraint = NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .Equal, toItem: childBtn, attribute: .Right, multiplier: 1.0, constant: CGFloat(Constants.Sizes.popupMargin))
childBtn.addConstraint(rightLabelConstraint)
// top constraint
let labelTopSideConstraint = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: childBtn, attribute: .Top, multiplier: 1.0, constant: 0)
childBtn.addConstraint(labelTopSideConstraint)
// bottom constraint
//let labelBottomSideConstraint = NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: childBtn, attribute: .Bottom, multiplier: 1.0, constant: 0)
//childBtn.addConstraint(labelBottomSideConstraint)
return childBtn
}
No, it is not broken.
When defining trailing constraints you must set the parent view as the first item and the child view as the second item. This is in reversed order compared to a leading constraint.
I pulled to constraints from a storyboard to illustrate this. These constraints make sure the header has a 10px margin from leading and trailing of parent view.

Adding a UISearchController and position it with constraints programmatically

I want to add a leading, trailing, bottom and width constraint programmatically to a UISearchController. This is my code:
#IBOutlet weak var navigationBar: UIView!
// create search bar
searchBar = UISearchController(searchResultsController: nil)
navigationBar.addSubview(searchBar.searchBar)
searchBar.searchBar.translatesAutoresizingMaskIntoConstraints = false
let leftConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Leading, relatedBy: .Equal, toItem: navigationBar, attribute: .Leading, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Trailing, relatedBy: .Equal, toItem: navigationBar, attribute: .Trailing, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Bottom, relatedBy: .Equal, toItem: navigationBar, attribute: .Bottom, multiplier: 1, constant: 0)
let heightConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 44)
navigationBar.addConstraints([leftConstraint, rightConstraint, bottomConstraint, widthConstraint])
When running the app, the search bar appears correctly, but when I press on the search bar, it shrinks, and if I press another time the app crashes. Here is the output:
2015-08-12 20:20:37.696 Contacts++[96997:8547485] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7fb22580c7a0 UIView:0x7fb225817b20.leading == UIView:0x7fb223449860.leading>
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView(UIConstraintBasedLayout) _viewHierarchyUnpreparedForConstraint:] to debug.
2015-08-12 20:20:37.697 Contacts++[96997:8547485] *** Assertion failure in -[UIView _layoutEngine_didAddLayoutConstraint:roundingAdjustment:mutuallyExclusiveConstraints:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3491.2.5/NSLayoutConstraint_UIKitAdditions.m:590
Why do you need to create an instance of 'UISearchController' and get its searchBar?
why not just make the searchBar from UISearchBar?
// create a searchBar from UISearchBar
let searchBar = UISearchBar(frame: CGRectZero)
// add searchBar to navigationBar
navigationController?.navigationBar.addSubview(searchBar)
// call sizeToFit.. this will set the frame of the searchBar to exactly the same as the size of the allowable frame of the navigationBar
searchBar.sizeToFit()
// now reframe the searchBar to add some margins
var frame = searchBar.frame
frame.origin.x = 20
frame.size.width -= 40
searchBar.frame = frame // set new frame with margins
note: you won't need any of those constraint to achieve this.
But if you really prefer the constraint, here's the constraint code without a crash.
let searchBar = UISearchBar(frame: CGRectZero)
navigationController?.navigationBar.addSubview(searchBar)
searchBar.setTranslatesAutoresizingMaskIntoConstraints(false)
let leftConstraint = NSLayoutConstraint(item: searchBar, attribute: .Leading, relatedBy: .Equal, toItem: navigationController?.navigationBar, attribute: .Leading, multiplier: 1, constant: 20) // add margin
let bottomConstraint = NSLayoutConstraint(item: searchBar, attribute: .Bottom, relatedBy: .Equal, toItem: navigationController?.navigationBar, attribute: .Bottom, multiplier: 1, constant: 1)
let topConstraint = NSLayoutConstraint(item: searchBar, attribute: .Top, relatedBy: .Equal, toItem: navigationController?.navigationBar, attribute: .Top, multiplier: 1, constant: 1)
let widthConstraint = NSLayoutConstraint(item: searchBar, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: self.view.frame.size.width - 40) // - margins from both sides
navigationController?.navigationBar.addConstraints([leftConstraint, bottomConstraint, topConstraint, widthConstraint])
Do you really want to set the width of the navigatinBar to 44 points? Width is horizontal. You already have a width constraint by adding trailing and leading constraints.

Have view centered and fill available height without going offscreen

I've got a situation where I would like a view to be centered in its superview, remain square, but fill as much height as possible without going off the edge, i.e., it should look at the available vertical and horizontal space, choosing the smallest between the 2.
There are 2 other views, one below and one above, that will both be either a button or label. The bottom/top of these views should be attached to the top/bottom of the central view. I can get this to work, to an extent, but I'll explain my issue below, and what I've got so far:
Top label has:
.Top >= TopLayoutGuide.Bottom
.Top = TopLayoutGuide.Bottom (priority 250)
.Right = CentralView.Right
Central view has:
Center X and Y = Superview Center X and Y
.Height <= Superview.Width * 0.9
.Width = self.Height
.Top = TopLabel.Bottom
Bottom button has:
.Right = CentralView.Right
.Top = CentralView.Bottom
.Bottom <= (BottomLayoutGuide.Top - 16)
Running this seems fine, and produces the desired results:
However, if I make the view an instance of my custom class and add a UIButton subview, it all goes wrong. In this class I perform:
self.topLeftButton = CustomButtonClass()
self.topLeftButton.setTranslatesAutoresizingMaskIntoConstraints(false)
self.addSubview(self.topLeftButton)
self.addConstraints([
NSLayoutConstraint(item: self.topLeftButton, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.topLeftButton, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.topLeftButton, attribute: .Height, relatedBy: .Equal, toItem: self, attribute: .Height, multiplier: 0.5, constant: 0),
NSLayoutConstraint(item: self.topLeftButton, attribute: .Width, relatedBy: .Equal, toItem: self.topLeftButton, attribute: .Width, multiplier: 1, constant: 0)
])
Using this code the view collapses down to the following:
I can't figure out why this is. I've made a few small tweaks here and there, but not managed to get it to work as desired. If I add the same button in IB the view wants to collapse again, and it's as if the button will not grow in height.
In real life I wouldn't subclass UIButton, but have done in my answer, as that is what the question indicated. UIButton works best through composition. So maybe better to create a UIButton, then modify its properties.
class FooViewController: UIViewController {
override func viewDidLoad() {
var view = CustomView()
view.backgroundColor = UIColor.darkGrayColor()
var label = UILabel()
label.text = "Label"
var button = UIButton.buttonWithType(.System) as UIButton
button.setTitle("Button", forState: .Normal)
view.setTranslatesAutoresizingMaskIntoConstraints(false)
label.setTranslatesAutoresizingMaskIntoConstraints(false)
button.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(view)
self.view.addSubview(label)
self.view.addSubview(button)
// The width should be as big as possible...
var maxWidthConstraint = NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .Equal, toItem: view.superview, attribute: .Width, multiplier: 1, constant: 0);
// ... but not at the expense of other constraints
maxWidthConstraint.priority = 1
self.view.addConstraints([
// Max width, if possible
maxWidthConstraint,
// Width and height can't be bigger than the container
NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .LessThanOrEqual, toItem: view.superview, attribute: .Width, multiplier: 1, constant: 0),
NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .LessThanOrEqual, toItem: view.superview, attribute: .Height, multiplier: 1, constant: 0),
// Width and height are equal
NSLayoutConstraint(item: view, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0),
// View is centered
NSLayoutConstraint(item: view, attribute: .CenterX, relatedBy: .Equal, toItem: view.superview, attribute: .CenterX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: view, attribute: .CenterY, relatedBy: .Equal, toItem: view.superview, attribute: .CenterY, multiplier: 1, constant: 0),
])
// Label above view
self.view.addConstraints([
NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .GreaterThanOrEqual, toItem: label.superview, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .LessThanOrEqual, toItem: view, attribute: .Right, multiplier: 1, constant: 0),
])
// Button below view
self.view.addConstraints([
NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .LessThanOrEqual, toItem: button.superview, attribute: .Bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .LessThanOrEqual, toItem: view, attribute: .Right, multiplier: 1, constant: 0),
])
}
}
class CustomView: UIView {
required init(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override init() {
super.init()
var button = CustomButton()
button.setTitle("Custom Button", forState: UIControlState.Normal)
button.setTranslatesAutoresizingMaskIntoConstraints(false)
self.addSubview(button)
// Custom button in the top left
self.addConstraints([
NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 0),
])
}
}
class CustomButton: UIButton {
required init(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override init() {
super.init()
self.backgroundColor = UIColor.greenColor()
}
}

Resources