Centering button with fixed width breaks NSLayoutConstraint - ios

I have an interesting problem. My app is entirely managed through code instead of storyboards. I have a UIViewController which is presented programmatically. It creates a button and constraints for it.
This is what the controller code looks like.
import Foundation
import UIKit
class CreateViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.layer.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .green
return button
}()
func setupViews() {
self.view.addSubview(button)
}
func setupConstraints() {
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[button]-100-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: ["button": button]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[button(50)]-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: ["button": button]))
}
override func viewDidLoad() {
setupViews()
setupConstraints()
}
}
It throws this error.
(
"<NSLayoutConstraint:0x60000288bb60 UIButton:0x7fa1ab422680.leading == UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide'.leading NSSpace(0) (active)>",
"<NSLayoutConstraint:0x60000288bb10 UIButton:0x7fa1ab422680.width == 50 (active)>",
"<NSLayoutConstraint:0x60000288d7c0 UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide'.trailing == UIButton:0x7fa1ab422680.trailing NSSpace(0) (active)>",
"<NSLayoutConstraint:0x6000028fe990 'UIView-Encapsulated-Layout-Width' UIView:0x7fa1ab4224a0.width == 375 (active)>",
"<NSLayoutConstraint:0x60000288fe30 'UIView-leftMargin-guide-constraint' H:|-(16)-[UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide'](LTR) (active, names: '|':UIView:0x7fa1ab4224a0 )>",
"<NSLayoutConstraint:0x60000288d180 'UIView-rightMargin-guide-constraint' H:[UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide']-(16)-|(LTR) (active, names: '|':UIView:0x7fa1ab4224a0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60000288d7c0 UILayoutGuide:0x6000032da760'UIViewLayoutMarginsGuide'.trailing == UIButton:0x7fa1ab422680.trailing NSSpace(0) (active)>
My aim here is pretty simple. I want the button to be 100 pixels above the bottom, to be centered horizontally, and to have a fixed width and height.
This is how the controller is being instantiated. It exists inside of a
let nav2 = UINavigationController()
let create = CreateViewController()
nav2.viewControllers = [create]
nav2.navigationBar.isTranslucent = false
...
let tabs = UITabBarController()
tabs.viewControllers = [..., nav2, ...]
tabs.delegate = self
Any help in understanding why these constraints are being violated would be greatly appreciated! If any part of the question is unclear, just let me know.
FYI the resulting view looks like this:

Centering an element with Visual Format Language is difficult.
You can easily do it using .centerXAnchor:
class CreateViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
// next line is not needed
// button.layer.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .green
return button
}()
func setupViews() {
self.view.addSubview(button)
}
func setupConstraints() {
NSLayoutConstraint.activate([
// button width of 50
button.widthAnchor.constraint(equalToConstant: 50.0),
// button height of 50
button.heightAnchor.constraint(equalToConstant: 50.0),
// bottom of button 100-pts from bottom of view
// note: contant is negative!
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100),
// center the button horizontally
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
override func viewDidLoad() {
setupViews()
setupConstraints()
}
}

you can try following way to add constraints which is more swifty and cleaner
func setupConstraints() {
let topConstraint = NSLayoutConstraint(item: button,
attribute: .top,
relatedBy: .equal,
toItem: self.view,
attribute: .top,
multiplier: 1,
constant: 100)
let bottomConstraint = NSLayoutConstraint(item: button,
attribute: .left,
relatedBy: .equal,
toItem: self.view,
attribute: .left,
multiplier: 1,
constant: 100)
self.view.addConstraints([topConstraint,bottomConstraint])
}
although I wanted to know why your adding constraints to the button ,when you can declare x and y at the time of initialisation. if you need help in that I've added the code for it too where the button is at center, hope it helps.
lazy var button: UIButton = {
let button = UIButton()
button.frame = CGRect(x: UIScreen.main.bounds.width/2-25, y: UIScreen.main.bounds.height/2-25, width: 50, height: 50)
button.backgroundColor = .green
return button
}()

Related

Self sizing cell with multiple Stack Views

I've looked all over the forum and attempted all the solutions and thus far nothing has worked. I noticed my UIImageView was overlaying multiple cells, meaning the celll did not automatically adjust its height. Here is the constraint i found in the console it complained about.
"<NSLayoutConstraint:0x600001970f50 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7f86a4813dd0.height == 44 (active)>"
In my tableViewController I have the follow
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 300
Here is my entire cell that should self size.
import UIKit
class UserConnectionCell: UITableViewCell {
fileprivate let leftImageView: UIImageView = {
let uiImageView = UIImageView()
uiImageView.translatesAutoresizingMaskIntoConstraints = false
return uiImageView
}()
fileprivate let leftLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
fileprivate let middleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont(name: "Ariel", size: 10)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
fileprivate let rightImageView: UIImageView = {
let uiImageView = UIImageView()
uiImageView.translatesAutoresizingMaskIntoConstraints = false
return uiImageView
}()
fileprivate let rightLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
fileprivate let stackViewLeft: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
fileprivate let stackViewRight: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
fileprivate let stackViewMain: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.spacing = 0
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
//
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier:reuseIdentifier)
stackViewLeft.addArrangedSubview(leftImageView)
stackViewLeft.addArrangedSubview(leftLabel)
stackViewRight.addArrangedSubview(rightImageView)
stackViewRight.addArrangedSubview(rightLabel)
stackViewMain.addArrangedSubview(stackViewLeft)
stackViewMain.addArrangedSubview(middleLabel)
stackViewMain.addArrangedSubview(stackViewRight)
contentView.addSubview(stackViewMain)
}
// called when trying to layout subviews.
override func layoutSubviews() {
super.layoutSubviews()
stackViewLeft.addConstraint(NSLayoutConstraint(item: leftImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 100))
stackViewLeft.addConstraint(NSLayoutConstraint(item: leftImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 100))
stackViewRight.addConstraint(NSLayoutConstraint(item: rightImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 100))
stackViewRight.addConstraint(NSLayoutConstraint(item: rightImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 100))
NSLayoutConstraint.activate(
[stackViewMain.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 0),
stackViewMain.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 0),
stackViewMain.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: 0),
stackViewMain.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var viewModel : UserConnectionViewModel? {
didSet {
// move this to the view model
if let profileUrl = viewModel?.leftImageUrl {
leftImageView.loadImageFromURL(url: profileUrl)
} else {
leftImageView.image = UIImage(named: "defaultprofile")
}
if let profileUrl = viewModel?.rightImageUrl {
rightImageView.loadImageFromURL(url: profileUrl)
} else {
rightImageView.image = UIImage(named: "defaultprofile")
}
leftLabel.text = viewModel?.leftLabel
middleLabel.text = viewModel?.middleLabel
rightLabel.text = viewModel?.rightlabel
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.autoresizingMask = .flexibleHeight
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Any ideas for why the cell is not self sizing?
First, a cell's contentView is a "special" view with properties integral to the table view's operation.
So, do not do this:
self.contentView.autoresizingMask = .flexibleHeight
Second, layoutSubviews() can be (and usually is) called multiple times during the lifecycle of a cell / view. Your constraint setup should be done in init:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier:reuseIdentifier)
stackViewLeft.addArrangedSubview(leftImageView)
stackViewLeft.addArrangedSubview(leftLabel)
stackViewRight.addArrangedSubview(rightImageView)
stackViewRight.addArrangedSubview(rightLabel)
stackViewMain.addArrangedSubview(stackViewLeft)
stackViewMain.addArrangedSubview(middleLabel)
stackViewMain.addArrangedSubview(stackViewRight)
contentView.addSubview(stackViewMain)
NSLayoutConstraint.activate([
// constrain main stack view to all 4 sides of contentView
stackViewMain.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 0),
stackViewMain.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 0),
stackViewMain.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: 0),
stackViewMain.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0),
// constrain left image view Width: 100-pts,
// Height equal to Width (1:1 ratio)
leftImageView.widthAnchor.constraint(equalToConstant: 100.0),
leftImageView.heightAnchor.constraint(equalTo: leftImageView.widthAnchor),
// constrain right image view Width: 100-pts,
// Height equal to Width (1:1 ratio)
rightImageView.widthAnchor.constraint(equalToConstant: 100.0),
rightImageView.heightAnchor.constraint(equalTo: rightImageView.widthAnchor),
])
}
So... replace your init with the above code and completely remove both your awakeFromNib() and layoutSubviews() funcs.
You should get this:

