UITableView with variable cell height: Working in IB but not programmatically - ios

TL;DR
My programmatically created table view cells are not resizing according to the intrinsic content height of their custom views, even though I am using UITableViewAutomaticDimension and setting both the top and bottom constraints.
The problem probably lies in my implementation of the UITableViewCell subclass. See the code below under Doesn't work programmatically > Code > MyCustomCell.swift.
Goal
I'm trying to make a suggestion bar for a custom Mongolian keyboard. Mongolian is written vertically. In Android it looks like this:
Progress
I've learned that I should use a UITableView with variable cell heights, which is available starting with iOS 8. This requires using auto layout and telling the table view to use automatic dimensions for the cell heights.
Some things I've had to learn along the way are represented in my recent SO questions and answers:
How to make a custom table view cell
Getting variable height to work with in a table view with a standard UILabel
Getting intrinsic content size to work for a custom view
Using a programmatically created UITableViewCell
Set constraints programmatically
So I have come to the point where I have the vertical labels that support intrinsic content size. These labels go in my custom table view cells. And as described in the next section, they work when I do it in the storyboard, but not when I create everything programmatically.
Works in IB
In order to isolate the problem I created two basic projects: one for where I use the storyboard and one where I do everything programmatically. The storyboard project works. As can be seen in the following image, each table view cell resizes to match the height of custom vertical label.
In IB
I set constraints to pin the top and bottom as well as centering the label.
Code
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let myStrings: [String] = ["a", "bbbbbbb", "cccc", "dddddddddd", "ee"]
let cellReuseIdentifier = "cell"
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
// number of rows in table view
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.myStrings.count
}
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.myStrings[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
MyCustomCell.swift
import UIKit
class MyCustomCell: UITableViewCell {
#IBOutlet weak var myCellLabel: UIMongolSingleLineLabel!
}
Doesn't work programmatically
Since I want the suggestion bar to be a part of the final keyboard, I need to be able to create it programmatically. However, when I try to recreate the above example project programmatically, it isn't working. I get the following result.
The cell heights are not resizing and the custom vertical labels are overlapping each other.
I also get the following error:
Warning once only: Detected a case where constraints ambiguously
suggest a height of zero for a tableview cell's content view. We're
considering the collapse unintentional and using standard height
instead.
This error has been brought up before multiple times on Stack Overflow:
iOS8 - constraints ambiguously suggest a height of zero
Detected a case where constraints ambiguously suggest a height of zero
custom UITableviewcell height not set correctly
ios 8 (UITableViewCell) : Constraints ambiguously suggest a height of zero for a tableview cell's content view
However, the problem for most of those people is that they were not setting both a top and bottom pin constraint. I am, or at least I think I am, as is shown in my code below.
Code
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let myStrings: [String] = ["a", "bbbbbbb", "cccc", "dddddddddd", "ee"]
let cellReuseIdentifier = "cell"
var tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
// Suggestion bar
tableView.frame = CGRect(x: 0, y: 20, width: view.bounds.width, height: view.bounds.height)
tableView.registerClass(MyCustomCell.self, forCellReuseIdentifier: cellReuseIdentifier)
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
view.addSubview(tableView)
}
// number of rows in table view
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.myStrings.count
}
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.myStrings[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
MyCustomCell.swift
I think the problem is probably in here since this is the main difference from the IB project.
import UIKit
class MyCustomCell: UITableViewCell {
var myCellLabel = UIMongolSingleLineLabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
self.myCellLabel.translatesAutoresizingMaskIntoConstraints = false
self.myCellLabel.centerText = false
self.myCellLabel.backgroundColor = UIColor.yellowColor()
self.addSubview(myCellLabel)
// Constraints
// pin top
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true
// pin bottom
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true
// center horizontal
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true
}
override internal class func requiresConstraintBasedLayout() -> Bool {
return true
}
}
Supplemental Code
I'll also include the code for the custom vertical label that I used in both projects above, but since the IB project works, I don't think the main problem is here.
import UIKit
#IBDesignable
class UIMongolSingleLineLabel: UIView {
private let textLayer = LabelTextLayer()
var useMirroredFont = false
// MARK: Primary input value
#IBInspectable var text: String = "A" {
didSet {
textLayer.displayString = text
updateTextLayerFrame()
}
}
#IBInspectable var fontSize: CGFloat = 17 {
didSet {
updateTextLayerFrame()
}
}
#IBInspectable var centerText: Bool = true {
didSet {
updateTextLayerFrame()
}
}
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
// Text layer
textLayer.backgroundColor = UIColor.yellowColor().CGColor
textLayer.useMirroredFont = useMirroredFont
textLayer.contentsScale = UIScreen.mainScreen().scale
layer.addSublayer(textLayer)
}
override func intrinsicContentSize() -> CGSize {
return textLayer.frame.size
}
func updateTextLayerFrame() {
let myAttribute = [ NSFontAttributeName: UIFont.systemFontOfSize(fontSize) ]
let attrString = NSMutableAttributedString(string: textLayer.displayString, attributes: myAttribute )
let size = dimensionsForAttributedString(attrString)
// This is the frame for the soon-to-be rotated layer
var x: CGFloat = 0
var y: CGFloat = 0
if layer.bounds.width > size.height {
x = (layer.bounds.width - size.height) / 2
}
if centerText {
y = (layer.bounds.height - size.width) / 2
}
textLayer.frame = CGRect(x: x, y: y, width: size.height, height: size.width)
textLayer.string = attrString
invalidateIntrinsicContentSize()
}
func dimensionsForAttributedString(attrString: NSAttributedString) -> CGSize {
var ascent: CGFloat = 0
var descent: CGFloat = 0
var width: CGFloat = 0
let line: CTLineRef = CTLineCreateWithAttributedString(attrString)
width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, nil))
// make width an even integer for better graphics rendering
width = ceil(width)
if Int(width)%2 == 1 {
width += 1.0
}
return CGSize(width: width, height: ceil(ascent+descent))
}
}
// MARK: - Key Text Layer Class
class LabelTextLayer: CATextLayer {
// set this to false if not using a mirrored font
var useMirroredFont = true
var displayString = ""
override func drawInContext(ctx: CGContext) {
// A frame is passed in, in which the frame size is already rotated at the center but the content is not.
CGContextSaveGState(ctx)
if useMirroredFont {
CGContextRotateCTM(ctx, CGFloat(M_PI_2))
CGContextScaleCTM(ctx, 1.0, -1.0)
} else {
CGContextRotateCTM(ctx, CGFloat(M_PI_2))
CGContextTranslateCTM(ctx, 0, -self.bounds.width)
}
super.drawInContext(ctx)
CGContextRestoreGState(ctx)
}
}
Update
The entire code for the project is all here, so if anyone is interested enough to try it out, just make a new project and cut and paste the code above into the following three files:
ViewController.swift
MyCustomCell.swift
UIMongolSingleLineLabel.swift

