Adding constraints to UITableViewCell contentView - ios

I am trying to add constraints to tableViewCellSubViews, like so -
import UIKit
class SnakeTableViewCell: UITableViewCell {
var lessonViews = Array<UIView>()
override func awakeFromNib() {
super.awakeFromNib()
for var i = 0; i < 3; ++i
{
var view = UIView(frame: CGRectMake(CGFloat(i) * 110.0, 0.0, 100.0, 100.0))
view.backgroundColor = UIColor.redColor()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
self.contentView.addSubview(view)
lessonViews.append(view)
}
self.addConstraints(NSLayoutConstraint.constraintsForEvenDistributionOfViews(lessonViews, relativeToCenterOfView: self, vertically: false))
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
And the constraints code -
extension NSLayoutConstraint {
class func constraintsForEvenDistributionOfViews(views:Array<UIView>,relativeToCenterOfView toView:UIView, vertically:Bool ) -> Array<NSLayoutConstraint> {
var constraints = Array<NSLayoutConstraint>()
let attribute = vertically ? NSLayoutAttribute.CenterY : NSLayoutAttribute.CenterX
for (index, view) in enumerate(views) {
let multiplier = CGFloat(2*index + 2) / CGFloat(views.count + 1)
let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: NSLayoutRelation.Equal, toItem:toView, attribute: attribute, multiplier: multiplier, constant: 0)
constraints.append(constraint)
}
return constraints
}
}
The issue is that when I add the constraints, all the subviews disappears.
Any idea what am I doing wrong ?
Thanks

The multiplier generally is 1. The constant is the variable (horizontal or vertical amount.
You can setup your custom cell, its evenly distributed subViews, and its constraints, all within Storyboard. It's much simpler to do it in Interface Builder, than creating and constraining views in code.
You simply dequeue a reusable cell, and it's already got its properly spaced views, because the cell instantiated and spaced them for you.

You can add constraints to the contentView of a UITableViewCell in init(style:, reuseIdentifier:) as follows (in this case adjusting with an inset of 5pt):
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 5).isActive = true
contentView.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
contentView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -5).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -5).isActive = true
It works, but... the system will complain:
Changing the translatesAutoresizingMaskIntoConstraints property of the contentView of a UITableViewCell
is not supported and will result in undefined behavior
The warning appears only one time (even though I have multiple cells)
I am doing this to add an inner border and a shadow (without using additional views that will affect the smooth scroll). But the 'not supported' and 'undefined behavior' are things you may consider if you want to use this in a production environment.

Related

Which lifecycle event to use for declaring layout constraints in a child view controller?