How to add "inner padding" into custom view so that its size will become smaller visually yet able retain its touchable area?

I have the a custom view with the following simple constraint
Height 88
Width 88
Center X
Center Y
I tend to make it as circle visually. Here's my code.
extension UIView {
func asCircle() {
self.layer.cornerRadius = self.frame.width / 2;
self.layer.masksToBounds = true
}
}
class ViewController: UIViewController {
#IBOutlet weak var circleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
circleView.backgroundColor = .red
circleView.asCircle()
// 1. create a gesture recognizer (tap gesture)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
// 2. add the gesture recognizer to a view
circleView.addGestureRecognizer(tapGesture)
// Currently, the touchable area of circleView is 88 x 88
}
// 3. this method is called when a tap is recognized
#objc func handleTap(sender: UITapGestureRecognizer) {
print("tap")
}
}
What I would like to have is
The touchable area of the circle remains 88 x 88
The red circle however will look visually as 22 x 22
I add the following code, but it doesn't make any change
// We use 33, because 88-33-33 = 22
circleView.layoutMargins = UIEdgeInsets(top: 33, left: 33, bottom: 33, right: 33)
Does anyone know how to achieve so? Preferable without overriding draw function, or add an additional UIView as subview.
Add layer into your circleView and clear the circleView
example:
extension UIView {
func asCircle() {
self.layer.cornerRadius = self.frame.width / 2;
self.layer.masksToBounds = true
}
}
class ViewController: UIViewController {
var circleView: UIView = {
let circle = UIView()
circle.translatesAutoresizingMaskIntoConstraints = false
return circle
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.circleView)
circleView.backgroundColor = .clear
self.circleView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
self.circleView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
let width = NSLayoutConstraint(item: self.circleView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1.0, constant: 88)
let height = NSLayoutConstraint(item: self.circleView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1.0, constant: 88)
self.circleView.addConstraints([width, height])
self.circleView.layoutIfNeeded()
// 1. create a gesture recognizer (tap gesture)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
// 2. add the gesture recognizer to a view
circleView.addGestureRecognizer(tapGesture)
// Currently, the touchable area of circleView is 88 x 88
}
// 3. this method is called when a tap is recognized
#objc func handleTap(sender: UITapGestureRecognizer) {
print("tap")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let layer = CALayer()
layer.backgroundColor = UIColor.blue.cgColor
layer.frame = CGRect(x: 0, y: 0, width: 22, height: 22)
self.circleView.layer.insertSublayer(layer, at: 1)
layer.cornerRadius = layer.frame.height / 2
layer.position = CGPoint(x: self.circleView.frame.height / 2, y: self.circleView.frame.height / 2)
}
}