The error is pretty trivial:
Instead of
self.addSubview(myCellLabel)
use
self.contentView.addSubview(myCellLabel)
Also, I would replace
// pin top
NSLayoutConstraint(...).active = true
// pin bottom
NSLayoutConstraint(...).active = true
// center horizontal
NSLayoutConstraint(...).active = true
with
let topConstraint = NSLayoutConstraint(...)
let bottomConstraint = NSLayoutConstraint(...)
let centerConstraint = NSLayoutConstraint(...)
self.contentView.addConstraints([topConstraint, bottomConstraint, centerConstraint])
which is more explicit (you have to specify the constraint owner) and thus safer.
The problem is that when calling active = true on a constraint, the layout system has to decide to which view it should add the constraints. In your case, because the first common ancestor of contentView and myCellLabel is your UITableViewCell, they were added to your UITableViewCell, so they were not actually constraining the contentView (constraints were between siblings not between superview-subview).
Your code actually triggered a console warning:
Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
Which made me to look immediately at the way the constraints are created for your label.

I have tested your code and found the issue was in setting constraints please use below code part for setting constants in your "MyCustomCell.swift" file setup() function
let topConstraint = NSLayoutConstraint(item: myCellLabel, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: myCellLabel, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0)
let centerConstraint = NSLayoutConstraint(item: myCellLabel, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1, constant: 0)
self.addConstraints([centerConstraint, topConstraint, bottomConstraint])
Also set clips to bound property to your cell lable in "viewcontroller.swift"
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.myStrings[indexPath.row]
cell.myCellLabel.clipsToBounds=true
return cell
}
For your ease I have uploaded my sample code on GitHub Dynamic Height Sample
Output is looking like this now