I have a simple parent view controller and I'm adding a child view controller to it. Here's the parent:
class ParentViewController: UIViewController {
private var childViewController: ChildViewController!
override func viewDidLoad() {
super.viewDidLoad()
childViewController = ChildViewController()
addChild(childViewController)
view.addSubview(childViewController.view)
childViewController.didMove(toParent: self)
NSLayoutConstraint.activate([
childViewController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100),
childViewController.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 50),
childViewController.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -50),
childViewController.view.heightAnchor.constraint(equalToConstant: 100)
])
}
}
The child view controller is declared like this:
class ChildViewController: UIViewController {
private let label1: UILabel = {
let label = UILabel()
label.text = "First label"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let label2: UILabel = {
let label = UILabel()
label.text = "Second label"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
view.translatesAutoresizingMaskIntoConstraints = false
setupSubViews()
}
private func setupSubViews() {
view.addSubview(label1)
view.addSubview(label2)
print("view.frame.size: \(view.frame.size)")
NSLayoutConstraint.activate([
label1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8),
label1.centerYAnchor.constraint(equalTo: view.topAnchor, constant: self.view.frame.size.height * (1/3)),
label2.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8),
label2.centerYAnchor.constraint(equalTo: view.topAnchor, constant: self.view.frame.size.height * (2/3)),
])
}
}
Running the code produces the following:
The position of the two labels are obviously not what I intended. What I'm trying to do in the child view controller is define centerYAnchor constraints for the two labels using the height of the child view controller's view after it has been positioned in the parent view controller. If I print out the value of view.frame.size inside of setupSubViews() in my child view controller, the size of the view is the entire screen (428.0 x 926.0, in my case). Presumably, this is because the child view controller's view hasn't been fully loaded/positioned in the parent view controllers view yet?
So I moved the call to setupSubViews() into viewDidLayoutSubviews() of the child view controller and then the value of view.frame.size is correct (328.0 x 100.0) and the labels are positioned correctly within the child view controller's view. But I do see viewDidLayoutSubviews() being called multiple times, though, so I'm wondering if that's really the "correct" lifecycle method for declaring constraints like this? I've seen some people suggest using a boolean to ensure that the constraint code only runs once but I'm not sure that's the right way to handle this situation either.
The constraint you create this way must be updated whenever the height of the view changes, which can happen multiple times. When this happens, viewDidLayoutSubviews gets called. So it is not inappropriate to put the code to update the constraints' constants in viewDidLayoutSubviews, just because it is called multiple times. You don't want to set the constraints' constants only on the first time the height of the view changes, right?
Though, note that you should just update the constraint's constants in viewDidLayoutSubviews, and not call setupSubview there. You should call setupSubview in loadView, not viewDidLoad, since you are building the view programmatically. See What is the difference between loadView and viewDidLoad?
In fact, there is a much better of doing this. Rather than making the constraints constant, you can constraint the label's center Y to the view's center Y with a multiplier. Label 1 will have a multiplier of 2/3, and label 2 will have 4/3. This will make them one third, and two thirds of the way from the top of the view respectively,
NSLayoutConstraint(
item: label1,
attribute: .centerY,
relatedBy: .equal,
toItem: view,
attribute: .centerY,
multiplier: 2/3,
constant: 0),
NSLayoutConstraint(
item: label2,
attribute: .centerY,
relatedBy: .equal,
toItem: view,
attribute: .centerY,
multiplier: 4/3,
constant: 0),

Remove trailing constraint with ib action- Swift

I have created a View Controller with a segmented controller at the top. When you tap the segmented controller it just acts as a button and changes whether the imageView inside of the controller is portrait mode or in landscape mode just by calling a function that changes it to the according dimensions.
My problem is that the way I made it change is I just added contraints to the imageView, but when changing to landscape mode the trailing constraint doesn't get removed.
And, this is the code to change the imageView to portrait (The imageView already has the top and bottom contraints on it) :
func portraitContraints() {
NSLayoutConstraint.activate ([
// the trailing contraint
imageView.trailingAnchor.constraint(equalTo: viewContainer.safeAreaLayoutGuide.trailingAnchor, constant: -75), // this is the contraints that doesn't get removed
// the leading contraints
imageView.leadingAnchor.constraint(equalTo: viewContainer.safeAreaLayoutGuide.leadingAnchor, constant: 75
])
// the aspect ratio contraints
imageView.addConstraint(NSLayoutConstraint(item: self.imageView as Any,attribute: .height,relatedBy: .equal,toItem: self.imageView,attribute: .width,multiplier: (4.0 / 3.0),constant: 0))
}
This is the code to change the imageView to landscape:
func landscapeContraints() {
NSLayoutConstraint.activate([
// the trailing contraints
imageView.trailingAnchor.constraint(equalTo: viewContainer.safeAreaLayoutGuide.trailingAnchor, constant: 0),
// the leading contraint
imageView.leadingAnchor.constraint(equalTo: viewContainer.safeAreaLayoutGuide.leadingAnchor, constant: 0)
])
// the aspect ratio contraint
imageView.addConstraint(NSLayoutConstraint(item: self.imageView as Any,attribute: .height,relatedBy: .equal,toItem: self.imageView,attribute: .width,multiplier: (9.0 / 16.0),constant: 0))
}
This code works like a charm, except the only problem is that the trailing constraint from the portrait mode will stay on the view (the -75 constant constraint).
Landscape looks like this (notice the right side constant is -75):
Portrait looks like this:
You can do this by adding both ratio constraints, with different priorities. Change the priorities to make the desired ratio "active".
And, create leading and trailing constraint vars, so you can change their constants.
Here's a quick example:
class ToggleConstraintsViewController: UIViewController {
let imageView = UIImageView()
var isPortrait: Bool = true
var portraitConstraint: NSLayoutConstraint!
var landscapeConstraint: NSLayoutConstraint!
var leadingConstraint: NSLayoutConstraint!
var trailingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
imageView.backgroundColor = .blue
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
let g = view.safeAreaLayoutGuide
portraitConstraint = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 4.0/3.0)
portraitConstraint.priority = .defaultHigh
landscapeConstraint = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 9.0/16.0)
landscapeConstraint.priority = .defaultLow
leadingConstraint = imageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 75.0)
trailingConstraint = imageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -75.0)
NSLayoutConstraint.activate([
// y-position constraint does not change
imageView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// these will have their priorities changed when desired
portraitConstraint,
landscapeConstraint,
// these will have their constants changed when desired
leadingConstraint,
trailingConstraint,
])
let t = UITapGestureRecognizer(target: self, action: #selector(self.toggleOrientation(_:)))
view.addGestureRecognizer(t)
}
#objc func toggleOrientation(_ g: UITapGestureRecognizer) -> Void {
isPortrait.toggle()
portraitConstraint.priority = isPortrait ? .defaultHigh : .defaultLow
landscapeConstraint.priority = isPortrait ? .defaultLow : .defaultHigh
leadingConstraint.constant = isPortrait ? 75.0 : 0.0
trailingConstraint.constant = isPortrait ? -75.0 : 0.0
}
}
Each time you tap the view, the imageView's width:height ratio will change, and the leading/trailing constraint constants will change.
Try removing the old constraints before adding in the new ones. Here's Apples documentation on how to do that: https://developer.apple.com/documentation/uikit/uiview/1622593-removeconstraints