Adding a UILabel to a subview is making the view it disappear

I am trying to create the following view heirarchy
Base UIView-> UIScrollView -> UIView -> UILabel
In the Storyboard I created the view->UIScrollView and made the Scrollview and the base view the same size and aligned their origins.
I am then adding the subsequent UIView -> UILabels in code.
In the ViewController viewDidLoad() function I am adding the UIView and the UILabel and also setting contraints on the UIScrollView.
I am having trouble adding the UILabel. If I just add the UIView to the scrollView its working fine (I gave them different colors just to see that).
As soon as I add the UILabel to the view I see the following issues --
the UIView seems to be disappearing and the label is getting added to the base View.
The UILabel is always getting added to the top corner of the screen. No matter what I do its not changing.
I have a feeling these two issues are connected and I am not adding the constraints on the label properly.
It will be great if someone can point out the error in my code.
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.orange
let boxViewOrigin = CGPoint(x:0, y:0)
let boxViewSize = CGSize(width: view.bounds.width * 3, height: view.bounds.height * 3)
let boxView = UIView(frame: CGRect(origin: boxViewOrigin, size: boxViewSize))
boxView.backgroundColor = UIColor.blue
boxView.translatesAutoresizingMaskIntoConstraints = false
let scrollView = view.subviews[0] as! UIScrollView
scrollView.addSubview(boxView)
// Add contraints
scrollView.contentSize = boxViewSize //CGSize(width: view.bounds.width * 3, height: view.bounds.height * 3)
scrollView.contentOffset = CGPoint(x: (view.bounds.origin.x + view.bounds.width) , y: (view.bounds.origin.y + view.bounds.height) )
/// ---- Commenting out everything from here to the end of the function makes the UIView appear properly. ------
let nameLabel = UILabel()
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.backgroundColor = UIColor.yellow
let nameLabelOrigin = CGPoint(x: boxView.bounds.midX, y: boxView.bounds.midY )
nameLabel.frame = CGRect(origin: nameLabelOrigin, size: CGSize(width: 30, height: 30) )
nameLabel.text = "BB"
//nameLabel.isEnabled = false
nameLabel.isUserInteractionEnabled = false
let verticalConstraint:NSLayoutConstraint = NSLayoutConstraint(item: nameLabel, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: boxView, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0)
let horizontalConstraint:NSLayoutConstraint = NSLayoutConstraint(item: nameLabel, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: boxView, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
boxView.addSubview(nameLabel)
view.addConstraints([verticalConstraint, horizontalConstraint])
}