The problem seems to come from the vertical constraints in the cell
By putting them relative to self instead of self.contentView in MyCustomCell you can fix your problem
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true
// pin bottom
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true
// center horizontal
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true
the full class would be:
import UIKit
class MyCustomCell: UITableViewCell {
var myCellLabel = UIMongolSingleLineLabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
self.myCellLabel.translatesAutoresizingMaskIntoConstraints = false
self.myCellLabel.centerText = false
self.myCellLabel.backgroundColor = UIColor.yellowColor()
self.addSubview(myCellLabel)
// Constraints
// pin top
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true
// pin bottom
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true
// center horizontal
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true
}
override internal class func requiresConstraintBasedLayout() -> Bool {
return true
}
}

The thing you are missing is this function:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return heightValue
}
Im not so sure what you should do exactly, but by the fact that you know your labels you should be able to return an exact height value for each cell in this method

I think you are missing to set constraints for tableView with superview. And try to increase estimated row height also.

Related

How to create "real" UITableView(Cell) margins where cells cannot be selected outside their content frame?

I would like to create a UITableView layout where the table view itself fills the complete screen (-> scrollbars are shown at screen endges) but the cells are horizontally centered with a fixed width. It should only be possible to select a cell / tap on it within this fixed width but not within the margins:
Simply giving the UITableView and fixed with + center alignment does work, but in this case the scrollbars are not at the screen edges and it is not possible to scroll using the complete screen but only within the tableView frame.
I tried different other solutions:
let cellWidth = 200
let widthDiff = (tableView.frame.width - cellWidth) / 2
// Solution 1: Change horizontal content inset
tableView.contentInset.left = widthDiff
tableView.contentInset.rigth = widthDiff
// => Cells still use the complete width but can be scrolled horizontally by widthDiff
// => Does NOT work
// Solution 2: Setting layout margins on tableView
tableView.layoutMargins.left = widthDiff
tableView.layoutMargins.rigth = widthDiff
// Solution 3: Setting layout margins on cells...
...
cell.layoutMargins.left = widthDiff
cell.layoutMargins.rigth = widthDiff
// Solution 4: Manually center the cell content using constraints within the cell layout.
// => Layout looks correct in all three cases, but while the cell content
// has the correct margins, the cells itselfs still use the complete
// screen width and is still possible to tap/select sells outside
// their frame.
// => Does NOT work
So, I was not able to find a solution which fulfills all three requirements:
Fixed width, centered cells with left and right margins
Scrollbars at screen edges / table view can be scrolled using the complete screen
Cells can only be selected / tapped on within their content frame
Is there a solution using UITableView properties and methods?
EDIT: As requested this image shows what it should look like:
Cells are centered in the middle with some margins on both sides
Scrollbars are at the screen endge
But: The cells still occupy the complete width. When tapping in the area of the margins cell is still selected and its selected-background uses the complete width. This should be avoided.
From touches point of view you seem to want that cells are selectable only at specific position but table view can be selected everywhere where table view is (for scrolling).
From views point of view you wish to limit cells to specific location but want to draw table view everywhere (scrollbars at the edge).
Then I would say that table view needs to stretch through whole screen and the cell content should be limited. This would best be done with simply constraining a custom view within your cell. The selection of cells would then need to be custom. Consider something like the following:
(I intentionally did some parts programmatically to show what is being done. But I would put most of this in storyboard).
class ViewController: UIViewController {
#IBOutlet private var tableView: UITableView?
var dataSource: [Bool] = []
var cellWidth: CGFloat = 200.0
override func viewDidLoad() {
super.viewDidLoad()
tableView?.allowsSelection = false
dataSource = .init(repeating: false, count: 100)
tableView?.reloadData()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyCell()
cell.setupIfNeeded(width: cellWidth)
cell.isCellSelected = dataSource[indexPath.row]
cell.onMiddleViewPressed = { [weak self, weak cell] in
self?.dataSource[indexPath.row] = true
cell?.isCellSelected = true
}
return cell
}
}
class MyCell: UITableViewCell {
private var isSetup: Bool = false
private var middleView: UIView?
var onMiddleViewPressed: (() -> Void)?
var isCellSelected: Bool = false {
didSet {
middleView?.backgroundColor = isCellSelected ? .blue : .gray
}
}
func setupIfNeeded(width: CGFloat) {
guard isSetup == false else { return }
isSetup = true
let middleView = UIView(frame: .zero)
middleView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(middleView)
contentView.addConstraint(.init(item: middleView, attribute: .centerX, relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: 1.0, constant: 0.0))
middleView.addConstraint(.init(item: middleView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: width))
contentView.addConstraint(.init(item: middleView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1.0, constant: 0.0))
contentView.addConstraint(.init(item: middleView, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1.0, constant: 0.0))
middleView.backgroundColor = isCellSelected ? .blue : .gray
middleView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onMiddleViewSelected)))
self.middleView = middleView
}
#objc private func onMiddleViewSelected() {
onMiddleViewPressed?()
}
}
To create a toggle in selection you would then simply do:
cell.onMiddleViewPressed = { [weak self, weak cell] in
guard let self = self else { return }
self.dataSource[indexPath.row].toggle()
cell?.isCellSelected = self.dataSource[indexPath.row]
}
To use a single selection you would do
cell.onMiddleViewPressed = { [weak self, weak cell, weak tableView] in
guard let self = self else { return }
if let currentSelectionIndex = self.dataSource.firstIndex(of: true), currentSelectionIndex != indexPath.row {
self.dataSource[currentSelectionIndex] = false
let targetIndexPath = IndexPath(row: currentSelectionIndex, section: 0)
if tableView?.indexPathsForVisibleRows?.contains(targetIndexPath) == true {
tableView?.reloadRows(at: [targetIndexPath], with: .none)
}
}
self.dataSource[indexPath.row].toggle()
cell?.isCellSelected = self.dataSource[indexPath.row]
}
so this is nothing too heavy. And a pretty standard procedures in cases like having UISwitch (or similar components) for selection where selecting the cell does something completely different (navigate to details for instance).
Perhaps you also need transparent cells and table view. This is just:
tableView?.backgroundColor = .clear
cell.backgroundColor = .clear
cell.contentView.backgroundColor = .clear

