Setting up height of label constraint in collectionview cell programatically - ios

I am trying to setup height constraint for a label in collectionview cell
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
constraint()
}
func constraint() {
label.addConstraint(NSLayoutConstraint(item:label, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 25))
}
}
I did the above and it is not working. Does the layoutSubviews declaration work here.

There is a convenience way to use constraint and change it in code.
First, declare a constraint property:
#IBOutlet weak var labelHeight: NSLayoutConstraint!
Second, bind it in XIB or Stroyboard:
Finally, you are able to change it in programming way:
self.labelHeight.constant = 130

NSLayoutConstraint(item: label, attribute: .Height, relatedBy: .Equal, toItem: label, attribute:.Height, multiplier: 1.0, constant:25.0)
or
NSLayoutConstraint.constraintsWithVisualFormat(#"V:[label(==24)]", options: nil , metrics: nil, views: NSDictionaryOfVariableBindings(label))
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html
(...)you must specify a value for each parameter, even if it doesn’t affect the layout. The end result is a considerable amount of boilerplate code, which is usually harder to read. (...)

Related

How do I adjust a label constraint within a custom cell?

I have a table that has custom cells within it. These cells contain an image view and two labels. I have constraints in place to position everything for a typical cell.
Each cell represents either a file or a folder. The layout I have set up is for a file view (two labels are name and detail). When I create the custom cell I change the icon to be a folder and the details label becomes hidden. I then center the name label to make it prettier.
My issue occurs from the reusing of cells. I cannot seem to revert back from the centering of the name label. I have tried a couple different methods of adding this constraint and always seem to be able to have the constraint work the first time, but once a cell is reused I run into issues.
First creation of cell
Issue after cell is reused
One thing I noticed is I only have the issue when a cell is trying to remove the new center constraint (cell goes from folder cell to file cell)
Directory Cell Class
class DirectoryCell: UITableViewCell {
#IBOutlet weak var directoryTypeImage: UIImageView!
#IBOutlet weak var directoryNameLabel: UILabel!
#IBOutlet weak var directoryDetailsLabel: UILabel!
var directoryItem: DirectoryItem! {
didSet {
self.updateUI()
}
}
func updateUI() {
let centerConstraint = NSLayoutConstraint(item: directoryNameLabel, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.centerY, multiplier: 1.0, constant: 0.0)
let topConstraint = NSLayoutConstraint(item: directoryNameLabel, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 7.0)
directoryNameLabel.text = directoryItem.name
directoryTypeImage.image = directoryItem.typeIcon
if (directoryItem.type == DirectoryItem.types.FOLDER) {
self.removeConstraint(topConstraint)
self.addConstraint(centerConstraint)
directoryDetailsLabel.isHidden = true
} else {
self.removeConstraint(centerConstraint)
self.addConstraint(topConstraint)
directoryDetailsLabel.text = directoryItem.details
directoryDetailsLabel.isHidden = false
}
}
}
Am I simply applying/removing the constraints wrong or maybe applying/removing them in the incorrect place?
When I walk through the debugger and look at the self.constraints expression, I get no constraints. Where am I misunderstanding the constraints of my custom cell?
TL;DR
Cannot seem remove centering constraint and apply top constraint when a custom cell is reused
EDIT/SOLUTION
For any future people running into this issue, dan's answer below was exactly right. I needed to create a property for each constraint I wanted to apply. Then I remove all of the constraints and apply only the one that I want.
Added to DirectoryCell class
var topConstraint: NSLayoutConstraint {
get {
return NSLayoutConstraint(item: self.directoryNameLabel, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 7.0)
}
}
var centerConstraint: NSLayoutConstraint {
get {
return NSLayoutConstraint(item: self.directoryNameLabel, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.centerY, multiplier: 1.0, constant: 0.0)
}
}
New updateUI()
func updateUI() {
directoryNameLabel.text = directoryItem.name
directoryTypeImage.image = directoryItem.typeIcon
if (directoryItem.type == DirectoryItem.types.FOLDER) {
self.removeConstraints(self.constraints) // Remove all constraints
self.addConstraint(centerConstraint) // Add constraint I want for this "cell type"
directoryDetailsLabel.isHidden = true
} else {
self.removeConstraints(self.constraints)
self.addConstraint(topConstraint)
directoryDetailsLabel.text = directoryItem.details
directoryDetailsLabel.isHidden = false
}
}
You aren't actually removing the constraint that you added the first time updateUI ran, you're creating a new centering constraint which is never added and removing that one. So you have both the center and top constraint on your cell when it is reused and the centering constraint apparently wins the conflict.
You need to create centerConstraint and topConstraint once and store them in properties on your cell and then just add or remove those ones in updateUI.

NSLayoutConstraint ivar init with self as an item in UIView subclass in Swift 1.2

I have a subclass of UIView which uses some NSLayoutConstraint instances declared as ivars using Swift. The reason for them to be ivars is that I change the constant value. One of them is related to self:
NSLayoutConstraint(item: self.contentView, attribute: .Right,
relatedBy: .Equal, toItem: self, attribute: .Right,
multiplier: 1, constant: self.contentViewWidth)
As you know, all ivars should be initialized before calling to a superclass init, but you can't refer to self only after superclass init. Also, you can't change the toItem (second item) value after the NSLayoutConstraint instance has been initialized.
Before Swift 1.2 marking an ivar with an exclamation mark (implicitly unwrapped value) told the compiler that you're responsible for this ivar to be non-nil when needed, so you can initialize the ivar wherever you want until you're sure it won't be used before that code has been called. That's what I used: I made this NSLayoutConstraint implicitly unwrapped and initialized just after the super.init call, so the self could be referenced:
class FilterView: UIView, UITableViewDelegate, UITableViewDataSource {
private let contentRightConstraint : NSLayoutConstraint!
init(frame: CGRect, customMode: Bool = false) {
super.init(frame: CGRectZero)
contentRightConstraint = NSLayoutConstraint(item: self.contentView,
attribute: .Right, relatedBy: .Equal, toItem: self,
attribute: .Right, multiplier: 1, constant: self.contentViewWidth)
}
}
Since Swift 1.2 the solution above doesn't work anymore. I found two workarounds for this:
1) replace let with var and use a dummy value:
class FilterView: UIView, UITableViewDelegate, UITableViewDataSource {
private var contentRightConstraint = NSLayoutConstraint()
init(frame: CGRect, customMode: Bool = false) {
super.init(frame: CGRectZero)
contentRightConstraint = NSLayoutConstraint(item: self.contentView,
attribute: .Right, relatedBy: .Equal, toItem: self,
attribute: .Right, multiplier: 1, constant: self.contentViewWidth)
}
}
It's not good because it make the ivar changeable and creates an unnecessary instance.
2) Use a lazy var:
class FilterView: UIView, UITableViewDelegate, UITableViewDataSource {
private lazy var contentRightConstraint : NSLayoutConstraint =
NSLayoutConstraint(item: self.contentView, attribute: .Right,
relatedBy: .Equal, toItem: self, attribute: .Right,
multiplier: 1, constant: self.contentViewWidth)
}
This is better, but still is changeable though should be let.
Does anybody have other ideas?