Custom View size in iOS not dynamic

I just have started my first app on iOS a week ago. I have created a custom view to use it in my app using AppleDeveloperWebsite Custom Rating Control Tutorial.
Now I have chosen iPhone7 device in storyboard and I run this on iPhone 7 emulator it works perfectly but when I run it on iPhone 5 emulator (size of screen changes) my custom views extend beyond the screen. My all other controls sizes resize as I set constraints but only my custom view sizes get messed up.
Please Help
import UIKit
#IBDesignable class xRadioButtonView: UIView {
var button: UIButton!
var label: UILabel!
#IBInspectable var text: String? {
didSet{
label.text = text
}
}
//Properties
var isSelected = 0
//Initialization
override init(frame: CGRect){
super.init(frame: frame)
addSubviews()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
addSubviews()
}
func addSubviews() {
self.backgroundColor = UIColor.white
let xWidth = bounds.size.width
let xHeight = bounds.size.height
let tap = UITapGestureRecognizer(target: self, action: #selector(xRadioButtonView.radioButtonTextTapped))
button = UIButton(frame: CGRect(x: 1, y: 1, width: xWidth - 2, height: xHeight - 4))
button.backgroundColor = UIColor.white
button.addTarget(self, action: #selector(xRadioButtonView.radioButtonTapped(button:)), for: .touchDown)
addSubview(button)
label = UILabel(frame: CGRect(x: 1, y: 1, width: xWidth - 2, height: xHeight - 2))
label.textColor = UIColor.init(hex: "#D5D5D5")
//label.font = UIFont.init(name: label.font.fontName, size: 25)
//label.font = label.font.withSize(25)
label.font = UIFont.boldSystemFont(ofSize: 25)
label.textAlignment = NSTextAlignment.center
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tap)
addSubview(label)
}
override func layoutSubviews() {
// Set the button's width and height to a square the size of the frame's height.
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
label.text = "xRBV"
}
func radioButtonTapped(button: UIButton) {
if isSelected == 0 {
isSelected = 1
self.backgroundColor = UIColor.init(hex: "#00BFA5")
label.textColor = UIColor.init(hex: "#00BFA5")
} else {
isSelected = 0
self.backgroundColor = UIColor.white
label.textColor = UIColor.init(hex: "#D5D5D5")
}
}
func radioButtonTextTapped(sender: UITapGestureRecognizer){
if isSelected == 0 {
isSelected = 1
self.backgroundColor = UIColor.init(hex: "#00BFA5")
label.textColor = UIColor.init(hex: "#00BFA5")
} else {
isSelected = 0
self.backgroundColor = UIColor.white
label.textColor = UIColor.init(hex: "#D5D5D5")
}
}
}
As you can see PG button should finish where green color finishes but white color button is extended beyond the screen
You either need to set the frame in layoutSubViews or you need to implement autolayout in code:
button = UIButton(frame: CGRect(x: 1, y: 1, width: xWidth - 2, height: xHeight - 4))
button.backgroundColor = UIColor.white
button.addTarget(self, action: #selector(xRadioButtonView.radioButtonTapped(button:)), for: .touchDown)
addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
let attributes: [NSLayoutAttribute] = [.top, .bottom, .leading, .trailing]
let constants = [2, 2, 10, 10] // 2 from top and bottom, 10 from leading and trailing
NSLayoutConstraint.activate(attributes.enumerated().map { NSLayoutConstraint(item: button, attribute: $1, relatedBy: .equal, toItem: button.superview, attribute: $1, multiplier: 1, constant: constants[$0]) })
The example uses the old way because your constraints are uniform, but if you have something more complicated its often simpler to use NSLayoutAnchor as of iOS 9.
EDIT: here is the code for tuples if anyone is interested:
button.translatesAutoresizingMaskIntoConstraints = false
let attributes: [(NSLayoutAttribute, CGFloat)] = [(.top, 2), (.bottom, 2), (.leading, 12), (.trailing, 12)]
NSLayoutConstraint.activate(attributes.map { NSLayoutConstraint(item: button, attribute: $0.0, relatedBy: .equal, toItem: button.superview, attribute: $0.0, multiplier: 1, constant: $0.1) })
Thanks I wasn't even aware of this NSLayout yet. (HEHE 7 Days) Thanks to you I have a solution for my problem. Although I wanted different values for .top .bottom .leading .trailling
I used your code like this
NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal, toItem: button.superview, attribute: .top, multiplier: 1, constant: 1).isActive = true
NSLayoutConstraint(item: button, attribute: .bottom, relatedBy: .equal, toItem: button.superview, attribute: .bottom, multiplier: 1, constant: 4).isActive = true
to all 4 sides. But is there a way to provide constant values as well like you have provided multiple attribute values?