How do I add constraints to a subview loaded from a nib file?

I'm trying to load a sub view on to every single page of my app from a nib file. Right now I'm using a somewhat unusual approach to loading this sub view in that I am doing it through an extension of UIStoryboard (probably not relevant to my problem, but I'm not sure). So this is how the code looks when I load the nib file:
extension UIStoryboard {
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
viewController.view.addSubview(myCustomSubview)
}
}
}
This code does what it's supposed to do and adds "MyCustomSubview" to the view controller (I won't go in to detail on exactly how this method gets called because it works so it doesn't seem important). The problem is I can't for the life of me figure out how to add constraints that effect the size of myCustomSubview. I have tried putting code in the function I showed above as well as in the MyCustomSubview swift file to add constraints but no matter what I do the subview never changes.
Ideally the constraints would pin "MyCustomSubview" to the bottom of the ViewController, with width set to the size of the screen and a hard coded height constraint.
Here are the two main methods I tried (with about 100 minor variations for each) that did NOT work:
Method 1 - Add constraint directly from "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let top = NSLayoutConstraint(item: myCustomSubview, attribute: .top, relatedBy: .equal
, toItem: viewController.view, attribute: .top, multiplier: 1, constant: 50.0)
viewController.view.addSubview(myCustomSubview)
viewController.view.addConstraint(top)
}
}
Method 2 - Add constraint outlets and setter method in MyCustomSubview
class MyCustomSubview: UIView {
#IBOutlet weak var widthConstraint: NSLayoutConstraint!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
func setConstraints(){
self.widthConstraint.constant = UIScreen.main.bounds.size.width
self.heightConstraint.constant = 20
}
}
And call setter method in "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
myCustomSubview.setConstraints()
viewController.view.addSubview(myCustomSubview)
}
}
(*note: the actual constraints of these examples are irrelevant and I wasn't trying to meet the specs I mentioned above, I was just trying to make any sort of change to the view to know that the constraints were updating. They weren't.)
Edit : Changed "MyCustomNib" to "MyCustomSubview" for clarity.
When you add constraints onto a view from a Nib, you have to call yourView.translatesAutoresizingMaskIntoConstraints = false, and you also need to make sure that you have all 4 (unless it's a label or a few other view types which only need 2) constraints in place:
Here's some sample code that makes a view fill it's parent view:
parentView.addSubview(yourView)
yourView.translatesAutoresizingMaskIntoConstraints = false
yourView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).isActive = true
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor).isActive = true
Edit: I've actually come around to perferring this method of adding NSLayoutConstraints, even though the results are the same
NSLayoutConstraint.activate([
yourView.topAnchor.constraint(equalTo: parentView.topAnchor),
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
])
For anyone who comes across this in the future, this is the solution I came up with by tweaking this answer a little bit
Add a setConstraints(withRelationshipTo) method in the swift class that corresponds to the nib file:
class MyCustomSubview: UIView {
func setConstraints(withRelationshipTo view: UIView){
self.translatesAutoresizingMaskIntoConstraints = false
// Replace with your own custom constraints
self.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
self.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
Then call the setConstraints method after you add the nib file to the view (probably in viewWillAppear or viewDidLoad of a view controller )
class MyViewController: UIViewController {
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let view = self.view // Added for clarity
view.addSubview(myCustomSubview)
myCustomSubview.setConstraints(withRelationshipTo: view)
}
}
}
You can use this extension for anywhere you're going to add a subview to a existing UIView.
extension UIView {
func setConstraintsFor(contentView: UIView, left: Bool = true, top: Bool = true, right: Bool = true, bottom: Bool = true) {
contentView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(contentView)
var constraints : [NSLayoutConstraint] = []
if left {
let constraintLeft = NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
constraints.append(constraintLeft)
}
if top {
let constraintTop = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0)
constraints.append(constraintTop)
}
if right {
let constraintRight = NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0)
constraints.append(constraintRight)
}
if bottom {
let constraintBottom = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
constraints.append(constraintBottom)
}
self.addConstraints(constraints)
}
}
You can call this method like this:
containerView.setConstraintsFor(contentView: subView!, top: false)
This will add subView to the containerView and constraint to all sides except top side.
You can modify this method to pass left, top, right, bottom Constant value if you want.