How to embed a UIViewController's view into another view without breaking responder chain

I'm trying to implement a base class for View Controllers that will add a top banner that is shown and hidden based on a specific status. Because my UIViewController extend this base class the view property is already loaded by the time I have to embed the original view property into a new view property that is created programmatically.
The embedding works fine (UI wise) the views adjust themselves appropriately however the issue that I'm seeing is that after embedding it the embedded views no longer respond to touch events, in the controller that I'm originally embedding I've got a table view with 16 rows and a button, before embedding it they both respond to tap and scroll events correctly.
I'm constrained to the fact that I cannot use a split view controller in IB to achieve the dual view split.
Here is my current implementation, can't seem to figure out what I'm missing to have event's propagate, I've tried using a custom view that overrides hitTest() to no avail for both newRootView and contentView variables.
Any help or insights are really appreciated, thanks!
class BaseViewController: UIViewController {
var isInOfflineMode: Bool {
didSet { if isInOfflineMode { embedControllerView() }}
}
var offlineBanner: UIView!
var contentView: UIView!
private func embedControllerView() {
guard let currentRoot = viewIfLoaded else { return }
currentRoot.translatesAutoresizingMaskIntoConstraints = false
let newRootView = UIView(frame: UIScreen.main.bounds)
newRootView.backgroundColor = .yellow // debugging
newRootView.isUserInteractionEnabled = true
newRootView.clipsToBounds = true
view = newRootView
offlineBanner = createOfflineBanner() // returns a button that I've verified to be tapable.
view.addSubview(offlineBanner)
contentView = UIView(frame: .zero)
contentView.backgroundColor = .cyan // debugging
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.isUserInteractionEnabled = true
view.addSubview(contentView)
contentView.addSubview(currentRoot)
let bannerHeight: CGFloat = 40.00
var topAnchor: NSLayoutYAxisAnchor
var bottomAnchor: NSLayoutYAxisAnchor
var trailingAnchor: NSLayoutXAxisAnchor
var leadingAnchor: NSLayoutXAxisAnchor
if #available(iOS 11.0, *) {
topAnchor = view.safeAreaLayoutGuide.topAnchor
bottomAnchor = view.safeAreaLayoutGuide.bottomAnchor
leadingAnchor = view.safeAreaLayoutGuide.leadingAnchor
trailingAnchor = view.safeAreaLayoutGuide.trailingAnchor
} else {
topAnchor = view.topAnchor
bottomAnchor = view.bottomAnchor
leadingAnchor = view.leadingAnchor
trailingAnchor = view.trailingAnchor
}
NSLayoutConstraint.activate([
offlineBanner.heightAnchor.constraint(equalToConstant: bannerHeight),
offlineBanner.topAnchor.constraint(equalTo: topAnchor),
offlineBanner.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0),
offlineBanner.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
])
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor, constant: bannerHeight),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
])
OfflineViewController.migrateViewContraints(from: currentRoot, to: contentView)
view.setNeedsUpdateConstraints()
offlineBanner.setNeedsUpdateConstraints()
currentRoot.setNeedsUpdateConstraints()
}
private func unembedControllerView() {
let v = contentView.subviews[0]
v.removeFromSuperview()
view = v
OfflineViewController.migrateViewContraints(from: contentView, to: v)
}
/**
Replaces any constraints associated with the current root's safe area`UILayoutGuide` or with the actual
current root view.
*/
private static func migrateViewContraints(from currentRoot: UIView, to newRoot: UIView) {
for ct in currentRoot.constraints {
var firstItem: Any? = ct.firstItem
var secondItem: Any? = ct.secondItem
if #available(iOS 11.0, *) {
if firstItem as? UILayoutGuide == currentRoot.safeAreaLayoutGuide {
debugPrint("Migrating firstItem is currentLayoutGuide")
firstItem = newRoot.safeAreaLayoutGuide
}
if secondItem as? UILayoutGuide == currentRoot.safeAreaLayoutGuide {
debugPrint("Migrating secondItem is currentLayoutGuide")
secondItem = newRoot.safeAreaLayoutGuide
}
}
if firstItem as? UIView == currentRoot {
debugPrint("Migrating firstItem is currentRoot")
firstItem = newRoot
}
if secondItem as? UIView == currentRoot {
debugPrint("Migrating secondItem is currentRoot")
secondItem = newRoot
}
NSLayoutConstraint.deactivate([ct])
NSLayoutConstraint.activate([
NSLayoutConstraint(item: firstItem as Any,
attribute: ct.firstAttribute,
relatedBy: ct.relation,
toItem: secondItem,
attribute: ct.secondAttribute,
multiplier: ct.multiplier,
constant: ct.constant)
])
}
}
}
In this specific view the green buttons does get events it's a button that I create programmatically:
And here's the view that does not respond to events, a table view with a button:
I've figured what the issue was; I was missing constraints between contentView (the new root view) and the original self.view. The constraints essentially made the original currentRoot view take the full space of contentView:
NSLayoutConstraint.activate([
currentRoot.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0),
currentRoot.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0),
currentRoot.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
currentRoot.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0),
])