Swift make percentage of width programmatically

I am programmatically creating multiple buttons in Swift for a UITableViewCell, and would like to set their widths to be a certain percentage width of the UITableViewCell so that they use up all of the space. Right now I am creating the buttons in a for loop (I want to be able to create a variable amount of buttons) and am using
button.buttonWidth = self.contentView.frame.width / numberOfButtons
where button is a UIButton and self is a UITableViewCell and numberOfButtons is obviously the number of buttons. I have also tried:
button.buttonWidth = self.frame.size.width / numberOfButtons
and every combination of using/not using .size and using/not using contentView. These don't seem to work, however, as the buttons are too big. Anyone know how to accomplish this?
NOTE: I CAN'T use Autolayout in the storyboard, as I'm creating a variable number of buttons.
You say:
NOTE: I CAN'T use Autolayout in the storyboard, as I'm creating a variable number of buttons.
You can't add the variable number of buttons right in Interface Builder, but there's nothing to stop you from using autolayout. The basic autolayout constraints you need to set up are:
Set buttons to have same width;
Set buttons to have their leading constraint connected to the previous button's trailing constraint;
The first button should have its leading constraint connected to the container view; and
The last button should have its trailing constraint connected to the container, too.
So, for example, in Swift 3, you could do something like:
var previousButton: UIButton?
for _ in 0 ..< count {
let button = ...
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
// if no "previous" button, hook leading constraint to its superview;
// otherwise hook leading constraint and width to previous button
if previousButton == nil {
button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 5).isActive = true
} else {
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: previousButton!.trailingAnchor, constant: 5),
button.widthAnchor.constraint(equalTo: previousButton!.widthAnchor, constant: 5)
])
}
// add vertical constraints
NSLayoutConstraint.activate([
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 50),
view.bottomAnchor.constraint(equalTo: button.bottomAnchor, constant: 5)
])
previousButton = button
}
// make sure to hook last button's trailing anchor to superview, too
view.trailingAnchor.constraint(equalTo: previousButton!.trailingAnchor, constant: 5).isActive = true
Or, in Swift 2:
var previousButton: UIButton?
for _ in 0 ..< buttonCount {
let button = ...
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
// if no "previous" button, hook leading constraint to its superview;
// otherwise hook leading constraint and width to previous button
if previousButton == nil {
button.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 5).active = true
} else {
NSLayoutConstraint.activateConstraints([
button.leadingAnchor.constraintEqualToAnchor(previousButton!.trailingAnchor, constant: 5),
button.widthAnchor.constraintEqualToAnchor(previousButton!.widthAnchor, constant: 5)
])
}
// add vertical constraints
NSLayoutConstraint.activateConstraints([
button.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 5),
view.bottomAnchor.constraintEqualToAnchor(button.bottomAnchor, constant: 5)
])
previousButton = button
}
// make sure to hook last button's trailing anchor to superview, too
view.trailingAnchor.constraintEqualToAnchor(previousButton!.trailingAnchor, constant: 5).active = true
And, instead of fixed spacing between the buttons, you also could use UILayoutGuide, too (making the guides the same size as each other but a percent of the width of the buttons, achieving a more natural spacing regardless of the width of the superview).
You also can use UIStackView, which is another great way to get controls evenly spaced in a view.
Bottom line, there are many ways to achieve this using autolayout.
Maybe because you're doing this before the cell layouts its subviews and it's size is the same as size set in Any-Any size class 600 * 600.
Try doing adding your buttons in layoutSubviews method, like this:
override func layoutSubviews() {
super.layoutSubviews()
self.layoutIfNeeded()
// add your buttons here
}
Hi there is multiple ways of solving your problem:
Using Autolayout, you can using Autolayout in code and in StoryBoard ;-). Bear with me, I didn't check the code of Autolayout but the logic is hereā€¦
let button1: UIButton!, button2: UIButton!, button3: UIButton!
convenience init() {
self.init(frame:CGRectZero)
contentView.addSubview(button1)
contentView.addSubview(button2)
contentView.addSubview(button3)
button1.translatesAutoresizingMaskIntoConstraints = false
button2.translatesAutoresizingMaskIntoConstraints = false
button3.translatesAutoresizingMaskIntoConstraints = false
addConstraint(
item: button1,
attribute: .Left,
relatedBy: .Equal,
toItem: self,
attribute: .Left,
multiplier: 1,
constant: 8)
)
addConstraint(
item: button2,
attribute: .Left,
relatedBy: .Equal,
toItem: button1,
attribute: .Right,
multiplier: 1,
constant: 8)
)
addConstraint(
item: button1,
attribute: .Left,
relatedBy: .Equal,
toItem: button2,
attribute: .Right,
multiplier: 1,
constant: 8)
}
Source: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/
Using Snapkit
let button1: UIButton!, button2: UIButton!, button3: UIButton!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
button1 = UIButton()
button1.setTitle("Button 1", forState: .Normal)
contentView.addSubview(button1)
button1.snp_makeConstraints { (make) in
make.bottom.equalTo(contentView.snp_botton)
make.left.equalTo(contentView.snp_left)
make.height.equalTo(20)
}
button2 = UIButton()
button2.setTitle("Button 2", forState: .Normal)
contentView.addSubview(button2)
button2.snp_makeConstraints { (make) in
make.bottom.equalTo(contentView.snp_botton)
make.left.equalTo(button2.snp_right)
make.height.equalTo(20)
}
button1 = UIButton()
button1.setTitle("Button 3", forState: .Normal)
contentView.addSubview(button2)
button1.snp_makeConstraints { (make) in
make.bottom.equalTo(contentView.snp_botton)
make.left.equalTo(button2.snp_right)
make.height.equalTo(20)
}
}
Source: http://snapkit.io/docs/
My preferred version is Snapkit because it is less verbose than Autolayout.
UIStackView is exactly what you need. Check out my tutorial below:
http://www.raizlabs.com/dev/2016/04/uistackview/

Resources