Two vertically aligned views start differently when their shared parent view embedded in Navigation Controller

I have two table views within a view controller, and the view controller is embedded in a navigation controller.
I have added the following constraints vertically, and I expect the two table views start at the same point, near the bottom of the navigation bar.
constraints.append(t1.topAnchor.constraintEqualToAnchor(self.topLayoutGuide.bottomAnchor, constant: 8.0))
constraints.append(t1.bottomAnchor.constraintEqualToAnchor(self.bottomLayoutGuide.topAnchor, constant: -8.0))
constraints.append(t2.topAnchor.constraintEqualToAnchor(t1.topAnchor))
constraints.append(t2.bottomAnchor.constraintEqualToAnchor(t1.bottomAnchor))
However, it turns out the table view t1 starts much lower than the table view t2, the latter of which starts near the bottom of the navigation bar as expected.
Why does this happen? How to fix this?
UPDATE
Constraints:
t1.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cell")
t2.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cell")
t1.dataSource = self
t2.dataSource = self
self.view.addSubview(t1)
self.view.addSubview(t2)
t1.translatesAutoresizingMaskIntoConstraints = false
t2.translatesAutoresizingMaskIntoConstraints = false
var constraints = [NSLayoutConstraint]()
constraints.append(t1.topAnchor.constraintEqualToAnchor(self.topLayoutGuide.bottomAnchor, constant: 8.0))
constraints.append(t1.bottomAnchor.constraintEqualToAnchor(self.bottomLayoutGuide.topAnchor, constant: -8.0))
constraints.append(t2.topAnchor.constraintEqualToAnchor(t1.topAnchor))
constraints.append(t2.bottomAnchor.constraintEqualToAnchor(t1.bottomAnchor))
constraints.append(t1.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor, constant: 8.0))
constraints.append(t2.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor, constant: -8.0))
constraints.append(t2.leadingAnchor.constraintEqualToAnchor(t1.trailingAnchor, constant: 8.0))
constraints.append(t1.widthAnchor.constraintEqualToAnchor(t2.widthAnchor))
NSLayoutConstraint.activateConstraints(constraints)
Data Sources:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = "Start"
return cell
}
Unexpected Alignments:
just tried to replicate the issue.
The table views get perfectly aligned if I use the code below. Just added the constraints in the view controller.
t1.backgroundColor = UIColor.redColor()
t2.backgroundColor = UIColor.blueColor()
view.addSubview(t1)
view.addSubview(t2)
t1.translatesAutoresizingMaskIntoConstraints = false
t2.translatesAutoresizingMaskIntoConstraints = false
view.addConstraint(t1.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 0.0))
view.addConstraint(t2.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: 0.0))
t1.addConstraint(NSLayoutConstraint(item: t1, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100.0))
t2.addConstraint(NSLayoutConstraint(item: t2, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100.0))
view.addConstraint(t1.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor, constant: 8.0))
view.addConstraint(t1.bottomAnchor.constraintEqualToAnchor(bottomLayoutGuide.topAnchor, constant: -8.0))
view.addConstraint(t2.topAnchor.constraintEqualToAnchor(t1.topAnchor))
view.addConstraint(t2.bottomAnchor.constraintEqualToAnchor(t1.bottomAnchor))
Just gives me this layout:
Maybe you can give more insights into your whole layout code?
UPDATE
After investigating the issue with your provided layout code:
The table views are layouted exactly as expected. The problem is with the
contentInset.
Add this for some console logs:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("Insets t1 -> \(t1.contentInset)")
print("Insets t2 -> \(t2.contentInset)")
}
This prints out:
Insets t1 -> UIEdgeInsets(top: 64.0, left: 0.0, bottom: 0.0, right: 0.0)
Insets t2 -> UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
There is an option in Interface Builder for your view controller named Adjust Scroll View Insets. Disable it (or use automaticallyAdjustsScrollViewInsets = false in code).
In general table views (as can be seen in table view controllers) go under the navigation bar (so you get the blurring scroll effect...) but the content inset is set so that your first cell is below the navigation bar.
To avoid this behavior when referencing the top layout guide you have to disable this option. The other table view is not affected because it only references the layout constraints from the other one.
It is always very helpful to color the views, so you can see if misalignments come from your layout code or any other side effect. (Or use the view debugger from Xcode)
So after setting the flag it gets from this:
to this:
Cheers
Orlando 🍻
Modify one line code would resolve it.
Replace
NSLayoutConstraint.activateConstraints(constraints)
with
self.view.addConstraints(constraints)
I think you don't understand the concept of Auto Layout completely.
UPDATE:
Sorry about that I did't mean to the UINavigationController.
UIViewController defaultly adjust scrollview's inset, you can prevent it via automaticallyAdjustsScrollViewInsets = false.
BTW, you can use Mansory or Purelayout to set constraints easily.
class ViewController: UIViewController, UITableViewDataSource {
var t1: UITableView = UITableView(frame: CGRectZero, style: .Plain)
var t2: UITableView = UITableView(frame: CGRectZero, style: .Plain)
override func viewDidLoad() {
super.viewDidLoad()
// This line is the point.
automaticallyAdjustsScrollViewInsets = false
commonInit()
}
func commonInit(){
view.backgroundColor = UIColor .whiteColor()
t1.backgroundColor = UIColor.blueColor()
t2.backgroundColor = UIColor.greenColor()
view.addSubview(t1)
view.addSubview(t2)
t1.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cell")
t2.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cell")
t1.dataSource = self
t2.dataSource = self
self.view.addSubview(t1)
self.view.addSubview(t2)
t1.translatesAutoresizingMaskIntoConstraints = false
t2.translatesAutoresizingMaskIntoConstraints = false
var constraints = [NSLayoutConstraint]()
constraints.append(t1.topAnchor.constraintEqualToAnchor(self.topLayoutGuide.bottomAnchor, constant: 8.0))
constraints.append(t1.bottomAnchor.constraintEqualToAnchor(self.bottomLayoutGuide.topAnchor, constant: -8.0))
constraints.append(t2.topAnchor.constraintEqualToAnchor(t1.topAnchor))
constraints.append(t2.bottomAnchor.constraintEqualToAnchor(t1.bottomAnchor))
constraints.append(t1.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor, constant: 8.0))
constraints.append(t2.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor, constant: -8.0))
constraints.append(t2.leadingAnchor.constraintEqualToAnchor(t1.trailingAnchor, constant: 8.0))
constraints.append(t1.widthAnchor.constraintEqualToAnchor(t2.widthAnchor))
view.addConstraints(constraints)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = "Start"
return cell
}
}