Autolayout with xib and storyboard won’t use my constraints

I am trying to create a reusable view (xib) and use it in my storyboard with autolayout. The problem is that it does not seem to use the constraints that I have set up on the storyboard for it.
So my realy basic IOS-project contains:
MyView.swift
MyView.xib
Main.storyboard
ViewController.swift
The MyView class is connected to the xib file in the proper manner:
import UIKit
class MyView: UIView {
#IBOutlet var View: UIView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)
self.addSubview(self.View);
}
}
In my storyboard i added a UIView (CustomClass=MyView) with 2 simple height/width constraints. When I run the app the view is not sized according to those constraints. It is rendered with the height/width properties in the xib.
Does anyone have a clue on how to make my custom view conform to my constraints that I set up in my storyboard?
Are you saying that your UIView is rendered with the height and width of the xib when you load it's view with the xib? Meaning like, the xib is the only thing you see when you run the simulator? If so, I think the simple solution is to create an additional UIView that loads the xib. Make the NewView a subview of MyView That way you have MyView -> NewView(loaded with xib).
Sorry if that is way off. It is just a little difficult to get what you mean without seeing it. Maybe throw up a screenshot or two. Would have posted this as a comment and not an answer but I don't have the reputation yet.
this should help
var contentView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)
contentView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.addSubview(contentView)
var constTop:NSLayoutConstraint = NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0);
self.view.addConstraint(constTop);
var constBottom:NSLayoutConstraint = NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0);
self.view.addConstraint(constBottom);
var constLeft:NSLayoutConstraint = NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 0);
self.view.addConstraint(constLeft);
var constRight:NSLayoutConstraint = NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 0);
self.view.addConstraint(constRight);

