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.
Related
i'm not very familiar with autolay-out and constraints.
I have build a view with 7 subviews which are build with constraints to fit landscape and portait mode. Everything is fine (see picture).
Here under a sample code used for the 6th subview.
The width constraint has mediumPriority and the others (left, top, bottom) have highPriority.
6View = UIView()
mainScrollView.addSubview(6View)
6View.backgroundColor = UIColor(red: 120/255, green: 120/255, blue: 120/255, alpha: 1.0)
6View.translatesAutoresizingMaskIntoConstraints = false
// constraints
let topConstraint = NSLayoutConstraint(item: 6View, attribute: .top, relatedBy: .equal, toItem: mainScrollView, attribute: .top, multiplier: 1, constant: kMainMargin)
topConstraint.priority = highContraintPriority
mainScrollView.addConstraint(topConstraint)
let leftConstraint = NSLayoutConstraint(item: 6View, attribute: .left, relatedBy: .equal, toItem: day5View, attribute: .right, multiplier: 1, constant: kMainMargin)
leftConstraint.priority = highContraintPriority
mainScrollView.addConstraint(leftConstraint)
let bottomConstraint = NSLayoutConstraint(item: 6View, attribute: .bottom, relatedBy: .equal, toItem: mainScrollView, attribute: .bottom, multiplier: 1, constant: kMainMargin)
bottomConstraint.priority = highContraintPriority
mainScrollView.addConstraint(bottomConstraint)
let widthConstraint = NSLayoutConstraint(item: 6View, attribute: .width, relatedBy: .equal, toItem: mainScrollView, attribute: .width, multiplier: (1/7), constant: -1)
widthConstraint.priority = mediumContraintPriority
mainScrollView.addConstraint(widthConstraint)
Now i'd like when i touch any of the 7 views to see the touched view to expand (double size) and the other 6 views to collapse.
How would you achieve this ?
Thanks a lot for your help.
Best regards.
Make a reference to the width constraint for every view and when clicked do
widthConstraint.constant = // expanded value
and set this for all the other views
otherwidthConstraint.constant = 0
then call
self.view.layoutIfNeeded()
You may also put all these constraints inside an array ( var arrOfAllWidths = [NSLayoutConstraint]()) and do this
arrOfAllWidths.forEach { $0.constant = 0 }
then get the index of the clicked view say it's view 0
arrOfAllWidths[0].constant = // expanded value
Also no need for the priorities , and don't forget to set this when give constraints to a view programmatically
view6.translatesAutoresizingMaskIntoConstraints = false
Hi and thanks in advance. I'm attempting to bind two UIButtons to a UIViewController's view like so:
First by declaring them:
fileprivate var deleteButton: UIButton = UIButton(type: .system)
fileprivate var addButton: UIButton = UIButton(type: .system)
Next in setting them up:
private func setupButtons() {
deleteButton.setTitle("Delete", for: .normal)
addButton.setTitle("Add", for: .normal)
deleteButton.sizeToFit()
addButton.sizeToFit()
deleteButton.alpha = 1
addButton.alpha = 1
view.addSubview(deleteButton)
view.addSubview(addButton)
view.addConstraint(NSLayoutConstraint(item: deleteButton,
attribute: .leading,
relatedBy: .equal,
toItem: view,
attribute: .leading,
multiplier: 1.0,
constant: 0))
view.addConstraint(NSLayoutConstraint(item: deleteButton,
attribute: .bottom,
relatedBy: .equal,
toItem: view,
attribute: .bottom,
multiplier: 1.0,
constant: 0))
view.addConstraint(NSLayoutConstraint(item: addButton,
attribute: .trailing,
relatedBy: .equal,
toItem: view,
attribute: .trailing,
multiplier: 1.0,
constant: 0))
view.addConstraint(NSLayoutConstraint(item: addButton,
attribute: .bottom,
relatedBy: .equal,
toItem: view,
attribute: .bottom,
multiplier: 1.0,
constant: 0))
}
But running the simulator sticks the two UIButtons in the top left, the default CGRect frames assigned to them both.
Might you know what i'm doing wrong? I feel like i'm close but perhaps it has something to do with re-drawing the view?
You should set translatesAutoresizingMaskIntoConstraints to false
// before activate constraint
deleteButton.translatesAutoresizingMaskIntoConstraints = false
addButton.translatesAutoresizingMaskIntoConstraints = false
Also, you have to set isActive = true instead of using addConstraint method
How to give programmatically constraints equal width and equal height with multiple views.I check google but not perfect answer for programmatically equal width and height constraints through auto layout.
my code look like below:
var countNoOfViews:Int = 3
#IBOutlet var viewForRow1: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.specialButtonViewLeft()
}
func specialButtonViewLeft(){
for i in 0..<countNoOfViews{
var customView:UIView!
customView = UIView(frame: CGRect.zero)
customView.translatesAutoresizingMaskIntoConstraints = false
viewForRow1.addSubview(customView)
let widthConstraint = NSLayoutConstraint(item: customView, attribute: .width, relatedBy: .equal,toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 20.0)
let topConstraint = NSLayoutConstraint(item: customView, attribute: .top, relatedBy: .equal,toItem: self.viewForRow1, attribute: .top, multiplier: 1.0, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: customView, attribute: .bottom, relatedBy: .equal,toItem: self.viewForRow1, attribute: .bottom, multiplier: 1.0, constant: 0)
let leadingConstraint = NSLayoutConstraint(item: customView, attribute: .leading, relatedBy: .equal, toItem: self.viewForRow1, attribute: .leading, multiplier: 1, constant: customLeadingSpaceLeft)
customLeadingSpaceLeft = customLeadingSpaceLeft + customViewWidth
arrayLeftBtnConstraints.append(widthConstraint)
if i == 0{
customView.backgroundColor = UIColor.red
}else if i == 1{
customView.backgroundColor = UIColor.green
}else if i == 2{
leftViewVal = customLeadingSpaceLeft
customView.backgroundColor = UIColor.black
}
customView.alpha = 0.50
viewForRow1.addConstraints([widthConstraint, leadingConstraint,topConstraint,bottomConstraint])
}
}
I want to add equal width constraint programmatically.
You have three possible ways to do that:
1) By using anchors:
view.widthAnchor.constraint(equalTo: otherView.widthAnchor, multiplier: 1.0).isActive = true
2) Visual format:
simple example - "H:|[otherView]-[view(==otherView)]|"
3) "Old school" constraints:
NSLayoutConstraint(item: #view, attribute: .width, relatedBy: .equal, toItem: #otherView, attribute: .width, multiplier: 1.0, constant: 0.0).isActive = true
hope it will help somebody.
Try this:
let widthConstraint = NSLayoutConstraint(item: customView, attribute: .width, relatedBy: .equal, toItem: viewForRow1, attribute: .width, multiplier: 0.25, constant: 0.0)
multiplier: 0.25, denotes that customView's width will be 1/4th of the parent view, viewForRow1.
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)
}
}
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()
}
}