iOS adding constraints programmatically in Swift

So, I'm done using the IB in Xcode and want to write all UI in Swift.
So what I've done is:
Created a new UIView to contain the elements i want to write - lets call it "TestView"
I've added TestView to a VC as a sub view.
In the TestView class I've added the elements like this:
class TestView: UIView {
var someLabel:UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
self.someLabel = UILabel(frame: CGRect(x: self.frame.midX, y: oneSixthHeight, width: 100, height: 22))
self.someLabel.text = "test"
var constraints:[NSLayoutConstraint] = []
self.someLabel.translatesAutoresizingMaskIntoConstraints = false
let rightsideAnchor:NSLayoutConstraint = NSLayoutConstraint(item: self.someLabel, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 1)
constraints.append(rightsideAnchor)
NSLayoutConstraint.activateConstraints(constraints)
}
}
With this I expect the UILabel to be anchored to the right side of the view.
However, I do get this error:
Terminating app due to uncaught exception 'NSGenericException',
reason: 'Unable to activate constraint with items > and > because they have no common ancestor.
Does the constraint reference items in different view hierarchies? That's illegal.'
What am I doing wrong?
You should add constraints only after view is added to the view hierarchy. From your code it is clear that you have not added the UILabel instance to view.
Updated for Swift 3
import UIKit
class ViewController: UIViewController {
let redView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupAutoLayout()
}
func setupViews() {
view.backgroundColor = .white
view.addSubview(redView)
}
func setupAutoLayout() {
// Available from iOS 9 commonly known as Anchoring System for AutoLayout...
redView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
redView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
redView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
redView.heightAnchor.constraint(equalToConstant: 300).isActive = true
// You can also modified above last two lines as follows by commenting above & uncommenting below lines...
// redView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
// redView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
Type of Constraints:
/*
// regular use
1.leftAnchor
2.rightAnchor
3.topAnchor
// intermediate use
4.widthAnchor
5.heightAnchor
6.bottomAnchor
7.centerXAnchor
8.centerYAnchor
// rare use
9.leadingAnchor
10.trailingAnchor
etc. (note: very project to project)
*/

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