Proper code to delete/or deactivate one constraint to add another to UIView

Why am I getting a Thread 1: signal SIGABRT?
I am trying to learn how to delete and create new constraints to UIViews
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var red: UIView!
#IBOutlet weak var green: UIView!
#IBOutlet weak var redTop: NSLayoutConstraint!
#IBOutlet weak var greenToRed: NSLayoutConstraint!
#IBAction func shift(sender: AnyObject) {
green.removeConstraint(greenToRed)
let newGreen = NSLayoutConstraint(item: green, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0)
green.addConstraint(newGreen)
newGreen.active = true
red.removeConstraint(redTop)
let newRed = NSLayoutConstraint(item: red, attribute: .Top, relatedBy: .Equal, toItem: green, attribute: .Bottom, multiplier: 1, constant: 10)
red.addConstraint(newRed)
newRed.active = true} }
Constraints on a subview need to be added to the superview of the view involved. So a constraint between green and view needs to be added to view (since it's the superview of green). Similarly, a constraint between red and green also needs to be added to their common superview, view (the controllers self.view).
From Apple's documentation: "To make a constraint active, you must add it to a view. The view that holds the constraint must be an ancestor of the views the constraint involves, and should usually be the closest common ancestor. (This is in the existing NSView API sense of the word ancestor, where a view is an ancestor of itself.)".

Swift change class at runtime

So I have 3 views set up in interface builder (XCode 6). They are linked to the ViewController that owns them. Also I have 3 subclasses of UIVIew in my project. At runtime I would need to change the class of one of the views from UIView to my custom view subclass.
How do I do this in swift? (I need all the autolayout set up in IB to work the same after the change).
To achieve what you need you can create a view in IB and later in the code add required view as a subview.
To make added view occupy all container view space you need either update child view's frame or setup auto-layout constraints. Variant with frames needs to be repeated each time container view changes it size. Code bellow:
Auto-Layout Contraints
override func viewDidLoad() {
super.viewDidLoad()
self.myView = UIView(frame: CGRect())
self.myView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.myViewContainer.addSubview(self.myView)
self.myViewContainer.addConstraint(NSLayoutConstraint(item: self.myView, attribute: .Top, relatedBy: .Equal, toItem: self.myViewContainer, attribute: .Top, multiplier: 1.0, constant: 0))
self.myViewContainer.addConstraint(NSLayoutConstraint(item: self.myView, attribute: .Right, relatedBy: .Equal, toItem: self.myViewContainer, attribute: .Right, multiplier: 1.0, constant: 0))
self.myViewContainer.addConstraint(NSLayoutConstraint(item: self.myView, attribute: .Bottom, relatedBy: .Equal, toItem: self.myViewContainer, attribute: .Bottom, multiplier: 1.0, constant: 0))
self.myViewContainer.addConstraint(NSLayoutConstraint(item: self.myView, attribute: .Left, relatedBy: .Equal, toItem: self.myViewContainer, attribute: .Left, multiplier: 1.0, constant: 0))
}
Manual Frame Updates
#IBOutlet var myViewContainer: UIView
var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.myView = UIView(frame: CGRect())
self.myViewContainer.addSubview(self.myView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.myView.frame = self.myViewContainer.bounds
}
Frame updates can be done even if container view has auto-layout constraints.

Resources