Adding labels programmatically, aligned with labels from Storyboard

Desired View:
In Storyboard I have two labels with the blue background that I am creating in Autolayout. Their position will never change. Next, I would like to add anywhere from 1 to 10 labels in code in cellForRowAtIndexPath below the blue background labels.
I am struggling to align the the labels added in code (brown background) with the ones created in Autolayout (blue background).
Below is my failed attempt:
Two approaches that failed:
In cellForRowAtIndexPath get the frame of "B AutoLayout Static Label" and use the X position for Dynamic Labels. Did not work.
Adding constraints also did not work -- perhaps I am not adding the constraints correctly.
Here is the code:
class TableViewController: UITableViewController {
var cellHeight = [Int: CGFloat]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 85.0
self.tableView.rowHeight = UITableViewAutomaticDimension
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell
let xLocation = cell.labelBStatic.frame.origin.x
var yLocation = cell.labelBStatic.frame.origin.y
let height = cell.labelBStatic.frame.size.height
var startYLocation = yLocation + height + 20
var i = 0
if indexPath.row % 2 == 0 {
i = 5
} else {
i = 7
}
while i < 10 {
let aLabel = UILabel()
aLabel.backgroundColor = UIColor.orangeColor()
aLabel.text = "Label # \(i)"
cell.contentView.addSubview(aLabel)
addConstraints(aLabel, verticalSpacing: startYLocation)
startYLocation += 20
i++
}
print(startYLocation)
cellHeight[indexPath.row] = startYLocation
return cell
}
func addConstraints(labelView: UILabel, verticalSpacing: CGFloat) {
// set Autoresizing Mask to false
labelView.translatesAutoresizingMaskIntoConstraints = false
//make dictionary for views
let viewsDictionary = ["view1": labelView]
//sizing constraints
let view1_constraint_H:Array = NSLayoutConstraint.constraintsWithVisualFormat("H:[view1(>=50)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
labelView.addConstraints(view1_constraint_H)
//position constraints
let view_constraint_H:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("H:|-15-[view1]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
let view_constraint_V:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("V:|-\(verticalSpacing)-[view1]", options: NSLayoutFormatOptions.AlignAllLeading, metrics: nil, views: viewsDictionary)
view.addConstraints(view_constraint_H as! [NSLayoutConstraint])
view.addConstraints(view_constraint_V as! [NSLayoutConstraint])
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if let height = cellHeight[indexPath.row] {
return height
}
return 0
}
Below is the Storyboard setup (Both labels are centered horizontally):
Question: How can I get my dynamic labels that I am creating in cellForRowAtIndexPath left align with my static labels that were created in Storyboard to match my desired view on top?
Just for demonstration purpose i have added one label programatically, you can add as much as labels you want, just add the constraints properly, also you need to add bottomSpace constraint to the last label inside cell so that your cell will auto resize as per the label height.
Follow the steps i have done to achieve what you want:
Access the B AutoLayout Static Label in cellForRowAtIndexPath: using tag or outlet if you have subclassed UITableViewCell and have created outlet.
let labelBAutolayoutStaticLabel = cell?.viewWithTag(20)
Create the label programatically as below and set translatesAutoresizingMaskIntoConstraints to false,
let labelDynamicLabel = UILabel()
labelDynamicLabel.backgroundColor = UIColor.orangeColor()
labelDynamicLabel.text = "A Dynamic Label"
labelDynamicLabel.translatesAutoresizingMaskIntoConstraints = false
cell?.contentView.addSubview(labelDynamicLabel)
You need to create two constraint, one is for TopSpace and second is LeadingSpace as below,
let leadingSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: labelBAutolayoutStaticLabel, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0);
let topSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: labelBAutolayoutStaticLabel, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 10); //Constant is the spacing between
Add constraint to your cell's contentView as below,
cell?.contentView.addConstraint(leadingSpaceConstraint)
cell?.contentView.addConstraint(topSpaceConstraint)
That's it.
And here is the result,
Here is the full code for cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("cell")
let labelBAutolayoutStaticLabel = cell?.viewWithTag(20)
let labelDynamicLabel = UILabel()
labelDynamicLabel.backgroundColor = UIColor.orangeColor()
labelDynamicLabel.text = "A Dynamic Label"
labelDynamicLabel.translatesAutoresizingMaskIntoConstraints = false
cell?.contentView.addSubview(labelDynamicLabel)
let leadingSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: labelBAutolayoutStaticLabel, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0);
let topSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: labelBAutolayoutStaticLabel, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 10);
cell?.contentView.addConstraint(leadingSpaceConstraint)
cell?.contentView.addConstraint(topSpaceConstraint)
return cell!
}
Edit/Update:
If you have to set bottomSpace constraint to the last label (label which is at bottom of cell), there are two way
Use NSLayoutConstraint as below:
let bottomSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.BottomMargin, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1, constant: -8)
cell.contentView.addConstraint(bottomSpaceConstraint)
Using Visual Format Language as below,
let views = ["cell": cell, "labelDynamicLabel": labelDynamicLabel, "labelBAutolayoutStaticLabel": labelBAutolayoutStaticLabel]
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:[labelBAutolayoutStaticLabel]-[labelDynamicLabel]-|", options: [], metrics: nil, views: views)
cell.contentView.addConstraints(verticalConstraints)
If you set constraint using VFL make sure you remove topSpaceConstraint
//cell.contentView.addConstraint(topSpaceConstraint)
What this "V:[labelBAutolayoutStaticLabel]-[labelDynamicLabel]-|" string mean is,
labelDynamicLabel should have TopSpace to labelBAutolayoutStaticLabel with standardSpacing (8pts) and labelDynamicLabel should have bottom space to SuperView with standardSpacing. ('-|' indicates the standard space to superView)

