EDIT - I fixed it. See answer.
I'm trying to learn how to create constraints programmatically. I tried to constrain a UILabel to the top of the screen, using this code, in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
//the variable label already exists. I make a UILabel.
label = UILabel()
label.text = "Hello"
label.backgroundColor = UIColor(red: 1, green: 1, blue: 0, alpha: 1)
//I add the label to the ViewController
view.addSubview(label)
//I use an array of constraints because I will make more later.
let constraints : [NSLayoutConstraint] = [
NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0.0)
]
view.addConstraints(constraints)
}
When I run my app, I get an error beginning with:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
My intended equation for the constraint was:
label.top = 1.0 * topLayoutGuide.bottom + 0.0
When I created what seems to be an identical constraint with Interface Builder, it worked. However, it doesn't when I try to do it programatically. What's wrong with what I'm doing, and how can I programmatically make a constraint using NSLayoutConstraint that will pin my Label to the top of the screen right below the status bar?
My sources:
Apple - Anatomy of a Constraint
Apple - Programmatically creating Constraints
StackOverflow - SWIFT | Adding constraints programmatically
This can be fixed by setting translatesAutoresizingMaskIntoConstraints to be false, to avoid clashes with existing Mask Constraints:
label.translatesAutoresizingMaskIntoConstraints = false
Related
I want to add constraints to a view programmatically.
This is what I did:
extension UIView {
func bottomToTop(other: UIView) {
self.translatesAutoresizingMaskIntoConstraints = false
other.translatesAutoresizingMaskIntoConstraints = false
let constraint = NSLayoutConstraint(
item: self,
attribute: .bottom,
relatedBy: .equal
toItem: other,
attribute: .top,
multiplier: 1.0,
constant: 0.0
)
superview?.addConstraint(constraint)
constraint.isActive = true
}
}
let label = UILabel()
label.text = "Lenaaaaa"
label.sizeToFit()
label.backgroundColor = .green
let label1 = UILabel()
label1.text = "Lena 2"
label1.sizeToFit()
label1.backgroundColor = .green
let uiView = UIView(frame: frame) (not zero)
uiView.addSubview(label)
uiView.addSubview(label2)
label.bottomToTop(label2)
Why do I end up with this?
Why do I end up with this?
Because your constraints are ambiguous. Once you add even one constraint that affects a view, you must describe that view's position and size in terms of autolayout completely. (And you must stop talking about .frame, as it is now effectively meaningless.)
Thus, you have said only
label.bottomToTop(label2)
But you have not said where the top of label is, where the left of label is, where the left of label2 is, and so on. Thus the autolayout engine throws up its hands in despair.
You could easily have discovered this just by running your app and using the view debugger. It puts up great big exclamation marks telling you what your autolayout issues are.
I have a UICollectionView which contains five cells. In there, I have a UIImageView, two UILabels and a UITextView. I want to change the height of the textview based on the text it contains, so afterwards I can set the height of the entire cell based on the height of the UITextView and the labels above them. Let me demonstrate with a screenshot.
So, as you can tell, the red background shows the height of the UITextView is not right. I set up the UITextView like this:
let commentTextView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = "This is just some text to act as a description"
textView.textContainerInset = UIEdgeInsets(top: 0, left: -4, bottom: 0, right: 0)
textView.font = UIFont.systemFont(ofSize: 16.0)
textView.textColor = UIColor.darkGray
textView.isEditable = false
textView.isSelectable = false
textView.backgroundColor = UIColor.red
textView.frame.size.width = (UIScreen.main.bounds.width - 16 - 16 - 60 - 8)
let numberOfLines = (textView.contentSize.height / textView.font!.lineHeight)
var textViewHeight = (textView.font?.lineHeight)! * floor(numberOfLines)
textView.frame.size.height = textViewHeight
return textView
}()
This does not create the wrong height, I think. I think the problem can be found in my constraints, which has a height constraint (if I delete it, the UITextView disappears). If I change the multiplier (currently set at 0.3), I have different heights, but I want this to be dynamically. So in my opinion, I would need to set a dynamic variable inside the multiplier, but I have no idea how to compose it. Could anyone help? Here are my constraints:
// top constraints
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .top, relatedBy: .equal, toItem: workoutLabel, attribute: .bottom, multiplier: 1, constant: 2)])
// left constraint
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .left, relatedBy: .equal, toItem: profilePictureImageView, attribute: .right, multiplier: 1, constant: 16)])
// right constraint
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .right, relatedBy: .equal, toItem: self.commentTextView.superview, attribute: .right, multiplier: 1, constant: -16)])
// height constraint
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 0.3, constant: 1)])
Cheers guys!
You could consider another way about dynamic height of UITextView. It's known about intrinsic content size. You could implement dynamic height with that tech.
Originally answered here.
Don't change or give any frame to your UITextView. Just give it leading, trailing, top & bottom constraints. Then if your cell is capable of having automatic size then you don't need to calculate anything for your text view.
When using Auto-Layout for dynamically sizing cells, you don't really need to implement sizeThatFits(...). If the constraints are setup correctly, then you only need to disable the scrolling of the UITextView.
From code:
yourTextView.scrollEnabled = false
From IB:
Select your Text View and open Attributes inspector, then
Now if you are facing problems with making your cell to have dynamic size then please look at this answer.
As mentioned yesterday, I had the textview adapt its size based on the answer of #nayem - thank you!
However, I faced another problem with making the cell height dynamic. I have looked into different solutions, got nothing to work, except when I calculate the height myself and set this as the height to be used. This works on all devices in Simulator, just wondering if this is the right approach. Code below, constraints are set on top, left and right.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let dummyText = "Just a long text to see what will happen to the cell. Will it adapt or not? Let's find out!"
let rectWidth = view.frame.width - 32 - 60 - 16
let rect = NSString(string: dummyText).boundingRect(with:CGSize(width: rectWidth, height: 1000), options: NSStringDrawingOptions.usesFontLeading.union(NSStringDrawingOptions.usesLineFragmentOrigin), attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)], context: nil)
let variableHeight = 16 + 20 + 2 + 20 + 2 + 16 + rect.height
return CGSize(width: view.frame.width, height: variableHeight)
}
Is this the best way to do this? Will this cause problems later on?
I want to add some n views inside a table cell (lets say each view is a row). Based on the designerExpertiseList count, i have created a view for each row and added 1 image view and 1 label.
But when i scroll, the data for cells is not correct. If i long press on a cell, i can see a different view overlapped with the one visible now. Please check the attached screenshots
1st time when the view is loaded : http://i.stack.imgur.com/M8itL.png
After i scroll down, scroll up again and long press: http://i.stack.imgur.com/AuTG0.png
And when i scroll up, the data which was correct the first time for few cell, even that is getting messed up. I even tried to add these dynamic views only once, on First Render.
There are the global declarations:
let rowHeight:CGFloat = 20.0
let imageWidth:CGFloat = 15.0
let imageHeight:CGFloat = 15.0
let labelX:CGFloat = 30.0
let labelHeight:CGFloat = 20.0
var firstRender:[Bool] = [Bool]()
code inside tableView cellForRowAtIndexPath method:
self.designer = AppController.getElementFromDesignersList(indexPath.section)
cell.designerName.text = designer.getFirstName() + " " + designer.getLastName()
cell.location.text = designer.getAddress()
// Add more ROWS of expertise if exist! Getting only 1st expertise now, if it exists
let expertiseList = self.designer.getExpertiseList()
if self.firstRender[indexPath.section] {
var i:Int = 0
for e in expertiseList {
let v = UIView(frame: CGRectMake(0, CGFloat(i)*rowHeight, cell.frame.width, rowHeight))
v.backgroundColor = UIColor.yellowColor()
let im = UIImageView(frame: CGRectMake(0, CGFloat(i)*imageHeight, imageWidth, imageHeight ))
//print("expertise image path: ", e.getImagePath())
im.af_setImageWithURL(
NSURL(string: e.getImagePath())!,
placeholderImage: UIImage(named: "default_galary_demo2")!
)
im.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(im)
// Adding constraints
NSLayoutConstraint(item: im, attribute: .CenterY, relatedBy: .Equal, toItem: v, attribute: .CenterY, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: im, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: v, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0).active = true
im.widthAnchor.constraintEqualToAnchor(nil, constant: imageWidth).active = true
im.heightAnchor.constraintEqualToAnchor(nil, constant: imageHeight).active = true
// cell.frame.width - im.frame.width - 50
let label = UILabel(frame: CGRectMake(0, CGFloat(i)*labelHeight, UIScreen.mainScreen().bounds.width - imageWidth, labelHeight))
label.font = UIFont(name: "OpenSans", size: 12)
print("expertise dump: ", dump(e.getExpertiseValuesList()))
//print("expertise str: ", e.getExpertiseValuesList().map({"\($0.getName())"}).joinWithSeparator(","))
label.text = e.getExpertiseValuesList().map({"\($0.getName())"}).joinWithSeparator(",")
//label.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(label)
NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: v, attribute: .CenterY, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: im, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 10.0).active = true
cell.designerExpertiseView.addSubview(v)
i += 1
}
self.firstRender[indexPath.section] = false
I don't think it correct to render cell only when if self.firstRender[indexPath.section] because when user scroll table view, the cells out of screen will be reused to show others different indexPaths.
Another thing, you create constraints, but not use them
You could use UIStackViews to handle the constraints for you. You would:
Greatly reduce the boilerplate-constraint code in your class;
Simplify adding subviews;
Allow for Adaptive Layouts.
Add a Vertical UIStackViewFor into your UITableViewCell. For more complex layouts, you can either embed more UIStackViews into the Vertical stack view or mix AutoLayout and UIStackViews.
I've quickly tried to recreate your layout and I've added a UIStackView. This implementation uses AutoLayout for some fixed components (like the profile picture) and the stack view to handle UILabels added programatically.
I hope this helps!
As it seems, adding views programmatically to cell using .addSubView() won't go well with reusable cell. It happens since cells are reused when go out of view, but when become visible again, the subviews are being added again.
The solution/workaround I used was to add another placeholder view from storyboard. And i removed the view from superview using .removeFromSuperview() when not needed.
I know this method is not good
Ex: Lets say I put 10 views in a cell ( assuming 10 is maximum views i need), but many of them might not be needed based on the data i get from server. So i will remove most of them from the view.
I am still looking for a better/actual solution for this issue.
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/
I'm trying to do a simple layout programmatically and I'm missing something simple, or have something out of place, I think. The following ViewController should center the label in the super view. However, it crashes with this trimmed message: The view hierarchy is not prepared for the constraint ... 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... View not found in container hierarchy: ... That view's superview: NO SUPERVIEW The other SO questions with this error message are using nibs for the most part, and I'm tring to avoid that, or use Obj-C instead of swift. This question deals with the topic a bit but is a bit old.
Can anyone point me in the right direction?
class ViewController: UIViewController {
let label1 = UILabel() as UILabel
func layoutView(){
label1.text = "Click to see device configuration"
label1.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(label1)
let viewsDictionary = ["label1":label1]
let label1_H:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("H:|-[label1]-|",
options: NSLayoutFormatOptions(0),
metrics: nil,
views: viewsDictionary)
let label1_V:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("V:|-[label1]-|",
options: NSLayoutFormatOptions(0),
metrics: nil, views:
viewsDictionary)
label1.addConstraints(label1_H) // Comment these 2 lines and it runs, but
label1.addConstraints(label1_V) // of course the label is upper left
}
override func viewDidLoad() {
super.viewDidLoad()
layoutView()
}
}
Those constraints are made between the label and its superview. The constraints should be added to that superview, not to the label.
You're almost there. Just replace the following lines...
label1.addConstraints(label1_H) // Comment these 2 lines and it runs, but
label1.addConstraints(label1_V) // of course the label is upper left
... with the following code:
view.addConstraints(label1_H) // Comment these 2 lines and it runs, but
view.addConstraints(label1_V) // of course the label is upper left
However, the constraints H:|-[label1]-|" and V:|-[label1]-|" are equivalent to H:|-8-[label1]-8-|" and V:|-8-[label1]-8-|" (see the iOS Developer Library for more details on default margins). Thus, those constraints are not meant to center your label. In fact, you will just have an enormous label that has 8 unit top, leading, trailing and bottom margins to the viewController's view.
Add the following line of code in your layoutView() method to see what I mean:
label1.backgroundColor = UIColor.orangeColor()
It can be OK but if you really want to center your label, you will have to use the following code:
func layoutView() {
label1.text = "Click to see device configuration"
label1.backgroundColor = UIColor.orangeColor()
//Set number of lines of label1 to more than one if necessary (prevent long text from being truncated)
label1.numberOfLines = 0
label1.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(label1)
let xConstraint = NSLayoutConstraint(item: label1,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: view,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: 0.0)
self.view.addConstraint(xConstraint)
let yConstraint = NSLayoutConstraint(item: label1,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: view,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: 0)
self.view.addConstraint(yConstraint)
//Add leading and trailing margins if necessary (prevent long text content in label1 to be larger than screen)
let viewsDictionary = ["label1" : label1]
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(>=10#750)-[label1]-(>=10#750)-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary))
}