I have three custom views side by side in one line. If the content of the last view does not fit (as can be seen in the picture), move that view to a new line. How?
These is constraints:
firstCustomView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 15),
secondCustomView.leadingAnchor.constraint(equalTo: firstCustomView.trailingAnchor, constant: 15),
thirdsCustomView.leadingAnchor.constraint(equalTo: secondCustomView.trailingAnchor, constant: 15),
thirdsCustomView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -15),
I did it in this way, first create custom collection view class:
class DynamicHeightCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
return contentSize
}
}
Second, create custom layout:
open class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout {
open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return super.layoutAttributesForElements(in: rect)?.map { $0.representedElementKind == nil ? layoutAttributesForItem(at: $0.indexPath)! : $0 }
}
open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let currentItemAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes,
collectionView != nil else {
return nil
}
if scrollDirection == .vertical {
if indexPath.item != 0,
let previousFrame = layoutAttributesForItem(at: IndexPath(item: indexPath.item - 1, section: indexPath.section))?.frame,
currentItemAttributes.frame.intersects(CGRect(x: -.infinity, y: previousFrame.origin.y, width: .infinity, height: previousFrame.size.height)) {
currentItemAttributes.frame.origin.x = previousFrame.origin.x + previousFrame.size.width + evaluatedMinimumInteritemSpacingForSection(at: indexPath.section)
} else {
currentItemAttributes.frame.origin.x = evaluatedSectionInsetForSection(at: indexPath.section).left
}
} else {
if indexPath.item != 0,
let previousFrame = layoutAttributesForItem(at: IndexPath(item: indexPath.item - 1, section: indexPath.section))?.frame,
currentItemAttributes.frame.intersects(CGRect(x: previousFrame.origin.x, y: -.infinity, width: previousFrame.size.width, height: .infinity)) {
currentItemAttributes.frame.origin.y = previousFrame.origin.y + previousFrame.size.height + evaluatedMinimumInteritemSpacingForSection(at: indexPath.section)
} else {
currentItemAttributes.frame.origin.y = evaluatedSectionInsetForSection(at: indexPath.section).top
}
}
return currentItemAttributes
}
func evaluatedMinimumInteritemSpacingForSection(at section: NSInteger) -> CGFloat {
return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingForSectionAt: section) ?? minimumInteritemSpacing
}
func evaluatedSectionInsetForSection(at index: NSInteger) -> UIEdgeInsets {
return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, insetForSectionAt: index) ?? sectionInset
}
}
And at initialization:
#IBOutlet weak var collectionView: UICollectionView! {
didSet {
collectionView.dataSource = self
collectionView.delegate = self
collectionView.isScrollEnabled = false
collectionView.register(UINib(nibName: "BubbleCell", bundle: nil), forCellWithReuseIdentifier: "BubbleCell")
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = data[indexPath.row].name.size(withAttributes:[.font: UIFont(name: "Lato-Regular", size: 16)!])
let width = min(size.width + 52, collectionView.frame.width - collectionView.contentInset.left - collectionView.contentInset.right)
return CGSize(width: width, height: 34)
}
Related
I want to create an app that shows an array of photos with captions (for now I have empty circles) that are arranged in horizontal uicollectionview. I want to make one larger circle to be displayed in the center of the screen, but the remaining circles in the line to be smaller and shaded. I did it by creating my own layout for uicollectionview. But I have one problem that I don't know how to fix it. I want the transition between circles to be smoothly animated. Here is an example of what I've done:
I want to achieve something like this:gif
I know that there are some library and frameworks which do that, but I'd like to do by my own.
Here is my code:
class FlowViewController: UIViewController {
let cellWidth : CGFloat = 275
let cellHeight : CGFloat = 300
let cellSpacing : CGFloat = 10
private let circleCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
private let layout = myCarouselFlowLayout()
private let collectionViewCellIdentifier = "PositionCollectionViewCell"
private var circles : [myCircle] = {
let pok1 = myCircle(name: "Bl4")
let pok2 = myCircle(name: "Bl3")
let pok3 = myCircle(name: "Bli")
let pok4 = myCircle(name: "Bl0")
let pok5 = myCircle(name: "Lal")
let pok6 = myCircle(name: "Te")
let pok7 = myCircle(name: "wTW")
let pok8 = myCircle(name: "H3RHG")
return [pok1, pok2, pok3, pok4, pok5, pok6, pok7, pok8]
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)
view.addSubview(circleCollectionView)
configureCollectionView()
print(circles.count)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
configureCollectionViewLayoutItemSize()
}
func configureCollectionView(){
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: cellWidth, height: cellHeight)
layout.minimumLineSpacing = cellSpacing
//set layout
circleCollectionView.setCollectionViewLayout(layout, animated: true)
//set delegates
setTableViewDelegates()
//register cells
circleCollectionView.register(CircleCollectionViewCell.self, forCellWithReuseIdentifier: collectionViewCellIdentifier)
//set contraits
circleCollectionView.translatesAutoresizingMaskIntoConstraints = false
circleCollectionView.heightAnchor.constraint(equalToConstant: cellHeight).isActive = true
circleCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
circleCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
circleCollectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
circleCollectionView.backgroundColor = .clear
circleCollectionView.decelerationRate = .fast
}
func calculateSectionInset() -> CGFloat { // should be overridden
let viewWith = UIScreen.main.bounds.size.width
return ( viewWith - cellWidth ) / 2
}
private func configureCollectionViewLayoutItemSize() {
let inset: CGFloat = calculateSectionInset()
layout.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
layout.itemSize = CGSize(width: layout.collectionView!.frame.size.width - inset * 2,
height: layout.collectionView!.frame.size.height)
}
}
extension FlowViewController: UICollectionViewDelegate,UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
circles.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCellIdentifier, for: indexPath) as? CircleCollectionViewCell else {
fatalError("Bad instance of FavoritesCollectionViewCell")
}
cell.nameLabel.text = circles[indexPath.row].name
return cell
}
func setTableViewDelegates(){
circleCollectionView.delegate = self
circleCollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(circles.count)
}
}
extension FlowViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = cellWidth
let height = cellHeight
return CGSize(width: width, height: height)
}
}
class myCarouselFlowLayout: UICollectionViewFlowLayout,UICollectionViewDelegateFlowLayout {
private let fadeFactor: CGFloat = 0.5
override var collectionViewContentSize: CGSize {
super.collectionViewContentSize
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
super.layoutAttributesForElements(in: rect)?.map {
self.layoutAttributesForItem(at: $0.indexPath)!
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let superValue = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else { return nil }
guard let cv = collectionView else { return nil }
let collectionMidPoint = CGPoint(x: cv.bounds.midX, y: cv.bounds.midY)
let itemMidPoint = superValue.center
let distance = abs(itemMidPoint.x - collectionMidPoint.x)
if distance > 100 {
UIView.animate(withDuration: 0.2) {
superValue.alpha = self.fadeFactor
superValue.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
cv.layoutIfNeeded()
}
}
return superValue
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
true
}
var velocityThresholdPerPage: CGFloat = 2
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return proposedContentOffset }
let pageLength: CGFloat
let approxPage: CGFloat
let currentPage: CGFloat
pageLength = (self.itemSize.width + self.minimumLineSpacing)
approxPage = collectionView.contentOffset.x / pageLength
currentPage = round(approxPage)
if velocity.x == 0 {
return CGPoint(x: currentPage * pageLength, y: 0)
}
var nextPage: CGFloat = currentPage + (velocity.x > 0 ? 1 : -1)
let increment = velocity.x / velocityThresholdPerPage
nextPage += round(increment)
return CGPoint(x: nextPage * pageLength, y: 0)
}
Please give me some advice on where and how should I use animations on it? Should I do it with UIView.animate or something else? I know that I need to use layoutIfNeeded() to animate constraints, but it doesn't work with it.
I want to create a view that resembles a chat view, where the newest message appears at the bottom of the screen. I would like the UICollectionViewCells to be 'pinned' to the bottom of the screen - the opposite of what the default flow layout does. How can I do this?
I've got this code from another stackoverflow answer, but it doesn't work with variable height items (which it needs to):
import Foundation
import UIKit
class InvertedStackLayout: UICollectionViewLayout {
let cellHeight: CGFloat = 100 // Your cell height here...
override func prepare() {
super.prepare()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttrs = [UICollectionViewLayoutAttributes]()
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
if let numberOfSectionItems = numberOfItemsInSection(section) {
for item in 0 ..< numberOfSectionItems {
let indexPath = IndexPath(item: item, section: section)
let layoutAttr = layoutAttributesForItem(at: indexPath)
if let layoutAttr = layoutAttr, layoutAttr.frame.intersects(rect) {
layoutAttrs.append(layoutAttr)
}
}
}
}
}
return layoutAttrs
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let layoutAttr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let contentSize = self.collectionViewContentSize
layoutAttr.frame = CGRect(
x: 0, y: contentSize.height - CGFloat(indexPath.item + 1) * cellHeight,
width: contentSize.width, height: cellHeight)
return layoutAttr
}
func numberOfItemsInSection(_ section: Int) -> Int? {
if let collectionView = self.collectionView,
let numSectionItems = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: section)
{
return numSectionItems
}
return 0
}
override var collectionViewContentSize: CGSize {
get {
var height: CGFloat = 0
var bounds: CGRect = .zero
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
if let numItems = numberOfItemsInSection(section) {
height += CGFloat(numItems) * cellHeight
}
}
bounds = collectionView.bounds
}
return CGSize(width: bounds.width, height: max(height, bounds.height))
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if let oldBounds = self.collectionView?.bounds,
oldBounds.width != newBounds.width || oldBounds.height != newBounds.height
{
return true
}
return false
}
}
Flip your collectionView with a CGAffineTransform so that it starts at the bottom.
collectionView.transform = CGAffineTransform(scaleX: 1, y: -1)
The only problem now is that all your cells display upside down. You then apply the transform to each cell to flip it back upright.
class InvertedCollectionViewFlowLayout: UICollectionViewFlowLayout {
// inverting the transform in the layout, rather than directly on the cell,
// is the only way I've found to prevent cells from flipping during animated
// cell updates
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attrs = super.layoutAttributesForItem(at: indexPath)
attrs?.transform = CGAffineTransform(scaleX: 1, y: -1)
return attrs
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attrsList = super.layoutAttributesForElements(in: rect)
if let list = attrsList {
for i in 0..<list.count {
list[i].transform = CGAffineTransform(scaleX: 1, y: -1)
}
}
return attrsList
}
}
UICollectionView with a reversed flow layout and dynamic cell and header height.
import Foundation
import UIKit
class InvertedFlowLayout: UICollectionViewFlowLayout {
override func prepare() {
super.prepare()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard super.layoutAttributesForElements(in: rect) != nil else { return nil }
var attributesArrayNew = [UICollectionViewLayoutAttributes]()
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
for item in 0 ..< collectionView.numberOfItems(inSection: section) {
let indexPathCurrent = IndexPath(item: item, section: section)
if let attributeCell = layoutAttributesForItem(at: indexPathCurrent) {
if attributeCell.frame.intersects(rect) {
attributesArrayNew.append(attributeCell)
}
}
}
}
for section in 0 ..< collectionView.numberOfSections {
let indexPathCurrent = IndexPath(item: 0, section: section)
if let attributeKind = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: indexPathCurrent) {
attributesArrayNew.append(attributeKind)
}
}
}
return attributesArrayNew
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributeKind = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath)
if let collectionView = self.collectionView {
var fullHeight: CGFloat = 0.0
for section in 0 ..< indexPath.section + 1 {
for item in 0 ..< collectionView.numberOfItems(inSection: section) {
let indexPathCurrent = IndexPath(item: item, section: section)
fullHeight += cellHeight(indexPathCurrent) + minimumLineSpacing
}
}
attributeKind.frame = CGRect(x: 0, y: collectionViewContentSize.height - fullHeight - CGFloat(indexPath.section + 1) * headerHeight(indexPath.section) - sectionInset.bottom + minimumLineSpacing/2, width: collectionViewContentSize.width, height: headerHeight(indexPath.section))
}
return attributeKind
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributeCell = UICollectionViewLayoutAttributes(forCellWith: indexPath)
if let collectionView = self.collectionView {
var fullHeight: CGFloat = 0.0
for section in 0 ..< indexPath.section + 1 {
for item in 0 ..< collectionView.numberOfItems(inSection: section) {
let indexPathCurrent = IndexPath(item: item, section: section)
fullHeight += cellHeight(indexPathCurrent) + minimumLineSpacing
if section == indexPath.section && item == indexPath.item {
break
}
}
}
attributeCell.frame = CGRect(x: 0, y: collectionViewContentSize.height - fullHeight + minimumLineSpacing - CGFloat(indexPath.section) * headerHeight(indexPath.section) - sectionInset.bottom, width: collectionViewContentSize.width, height: cellHeight(indexPath) )
}
return attributeCell
}
override var collectionViewContentSize: CGSize {
get {
var height: CGFloat = 0.0
var bounds = CGRect.zero
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
for item in 0 ..< collectionView.numberOfItems(inSection: section) {
let indexPathCurrent = IndexPath(item: item, section: section)
height += cellHeight(indexPathCurrent) + minimumLineSpacing
}
}
height += sectionInset.bottom + CGFloat(collectionView.numberOfSections) * headerHeight(0)
bounds = collectionView.bounds
}
return CGSize(width: bounds.width, height: max(height, bounds.height))
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if let oldBounds = self.collectionView?.bounds,
oldBounds.width != newBounds.width || oldBounds.height != newBounds.height {
return true
}
return false
}
func cellHeight(_ indexPath: IndexPath) -> CGFloat {
if let collectionView = self.collectionView, let delegateFlowLayout = collectionView.delegate as? UICollectionViewDelegateFlowLayout {
let size = delegateFlowLayout.collectionView!(collectionView, layout: self, sizeForItemAt: indexPath)
return size.height
}
return 0
}
func headerHeight(_ section: Int) -> CGFloat {
if let collectionView = self.collectionView, let delegateFlowLayout = collectionView.delegate as? UICollectionViewDelegateFlowLayout {
let size = delegateFlowLayout.collectionView!(collectionView, layout: self, referenceSizeForHeaderInSection: section)
return size.height
}
return 0
}
}
you could rotate(transform) collectionView 180 degrees so top will be at the bottom and rotate(transform) each cell 180 to make them look normal.
I've created custom collection view layout(for MultiDirectional scroll), running successfully. Now, I tried to achieve pagination from all sides (top, left, right and bottom). For that, I am tracing the scroll direction and upon reaching the border elements, I wish to paginate(load more data) in that direction. But as I need to place the page default to top-left position, the top-left elements are already reached and hence resulting in conflicts. Can you help?
Following code I tried:
import UIKit
class ViewController: UIViewController{
var cellId: String = "MyCellId"
var vPivotPrev = 0
var vPivotNext = 19
var hPivotPrev = 0
var hPivotNext = 29
var targetPoint = CGPoint(x: 0, y: 0)
var scrollDirection: String = ""
var scrolledDailyToTop:String = ""
var scrolledDailyToBottom:String = ""
var scrolledDailyToLeft:String = ""
var scrolledDailyToRight:String = ""
var shouldPaginate:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
showCollectionView()
}
var customCollectionView: UICollectionView = { // CODE TO CREATE CUSTOM COLLECTION VIEW
let layout = CustomCollectionViewLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
return cv
}()
class CustomCell: UICollectionViewCell{ // CODE TO CREATE CUSTOM CELLS
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
let label: UILabel = {
let myLabel = UILabel()
return myLabel
}()
func setup(){
addSubview(label)
}
}
func showCollectionView(){
view.addSubview(customCollectionView)
customCollectionView.backgroundColor = .darkGray
customCollectionView.frame = CGRect(x: 60, y: 145, width: UIScreen.main.bounds.size.width-120, height: UIScreen.main.bounds.size.height/1.8)
customCollectionView.dataSource = self
customCollectionView.delegate = self
customCollectionView.register(CustomCell.self, forCellWithReuseIdentifier: cellId) // REGISTER CELL CLASS
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// DETECT SCROLL DIRECTION CODE
var currentPoint = scrollView.contentOffset
if(targetPoint.y > currentPoint.y){
scrollDirection = "going up"
print(scrollDirection)
}
else if(targetPoint.y < currentPoint.y){
scrollDirection = "going down"
print(scrollDirection)
}
if(targetPoint.x > currentPoint.x){
scrollDirection = "going left"
print(scrollDirection)
}
else if(targetPoint.x < currentPoint.x){
scrollDirection = "going right"
print(scrollDirection)
}
self.targetPoint = scrollView.contentOffset
}
}
extension ViewController: UICollectionViewDataSource{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 20
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 30
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomCell
cell.label.text = "\(indexPath.section.description),\(indexPath.item.description)"
cell.backgroundColor = .lightGray
cell.label.frame = CGRect(x: 0, y: 0, width: cell.contentView.bounds.width, height: 10)
cell.label.textColor = .white
cell.bringSubview(toFront: cell.label)
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
for visibleIndices in collectionView.indexPathsForVisibleItems{
if(visibleIndices.section == vPivotPrev){
scrolledDailyToTop = "top"
shouldPaginate = true
paginate()
}
else if(visibleIndices.section == vPivotNext){
scrolledDailyToBottom = "bottom"
shouldPaginate = true
paginate()
}
else if(visibleIndices.item == hPivotPrev){
scrolledDailyToLeft = "left"
shouldPaginate = true
paginate()
}
else if(visibleIndices.item == hPivotNext){
scrolledDailyToRight = "right"
shouldPaginate = true
paginate()
}
}
}
func paginate(){
print("Swipe Direction: \(scrollDirection)")
if(shouldPaginate == true){
if(self.scrollDirection == "going down" && self.scrolledDailyToTop == "top"){ // PAGINATING USING 9 BOX LOGIC
print("Fetch Vertical previous page")
//getNewPageData(gotopageURL: self.goToPageURL)
self.customCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .bottom, animated: false)
self.customCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .left, animated: false)
shouldPaginate = false
}
else if(self.scrollDirection == "going up" && self.scrolledDailyToBottom == "bottom"){
print("Fetch Vertical next page")
//getNewPageData(gotopageURL: self.goToPageURL)
self.customCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
self.customCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .left, animated: false)
shouldPaginate = false
}
else if(self.scrollDirection == "going right" && self.scrolledDailyToLeft == "left"){
print("Fetch Horizontal previous page")
//getNewPageData(gotopageURL: self.goToPageURL)
self.customCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
self.customCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .right, animated: false)
shouldPaginate = false
}
else if(self.scrollDirection == "going left" && self.scrolledDailyToRight == "right"){
print("Fetch Horizontal next page")
//getNewPageData(gotopageURL: self.goToPageURL)
self.customCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
self.customCollectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .left, animated: false)
shouldPaginate = false
}
scrolledDailyToTop = ""
scrolledDailyToBottom = ""
scrolledDailyToLeft = ""
scrolledDailyToRight = ""
}
}
}
extension ViewController: UICollectionViewDelegate{
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
let cell:CustomCell = collectionView.cellForItem(at: indexPath)! as! ViewController.CustomCell
print("SELECTED CELL: \(cell.label.text!)")
return true
}
}
The code for Multi-Directional Scroll CustomCollectionViewLayout
import UIKit
class CustomCollectionViewLayout:UICollectionViewFlowLayout {
let CELL_HEIGHT = 50.0
let CELL_WIDTH = 80.0
let STATUS_BAR = UIApplication.shared.statusBarFrame.height
var cellAttrsDictionary = Dictionary<IndexPath, UICollectionViewLayoutAttributes>()
var contentSize = CGSize.zero
var dataSourceDidUpdate = true
override var collectionViewContentSize : CGSize {
return self.contentSize
}
override func prepare() {
dataSourceDidUpdate = false
cellAttrsDictionary.removeAll()
if let sectionCount = collectionView?.numberOfSections, sectionCount > 0 {
for section in 0...sectionCount-1 {
if let rowCount = collectionView?.numberOfItems(inSection: section), rowCount > 0 {
for item in 0...rowCount-1 {
let cellIndex = IndexPath(item: item, section: section)
let xPos = Double(item) * CELL_WIDTH
let yPos = Double(section) * CELL_HEIGHT
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
if section == 0 && item == 0 {
cellAttributes.zIndex = 4
} else if section == 0 {
cellAttributes.zIndex = 3
} else if item == 0 {
cellAttributes.zIndex = 2
} else {
cellAttributes.zIndex = 1
}
cellAttrsDictionary[cellIndex] = cellAttributes
}
}
}
}
let contentWidth = Double(collectionView!.numberOfItems(inSection: 0)) * CELL_WIDTH
let contentHeight = Double(collectionView!.numberOfSections) * CELL_HEIGHT
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesInRect = [UICollectionViewLayoutAttributes]()
for cellAttributes in cellAttrsDictionary.values {
if rect.intersects(cellAttributes.frame) {
attributesInRect.append(cellAttributes)
}
}
return attributesInRect
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttrsDictionary[indexPath]!
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
So, I have a CollectionView inside a TableViewCell, and I want to pass the cell view of that embed Collection view to an ViewControllerAnimatedTransioning when someone click on the cell. My Problem is, when you click the cell I save the indexPath inside a var so when I execute the delegate in the transition I can retrieve that cell, the thing is that the delegate works fine but it's returning nil. I have several hours trying to figure out, and I don't really know whats happening. I don't know if it's a Problem with the instances but It isn't working. Here I left you my Code. The cell I'm trying to pass is in ForYouCell.
HomeCellTransition
import UIKit
protocol HomeCellTransitionDelegate {
func transition(for: HomeCellTransition) -> UIView!
}
class HomeCellTransition: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case presenting
case dismissing
case none
}
enum TransitionState {
case initial
case final
}
let duration: TimeInterval = 0.8
var type: TransitionType = .none
var topSnapshot: UIView!
var cellSnapshot: UIView!
var bottomSnapshot: UIView!
var secondViewTopSnapshot: UIView!
var secondViewBottomSnapshot: UIView!
var delegate: HomeCellTransitionDelegate = ForYouCell()
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toVC = transitionContext.viewController(forKey: .to), let fromVC = transitionContext.viewController(forKey: .from) else {
return
}
let containerView = transitionContext.containerView
let presentingViewController = type == .presenting ? fromVC : toVC
let dismissingViewController = type == .presenting ? toVC : fromVC
let cellView = delegate.transition(for: self)
let targetFrame = cellView?.convert((cellView?.frame)!, to: cellView?.superview)
let percentCutImage: CGFloat = 0.10
let secondImageView = (dismissingViewController as! DetailController).topImage
snapshotViews(presentingVC: presentingViewController, dismissingVC: dismissingViewController, cellView: cellView!, targetFrame: targetFrame!, percentCutImage: percentCutImage, secondImageView: secondImageView)
containerView.addSubview(toVC.view)
containerView.addSubview(topSnapshot)
containerView.addSubview(cellSnapshot)
containerView.addSubview(bottomSnapshot)
containerView.addSubview(secondViewTopSnapshot)
containerView.addSubview(secondViewBottomSnapshot)
toVC.view.isHidden = true
topSnapshot.frame = CGRect.init(x: 0, y: 0, width: (presentingViewController.view.frame.width), height: (targetFrame?.minY)!)
cellSnapshot.frame = CGRect.init(x: 0, y: 0, width: (cellView?.frame.width)!, height: (cellView?.frame.height)! - ((cellView?.frame.height)! * percentCutImage))
bottomSnapshot.frame = CGRect.init(x: 0, y: targetFrame!.maxY, width: (presentingViewController.view.frame.width), height: (presentingViewController.view.frame.height) - (targetFrame?.maxY)!)
UIView.animate(withDuration: duration, animations: {
self.topSnapshot.frame.size.height = 0
self.bottomSnapshot.frame.origin.y = 0
self.cellSnapshot.frame = secondImageView.frame
}) { (completed) in
print("completed")
}
}
func snapshotViews(presentingVC: UIViewController, dismissingVC: UIViewController, cellView: UIView, targetFrame: CGRect, percentCutImage: CGFloat, secondImageView: UIImageView) {
let presentingView = presentingVC.view
let dismissingView = dismissingVC.view
let percentCutImage: CGFloat = percentCutImage
let secondImageView = secondImageView
let targetFrame = targetFrame
topSnapshot = presentingView?.resizableSnapshotView(from: CGRect.init(x: 0, y: 0, width: (presentingView?.frame.width)!, height: targetFrame.minY), afterScreenUpdates: false, withCapInsets: .zero)
cellSnapshot = cellView.resizableSnapshotView(from: CGRect.init(x: 0, y: 0, width: cellView.frame.width, height: cellView.frame.height - (cellView.frame.height * percentCutImage)), afterScreenUpdates: false, withCapInsets: .zero)
bottomSnapshot = presentingView?.resizableSnapshotView(from: CGRect.init(x: 0, y: targetFrame.maxY, width: (presentingView?.frame.width)!, height: (presentingView?.frame.height)! - targetFrame.maxY), afterScreenUpdates: false, withCapInsets: .zero)
secondViewTopSnapshot = dismissingView?.resizableSnapshotView(from: CGRect.init(x: 0, y: 0, width: (dismissingView?.frame.width)!, height: secondImageView.frame.height - (secondImageView.frame.height * percentCutImage)), afterScreenUpdates: true, withCapInsets: .zero)
secondViewBottomSnapshot = dismissingView?.resizableSnapshotView(from: CGRect.init(x: 0, y: secondImageView.frame.maxY - (secondImageView.frame.height * percentCutImage), width: (dismissingView?.frame.width)!, height: (dismissingView?.frame.height)! - (secondImageView.frame.height - (secondImageView.frame.height * percentCutImage))), afterScreenUpdates: true, withCapInsets: .zero)
}
}
extension HomeCellTransition: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
type = .presenting
return self
}
}
ForYouCell
//
// ForYouCell.swift
// AirbnbNav
//
// Created by Leonardo Dominguez on 9/24/17.
// Copyright © 2017 Leonardo Dominguez. All rights reserved.
//
import UIKit
protocol HeaderControllerPresentDelegate {
func didSelectCell()
}
class ForYouCell: UITableViewCell {
let cellIdentifier = "forYouCell"
var delegate: HeaderControllerPresentDelegate?
let itemsInsets: CGFloat = 15
var selectedCell: IndexPath?
let sectionLabel: UILabel = {
let lbl = UILabel()
lbl.text = "Section"
lbl.font = UIFont.boldSystemFont(ofSize: 14)
return lbl
}()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
return cv
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
collectionView.register(ItemCell.self, forCellWithReuseIdentifier: cellIdentifier)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.contentInset = UIEdgeInsets(top: 0, left: itemsInsets, bottom: 0, right: itemsInsets)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
addSubview(sectionLabel)
addSubview(collectionView)
_ = sectionLabel.anchor(top: topAnchor, bottom: nil, right: rightAnchor, left: leftAnchor, topConstant: 0, bottomConstant: 0, rightConstant: 0, leftConstant: itemsInsets, widthConstant: 0, heightConstant: 30)
_ = collectionView.anchor(top: sectionLabel.bottomAnchor, bottom: bottomAnchor, right: rightAnchor, left: leftAnchor, topConstant: 0, bottomConstant: 0, rightConstant: 0, leftConstant: 0, widthConstant: 0, heightConstant: 0)
}
}
extension ForYouCell: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? ItemCell {
cell.backgroundColor = .red
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedCell = indexPath
delegate?.didSelectCell()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.height + 50, height: collectionView.frame.size.height - 20)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 15
}
}
extension ForYouCell: HomeCellTransitionDelegate {
func transition(for: HomeCellTransition) -> UIView! {
let celll = collectionView.cellForItem(at: selectedCell!)
print(celll)
print("k")
return celll
}
}
HeaderController
//
// HeaderController.swift
// AirbnbNav
//
// Created by Leonardo Dominguez on 9/19/17.
// Copyright © 2017 Leonardo Dominguez. All rights reserved.
//
import UIKit
enum HeaderSizes {
case min
case med
case max
}
protocol HeaderControllerDelegate {
func didCollapse()
func didExpand()
}
class HeaderController: UIViewController {
// Status bar
var isHiddenStatusBar: Bool = false {
didSet {
UIView.animate(withDuration: 0.3) {
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
override var prefersStatusBarHidden: Bool {
return isHiddenStatusBar
}
override var preferredStatusBarStyle: UIStatusBarStyle {
if currentHeaderSize == .min {
return .default
}
return .lightContent
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .fade
}
// Properties
let tableView: UITableView = {
let tv = UITableView(frame: .zero)
tv.separatorStyle = .none
return tv
}()
let maxHeight: CGFloat = 300
let medHeight: CGFloat = 135 // 55 del height + 10 padding superior + 10 de padding inferior + 10 statusbar
let minHeight: CGFloat = 65 // 55 height del menu + 10 padding
var previousScroll: CGFloat = 0
var currentHeaderSize: HeaderSizes = .max
var currentHeaderHeight: NSLayoutConstraint?
fileprivate let cellIdentifier = "cellIdentifier"
let detailController = DetailController()
var selectedCell: UITableViewCell?
lazy var headerView: HeaderView = {
let hv = HeaderView(maxHeight: self.maxHeight, medHeight: self.medHeight, minHeight: self.minHeight, paddingBetween: 10)
return hv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
tableView.delegate = self
tableView.dataSource = self
tableView.register(ForYouCell.self, forCellReuseIdentifier: cellIdentifier)
headerView.headerControllerDelegate = self
}
func setupViews() {
view.addSubview(headerView)
view.addSubview(tableView)
currentHeaderHeight = headerView.anchor(top: view.topAnchor, bottom: nil, right: view.rightAnchor, left: view.leftAnchor, topConstant: 0, bottomConstant: 0, rightConstant: 0, leftConstant: 0, widthConstant: 0, heightConstant: maxHeight)[3]
_ = tableView.anchor(top: headerView.bottomAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, left: view.leftAnchor)
}
}
extension HeaderController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ForYouCell {
cell.delegate = self
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 250
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let absoluteTop: CGFloat = 0
let absoluteBottom: CGFloat = scrollView.contentSize.height - scrollView.frame.height
let scrollRange: CGFloat = scrollView.contentOffset.y - previousScroll
let isScrollingDown = scrollView.contentOffset.y > previousScroll && scrollView.contentOffset.y > absoluteTop
let isScrollingUp = scrollView.contentOffset.y < previousScroll && scrollView.contentOffset.y < absoluteBottom
var newHeight: CGFloat = currentHeaderHeight!.constant
if isScrollingDown {
newHeight = max(minHeight, ((currentHeaderHeight?.constant)! - abs(scrollRange)))
} else if isScrollingUp {
newHeight = min(maxHeight, ((currentHeaderHeight?.constant)! + abs(scrollRange)))
}
if newHeight != currentHeaderHeight?.constant {
currentHeaderHeight?.constant = newHeight
tableView.contentOffset = CGPoint(x: tableView.contentOffset.x, y: previousScroll)
let minMedAverage: CGFloat = (minHeight + medHeight) / 2
let medMaxAverage: CGFloat = (medHeight + maxHeight) / 2
if currentHeaderHeight!.constant < minMedAverage {
currentHeaderSize = .min
} else if currentHeaderHeight!.constant >= minMedAverage && currentHeaderHeight!.constant < medMaxAverage {
currentHeaderSize = .med
} else if currentHeaderHeight!.constant >= medMaxAverage {
currentHeaderSize = .max
}
updateHeader()
}
previousScroll = scrollView.contentOffset.y
}
func snapHeader(toSize: HeaderSizes) {
switch toSize {
case .max:
currentHeaderHeight?.constant = maxHeight
case .med:
currentHeaderHeight?.constant = medHeight
case .min:
currentHeaderHeight?.constant = minHeight
}
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
snapHeader(toSize: currentHeaderSize)
updateHeader()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
snapHeader(toSize: currentHeaderSize)
updateHeader()
}
}
func updateHeader() {
let range = maxHeight - medHeight
let percent = (currentHeaderHeight!.constant - medHeight) / range
headerView.updateHeader(percentage: percent, currentHeaderHeight: currentHeaderHeight!.constant)
// Status bar
isHiddenStatusBar = currentHeaderHeight!.constant < (medHeight / 2) && currentHeaderHeight!.constant > minHeight ? true : false
}
}
extension HeaderController: HeaderControllerPresentDelegate {
func didSelectCell() {
present(detailController, animated: true, completion: nil)
}
}
extension HeaderController: HeaderControllerDelegate {
func didExpand() {
print("")
}
func didCollapse() {
snapHeader(toSize: .min)
}
}
I have ran into a problem during UICollectionView custom layout implementation.
The thing is that I need to calculate collection view cell's height in custom layout's prepare(). To do so I have:
func heightForItem(_ collectionView: UICollectionView, at indexPath: IndexPath) -> CGFloat
method in custom layout's delegate protocol which is implemented in my view controller.
However this method is called before cell is dequeued and actually has any data based on which I could calculate it's height. Thus if cell's content exceeds it's initial bounds - I can't see part of content.
Has anyone encountered the same problem with custom layout? How did you solve it?
Protocol for custom layout:
protocol CustomLayoutDelegateProtocol: class {
func numberOfSectionsInRow() -> Int
func indecesOfSectionsInRow() -> [Int]
func minimumInteritemSpace() -> CGFloat
func heightForItem(_ collectionView: UICollectionView, at indexPath: IndexPath) -> CGFloat
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
}
Custom layout itself:
class CustomLayoutClass: UICollectionViewLayout {
weak var delegate: CustomLayoutDelegateProtocol? {
didSet {
setupLayout()
}
}
private var cache = [UICollectionViewLayoutAttributes]()
private var contentHeight: CGFloat = 0.0
private var contentWidth: CGFloat {
guard let collectionView = collectionView else { return 0 }
return collectionView.bounds.width
}
private var interitemSpace: CGFloat?
private var numberOfColumns: Int?
private var columnedSections: [Int]?
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func setupLayout() {
numberOfColumns = delegate?.numberOfSectionsInRow()
columnedSections = delegate?.indecesOfSectionsInRow()
interitemSpace = delegate?.minimumInteritemSpace()
}
override func invalidateLayout() {
cache.removeAll()
super.invalidateLayout()
}
override func prepare() {
if cache.isEmpty {
guard let collectionView = collectionView,
let numberOfColumns = numberOfColumns,
let columnedSections = columnedSections,
let interitemSpace = interitemSpace else { return }
let columnWidth = (contentWidth / CGFloat(numberOfColumns))
var xOffset = [CGFloat]()
for column in 0..<numberOfColumns {
var interitemSpace = interitemSpace
if column == 0 { interitemSpace = 0 }
xOffset.append(CGFloat(column) * columnWidth + interitemSpace)
}
var yOffset: CGFloat = 0.0
for section in 0..<collectionView.numberOfSections {
for item in 0..<collectionView.numberOfItems(inSection: section) {
let indexPath = IndexPath(item: item, section: section)
guard let sectionInsets = delegate?.collectionView(collectionView, layout: self, insetForSectionAt: indexPath.section),
let height = delegate?.heightForItem(collectionView, at: indexPath) else { continue }
let width = columnedSections.contains(section) ? columnWidth : contentWidth
let xOffsetIdx = columnedSections.contains(section) ? columnedSections.index(of: section)! % numberOfColumns : 0
yOffset += sectionInsets.top
let frame = CGRect(x: xOffset[xOffsetIdx], y: yOffset, width: width, height: height)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = frame
cache.append(attributes)
contentHeight = max(contentHeight, frame.maxY)
let isLastInRow = (columnedSections.contains(section) && columnedSections.index(of: section)! % numberOfColumns == (numberOfColumns-1))
let isNotColumnedSection = !columnedSections.contains(section)
if isLastInRow || isNotColumnedSection {
yOffset += height + sectionInsets.bottom
}
}
}
}
}
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if attributes.frame.intersects(rect) {
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
}
And implementation for protocol (from view controller):
extension ViewController: CustomLayoutDelegateProtocol {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
guard let sectionType = InvoiceSectionIndexType(rawValue: section) else { return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) }
switch sectionType {
case .receiver:
return UIEdgeInsets(top: Constants.bigLineSpace, left: 0, bottom: Constants.bigLineSpace, right: 0)
default:
return UIEdgeInsets(top: 0, left: 0, bottom: Constants.commonLineSpace, right: 0)
}
}
func numberOfSectionsInRow() -> Int {
return Constants.numberOfSectionsInRow
}
func indecesOfSectionsInRow() -> [Int] {
return [0, 1]
}
func minimumInteritemSpace() -> CGFloat {
return Constants.interitemSpace
}
func heightForItem(_ collectionView: UICollectionView, at indexPath: IndexPath) -> CGFloat {
guard let sectionType = InvoiceSectionIndexType(rawValue: indexPath.section) else { return 0 }
switch sectionType {
case .next:
return Constants.nextButtonHeight
default:
return Constants.estimatedRowHeight
}
}
}
I resolved this issue on my own and the solution is quite simple though not very obvious.
Since we have indexPath for concrete cell we can find our content (in my case content is simple text which is put into a label). Then we can create label (which we will not display since it's needed only for calculations) with width (in my case I know width for cells) and height which is CGFloat.greatestFiniteMagnitude. We then apply sizeToFit() to our label and now we have label with appropriate height. The only thing we should do now - apply this new height to our cell.
Sample code:
func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat {
// ask your DataSource for piece of data with indexPath
let testLabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
testLabel.text = // your text
testLabel.sizeToFit()
if testLabel.bounds.height > Constants.defaultContentLabelHeight {
return Constants.estimatedRowHeight + (testLabel.bounds.height - Constants.defaultContentLabelHeight)
} else {
return Constants.estimatedRowHeight
}
}