How can I add same uiview multiples times at different positions to a UIViewController?

I am trying to achieve this in swift.
So far I created my own custom view which is a subclass of UIView class:
class MyConnections: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 1)
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
let circle = CGRectMake(5, 60, 80, 80)
CGContextAddEllipseInRect(context, circle)
CGContextStrokePath(context)
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextFillEllipseInRect(context, circle)
}
}
This is my view controller where I add the above view as a subview:
let profile = MyConnections()
override func viewDidLoad() {
super.viewDidLoad()
profile.backgroundColor = UIColor.clearColor()
view.addSubview(profile)
self.profile.setTranslatesAutoresizingMaskIntoConstraints(false)
//constraints for the location button
let horizontalConstraint = NSLayoutConstraint(item: self.profile, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 10)
let verticalConstraint = NSLayoutConstraint(item: self.profile
, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 20)
let widthConstraint = NSLayoutConstraint(item: self.profile, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 150)
let heightConstraint = NSLayoutConstraint(item: self.profile, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 150)
self.view.addConstraints([verticalConstraint, horizontalConstraint, widthConstraint, heightConstraint])
// Do any additional setup after loading the view.
}
All the code above gives me a circle on top. Now I want to repeat that same circle multiple times at different positions as seen in the image. I can create multiple instances of the uiview add them as subview but every time I will have to define new constraints for it which I don't want to do.
Can anyone please help me and give me an efficient answer?
You should know a UIView can have a single superview/parent. If you add it as a subview at a different position (using addSubview method) it will be removed from the first position and added as a subview to the new position.
In your case to add more subviews you have to create more UIView objects not use a single global UIView.
If the layout is repetitive a UITableView / UICollectionView is a better choice.
Your requirement and UI qualify for a UICollectionView I think you should use UICollectionView and can create a custom UICollectionViewCell with round image and and badge view as well, and they add dataSource and delegate methods. That will not only help in creating UI but it will make your app more performant by reusing cells
Here is a nice tutorial about UICollectionView
Here's a simplified view to create collection view programmatically:
make the collection view and layout programmatically just like any other view you would code and add it as subview like below:
lazy var myCollectionView : UICollectionView = {
let layout = YourFlowLayout()
layout.scrollDirection = self.direction;
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
cv.dataSource = self
cv.delegate = self
cv.isPagingEnabled = true
cv.backgroundColor = UIColor.clear
cv.showsHorizontalScrollIndicator = false
cv.showsVerticalScrollIndicator = false
cv.allowsMultipleSelection = false
return cv
}()
and your flow layout could be something like:
mport UIKit
class Yourflowlayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return super.layoutAttributesForElements(in: rect)?.map {
attrs in
let attrscp = attrs.copy() as! UICollectionViewLayoutAttributes
self.applyLayoutAttributes(attributes: attrscp)
return attrscp
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if let attrs = super.layoutAttributesForItem(at: indexPath as IndexPath) {
let attrscp = attrs.copy() as! UICollectionViewLayoutAttributes
self.applyLayoutAttributes(attributes: attrscp)
return attrscp
}
return nil
}
func applyLayoutAttributes(attributes : UICollectionViewLayoutAttributes) {
if attributes.representedElementKind != nil {
return
}
if let collectionView = self.collectionView {
let stride = (self.scrollDirection == .horizontal) ? collectionView.frame.size.width : collectionView.frame.size.height
let offset = CGFloat(attributes.indexPath.section) * stride
var xCellOffset : CGFloat = CGFloat(attributes.indexPath.item) * self.itemSize.width
var yCellOffset : CGFloat = CGFloat(attributes.indexPath.item) * self.itemSize.height
if(self.scrollDirection == .horizontal) {
xCellOffset += offset;
} else {
yCellOffset += offset
}
attributes.frame = CGRect(x: xCellOffset, y: yCellOffset, width: self.itemSize.width, height: self.itemSize.height)
}
}
}
You can add the collectionView in your other classes as a subview , make sure you have the
myCollectionView.translatesAutoresizingMaskIntoConstraints = false so that your constrains are applied and you actually see the collection view and of course add your constrains or give it a frame.
Hope that helps someone.

Resources