UICollectionView Cell Custom FlowLayout Breaks When New Post is Added on Top












0















Description:
I have been working on an app which displays images and caption in a CollectionView. I have made a custom Cell and CustomFlowLayout for the cell where the width of the cell, image and caption is equal to width of screen and height will change according to aspect ratio of the image height and height needed for the caption. The images which are displayed are stored on FirebaseStorage and I have also saved imagewidth and imageheight in firebaseDatabase. I Use SD_Web images to load and cache images. When the app loads for the first time the layout works perfectly fine as expected. The cells are arranged according to image height and caption height , with images being in aspect ratio.
The main problem occurs when a new post is done. I insert new post on the top of collectionview, and when the post is received the layout breaks, images becomes messed up, the caption goes on top image, lot of white space between image or caption. when I terminate the app from background and run it again , this time the layout is perfectly fine with new post present. I have to terminate app after everyone post to make the layout work.



What I feel the problem is, when I post the image. The new post is added on the top cell and the item on top cell pushed below without having the old height. how can I take this problem. I tried searching a lot but still of no use.
PS:
Using Autolayout for cell in IB.



FactsFeverLayout Class



import UIKit
protocol FactsFeverLayoutDelegate: class {
func collectionView(CollectionView: UICollectionView, heightForThePhotoAt indexPath: IndexPath, with width: CGFloat) -> CGFloat

func collectionView(CollectionView: UICollectionView, heightForCaptionAt indexPath: IndexPath, with width: CGFloat) -> CGFloat
}



class FactsFeverLayout: UICollectionViewLayout {

var cellPadding : CGFloat = 5.0
var delegate: FactsFeverLayoutDelegate?

private var contentHeight : CGFloat = 0.0
private var contentWidth : CGFloat {
let insets = collectionView!.contentInset
return (collectionView!.bounds.width - insets.left + insets.right)
}
private var attributeCache = [FactsFeverLayoutAttributes]()
override func prepare() {
if attributeCache.isEmpty {
let containerWidth = contentWidth
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0

for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)

let width = containerWidth - cellPadding * 2
let photoHeight:CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForThePhotoAt: indexPath, with: width))!
let captionHeight: CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForCaptionAt: indexPath, with: width))!


let height: CGFloat = cellPadding + photoHeight + captionHeight + cellPadding

let frame = CGRect(x: xOffset, y: yOffset, width: containerWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

// Create CEll Layout Atributes
let attributes = FactsFeverLayoutAttributes(forCellWith: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
attributeCache.append(attributes)
// Update The Colunm any Y axis
contentHeight = max(contentHeight, frame.maxY)
yOffset = yOffset + height


}


}
}

override var collectionViewContentSize: CGSize{
return CGSize(width: contentWidth, height: contentHeight)
}

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()

for attributes in attributeCache {
if attributes.frame.intersects(rect){
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
}
// UICollectionView FlowLayout
// Abstract
class FactsFeverLayoutAttributes: UICollectionViewLayoutAttributes {
var photoHeight : CGFloat = 0.0
override func copy(with zone: NSZone? = nil) -> Any {
let copy = super.copy(with: zone) as! FactsFeverLayoutAttributes
copy.photoHeight = photoHeight
return copy
}

override func isEqual(_ object: Any?) -> Bool {
if let attributes = object as? FactsFeverLayoutAttributes {
if attributes.photoHeight == photoHeight {
super.isEqual(object)

}
}
return false
}
}


CollectionViewCell Class



class NewCellCollectionViewCell: UICollectionViewCell {

var facts: Facts!
var currentUser = Auth.auth().currentUser?.uid

// IBOutlets
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var imageHeightConstraint: NSLayoutConstraint!

@IBOutlet weak var likeLable: UILabel!
@IBOutlet weak var likeButton: UIButton!
@IBOutlet weak var infoButton: UIButton!
@IBOutlet weak var buttonView: UIView!
@IBOutlet weak var captionTextView: UITextView!

override func awakeFromNib() {
super.awakeFromNib()
likeButton.setImage(UIImage(named: "noLike"), for: .normal)
likeButton.setImage(UIImage(named: "like"), for: .selected)
setupLayout()
}


func configureCell(fact: Facts){
facts = fact

imageView.sd_setImage(with: URL(string: fact.factsLink))
likeLable.text = String(fact.factsLikes.count)
captionTextView.text = fact.captionText
let factsRef = Database.database().reference().child("Facts").child(facts.factsId).child("likes")
factsRef.observeSingleEvent(of: .value) { (snapshot) in
if fact.factsLikes.contains(self.currentUser!){
self.likeButton.isSelected = true
} else {
self.likeButton.isSelected = false
}


}
}


override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
if let attributes = layoutAttributes as? FactsFeverLayoutAttributes {
imageHeightConstraint.constant = attributes.photoHeight
}
}
}


ViewController Class



    class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//MARK: Outlets

@IBOutlet weak var uploadButtonOutlet: UIBarButtonItem!
@IBOutlet weak var collectionView: UICollectionView!

//MARK:- Properties

var images: [UIImage] =
var factsArray:[Facts] = [Facts]()
var likeUsers:[String] =
let currentUser = Auth.auth().currentUser?.uid



private let refreshControl = UIRefreshControl()

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if #available(iOS 10.0, *) {
collectionView.refreshControl = refreshControl
} else {
collectionView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(refreshView), for: .valueChanged)
refreshControl.tintColor = UIColor.white

if let layout = collectionView?.collectionViewLayout as? FactsFeverLayout {
layout.delegate = self
}
collectionView.backgroundColor = UIColor.black
observeFactsFromFirebase()
}


@objc func refreshView(){
observeFactsFromFirebase()
}






//MARK:- Upload Facts

@IBAction func uploadButtonPressed(_ sender: Any) {

self.selectPhoto()
(deleted the function of selectPhoto but it works, UIImagePicker is used)

}

private func uploadImageToFirebaseStorage(image: UIImage, completion: @escaping (_ imageUrl: String) -> ()){
let imageName = NSUUID().uuidString + ".jpg"
let ref = Storage.storage().reference().child("message_images").child(imageName)

if let uploadData = image.jpegData(compressionQuality: 0.2){
ref.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(" Failed to upload Image", error)
}
ref.downloadURL(completion: { (url, err) in
if let err = err {
print("Unable to upload image into storage due to (err)")
}
let messageImageURL = url?.absoluteString
completion(messageImageURL!)

})

})
}
}

func addToDatabase(imageUrl:String, caption: String, image: UIImage){
let Id = NSUUID().uuidString
likeUsers.append(currentUser!)
let timeStamp = NSNumber(value: Int(NSDate().timeIntervalSince1970))
let factsDB = Database.database().reference().child("Facts")
let factsDictionary = ["factsLink": imageUrl, "likes": likeUsers, "factsId": Id, "timeStamp": timeStamp, "captionText": caption, "imageWidth": image.size.width, "imageHeight": image.size.height] as [String : Any]
factsDB.child(Id).setValue(factsDictionary){
(error, reference) in

if error != nil {
print(error)
ProgressHUD.showError("Image Upload Failed")
self.uploadButtonOutlet.isEnabled = true
return

} else{
print("Message Saved In DB")
ProgressHUD.showSuccess("image Uploded Successfully")
self.uploadButtonOutlet.isEnabled = true

self.observeFactsFromFirebase()
}
}
}


var imageUrl: [String] =
func observeFactsFromFirebase(){

let factsDB = Database.database().reference().child("Facts").queryOrdered(byChild: "timeStamp")
factsDB.observe(.value){ (snapshot) in
print("Observer Data snapshot (snapshot.value)")

self.factsArray =
self.imageUrl =
self.likeUsers =

if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {

if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let id = snap.key
let facts = Facts(dictionary: postDictionary)
self.factsArray.insert(facts, at: 0)
self.imageUrl.insert(facts.factsLink, at: 0)

}
}
}
self.collectionView.reloadData()

self.refreshControl.endRefreshing()


}
collectionView.reloadData()
}
// Download Image From Database
//-> Here I download image from firebase and store it locally and append it to images array (Deleted the code to remove unwanted clutter)
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

//MARK: Data Source
extension ViewController: UICollectionViewDataSource{

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return factsArray.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let facts = factsArray[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "newCellTrial", for: indexPath) as? NewCellCollectionViewCell

cell?.configureCell(fact: facts)
cell?.infoButton.addTarget(self, action: #selector(reportButtonPressed), for: .touchUpInside)

return cell!
}
}
extension ViewController: UICollectionViewDelegate {

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
let photos = IDMPhoto.photos(withURLs: imageUrl)
let browser = IDMPhotoBrowser(photos: photos)
browser?.setInitialPageIndex(UInt(indexPath.row))
self.present(browser!, animated: true, completion: nil)
}
}
extension ViewController: FactsFeverLayoutDelegate {
func collectionView(CollectionView: UICollectionView, heightForThePhotoAt indexPath: IndexPath, with width: CGFloat) -> CGFloat {
let facts = factsArray[indexPath.item]
let imageSize = CGSize(width: CGFloat(facts.imageWidht), height: CGFloat(facts.imageHeight))
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let rect = AVMakeRect(aspectRatio: imageSize, insideRect: boundingRect)

return rect.size.height

}

func collectionView(CollectionView: UICollectionView, heightForCaptionAt indexPath: IndexPath, with width: CGFloat) -> CGFloat {
let fact = factsArray[indexPath.item]
let topPadding = CGFloat(8)
let bottomPadding = CGFloat(8)
let captionFont = UIFont.systemFont(ofSize: 15)
let viewHeight = CGFloat(40) //-> There is view below caption which holds like button and info button its height is constant (40)
let captionHeight = self.height(for: fact.captionText, with: captionFont, width: width)
let height = topPadding + captionHeight + topPadding + viewHeight + bottomPadding + topPadding + 10

return height

}

func height(for text: String, with font: UIFont, width: CGFloat) -> CGFloat {
let nsstring = NSString(string: text)
let maxHeight = CGFloat(1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let textAttributes = [NSAttributedString.Key.font: font]
let boundingRect = nsstring.boundingRect(with: CGSize(width: width, height: maxHeight), options: options, attributes: textAttributes, context: nil)

return ceil(boundingRect.height)
}


}









share|improve this question























  • Here the width of CollectionViewCell is constant and equal to the screen's width? Then I think you don't need a custom UICollectionViewLayout. Try using the default FlowLayout.

    – Damon
    Nov 25 '18 at 12:30













  • i think this is due to attributeCache.as first cell Attribute is Already Storead in Cache.When you append new post at Top it access old Attribute From Cache For indexpath(0,0).you should try Cache.removeAll() than CollectionView.invalidLayout.

    – Bhavesh.iosDev
    Nov 26 '18 at 9:42











  • Thank You for pointing me in the direction. I solved the issue by adding attributeCache.removeAll in prepare.

    – shree bhagwat
    Nov 26 '18 at 11:36
















0















Description:
I have been working on an app which displays images and caption in a CollectionView. I have made a custom Cell and CustomFlowLayout for the cell where the width of the cell, image and caption is equal to width of screen and height will change according to aspect ratio of the image height and height needed for the caption. The images which are displayed are stored on FirebaseStorage and I have also saved imagewidth and imageheight in firebaseDatabase. I Use SD_Web images to load and cache images. When the app loads for the first time the layout works perfectly fine as expected. The cells are arranged according to image height and caption height , with images being in aspect ratio.
The main problem occurs when a new post is done. I insert new post on the top of collectionview, and when the post is received the layout breaks, images becomes messed up, the caption goes on top image, lot of white space between image or caption. when I terminate the app from background and run it again , this time the layout is perfectly fine with new post present. I have to terminate app after everyone post to make the layout work.



What I feel the problem is, when I post the image. The new post is added on the top cell and the item on top cell pushed below without having the old height. how can I take this problem. I tried searching a lot but still of no use.
PS:
Using Autolayout for cell in IB.



FactsFeverLayout Class



import UIKit
protocol FactsFeverLayoutDelegate: class {
func collectionView(CollectionView: UICollectionView, heightForThePhotoAt indexPath: IndexPath, with width: CGFloat) -> CGFloat

func collectionView(CollectionView: UICollectionView, heightForCaptionAt indexPath: IndexPath, with width: CGFloat) -> CGFloat
}



class FactsFeverLayout: UICollectionViewLayout {

var cellPadding : CGFloat = 5.0
var delegate: FactsFeverLayoutDelegate?

private var contentHeight : CGFloat = 0.0
private var contentWidth : CGFloat {
let insets = collectionView!.contentInset
return (collectionView!.bounds.width - insets.left + insets.right)
}
private var attributeCache = [FactsFeverLayoutAttributes]()
override func prepare() {
if attributeCache.isEmpty {
let containerWidth = contentWidth
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0

for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)

let width = containerWidth - cellPadding * 2
let photoHeight:CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForThePhotoAt: indexPath, with: width))!
let captionHeight: CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForCaptionAt: indexPath, with: width))!


let height: CGFloat = cellPadding + photoHeight + captionHeight + cellPadding

let frame = CGRect(x: xOffset, y: yOffset, width: containerWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

// Create CEll Layout Atributes
let attributes = FactsFeverLayoutAttributes(forCellWith: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
attributeCache.append(attributes)
// Update The Colunm any Y axis
contentHeight = max(contentHeight, frame.maxY)
yOffset = yOffset + height


}


}
}

override var collectionViewContentSize: CGSize{
return CGSize(width: contentWidth, height: contentHeight)
}

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()

for attributes in attributeCache {
if attributes.frame.intersects(rect){
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
}
// UICollectionView FlowLayout
// Abstract
class FactsFeverLayoutAttributes: UICollectionViewLayoutAttributes {
var photoHeight : CGFloat = 0.0
override func copy(with zone: NSZone? = nil) -> Any {
let copy = super.copy(with: zone) as! FactsFeverLayoutAttributes
copy.photoHeight = photoHeight
return copy
}

override func isEqual(_ object: Any?) -> Bool {
if let attributes = object as? FactsFeverLayoutAttributes {
if attributes.photoHeight == photoHeight {
super.isEqual(object)

}
}
return false
}
}


CollectionViewCell Class



class NewCellCollectionViewCell: UICollectionViewCell {

var facts: Facts!
var currentUser = Auth.auth().currentUser?.uid

// IBOutlets
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var imageHeightConstraint: NSLayoutConstraint!

@IBOutlet weak var likeLable: UILabel!
@IBOutlet weak var likeButton: UIButton!
@IBOutlet weak var infoButton: UIButton!
@IBOutlet weak var buttonView: UIView!
@IBOutlet weak var captionTextView: UITextView!

override func awakeFromNib() {
super.awakeFromNib()
likeButton.setImage(UIImage(named: "noLike"), for: .normal)
likeButton.setImage(UIImage(named: "like"), for: .selected)
setupLayout()
}


func configureCell(fact: Facts){
facts = fact

imageView.sd_setImage(with: URL(string: fact.factsLink))
likeLable.text = String(fact.factsLikes.count)
captionTextView.text = fact.captionText
let factsRef = Database.database().reference().child("Facts").child(facts.factsId).child("likes")
factsRef.observeSingleEvent(of: .value) { (snapshot) in
if fact.factsLikes.contains(self.currentUser!){
self.likeButton.isSelected = true
} else {
self.likeButton.isSelected = false
}


}
}


override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
if let attributes = layoutAttributes as? FactsFeverLayoutAttributes {
imageHeightConstraint.constant = attributes.photoHeight
}
}
}


ViewController Class



    class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//MARK: Outlets

@IBOutlet weak var uploadButtonOutlet: UIBarButtonItem!
@IBOutlet weak var collectionView: UICollectionView!

//MARK:- Properties

var images: [UIImage] =
var factsArray:[Facts] = [Facts]()
var likeUsers:[String] =
let currentUser = Auth.auth().currentUser?.uid



private let refreshControl = UIRefreshControl()

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if #available(iOS 10.0, *) {
collectionView.refreshControl = refreshControl
} else {
collectionView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(refreshView), for: .valueChanged)
refreshControl.tintColor = UIColor.white

if let layout = collectionView?.collectionViewLayout as? FactsFeverLayout {
layout.delegate = self
}
collectionView.backgroundColor = UIColor.black
observeFactsFromFirebase()
}


@objc func refreshView(){
observeFactsFromFirebase()
}






//MARK:- Upload Facts

@IBAction func uploadButtonPressed(_ sender: Any) {

self.selectPhoto()
(deleted the function of selectPhoto but it works, UIImagePicker is used)

}

private func uploadImageToFirebaseStorage(image: UIImage, completion: @escaping (_ imageUrl: String) -> ()){
let imageName = NSUUID().uuidString + ".jpg"
let ref = Storage.storage().reference().child("message_images").child(imageName)

if let uploadData = image.jpegData(compressionQuality: 0.2){
ref.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(" Failed to upload Image", error)
}
ref.downloadURL(completion: { (url, err) in
if let err = err {
print("Unable to upload image into storage due to (err)")
}
let messageImageURL = url?.absoluteString
completion(messageImageURL!)

})

})
}
}

func addToDatabase(imageUrl:String, caption: String, image: UIImage){
let Id = NSUUID().uuidString
likeUsers.append(currentUser!)
let timeStamp = NSNumber(value: Int(NSDate().timeIntervalSince1970))
let factsDB = Database.database().reference().child("Facts")
let factsDictionary = ["factsLink": imageUrl, "likes": likeUsers, "factsId": Id, "timeStamp": timeStamp, "captionText": caption, "imageWidth": image.size.width, "imageHeight": image.size.height] as [String : Any]
factsDB.child(Id).setValue(factsDictionary){
(error, reference) in

if error != nil {
print(error)
ProgressHUD.showError("Image Upload Failed")
self.uploadButtonOutlet.isEnabled = true
return

} else{
print("Message Saved In DB")
ProgressHUD.showSuccess("image Uploded Successfully")
self.uploadButtonOutlet.isEnabled = true

self.observeFactsFromFirebase()
}
}
}


var imageUrl: [String] =
func observeFactsFromFirebase(){

let factsDB = Database.database().reference().child("Facts").queryOrdered(byChild: "timeStamp")
factsDB.observe(.value){ (snapshot) in
print("Observer Data snapshot (snapshot.value)")

self.factsArray =
self.imageUrl =
self.likeUsers =

if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {

if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let id = snap.key
let facts = Facts(dictionary: postDictionary)
self.factsArray.insert(facts, at: 0)
self.imageUrl.insert(facts.factsLink, at: 0)

}
}
}
self.collectionView.reloadData()

self.refreshControl.endRefreshing()


}
collectionView.reloadData()
}
// Download Image From Database
//-> Here I download image from firebase and store it locally and append it to images array (Deleted the code to remove unwanted clutter)
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

//MARK: Data Source
extension ViewController: UICollectionViewDataSource{

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return factsArray.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let facts = factsArray[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "newCellTrial", for: indexPath) as? NewCellCollectionViewCell

cell?.configureCell(fact: facts)
cell?.infoButton.addTarget(self, action: #selector(reportButtonPressed), for: .touchUpInside)

return cell!
}
}
extension ViewController: UICollectionViewDelegate {

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
let photos = IDMPhoto.photos(withURLs: imageUrl)
let browser = IDMPhotoBrowser(photos: photos)
browser?.setInitialPageIndex(UInt(indexPath.row))
self.present(browser!, animated: true, completion: nil)
}
}
extension ViewController: FactsFeverLayoutDelegate {
func collectionView(CollectionView: UICollectionView, heightForThePhotoAt indexPath: IndexPath, with width: CGFloat) -> CGFloat {
let facts = factsArray[indexPath.item]
let imageSize = CGSize(width: CGFloat(facts.imageWidht), height: CGFloat(facts.imageHeight))
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let rect = AVMakeRect(aspectRatio: imageSize, insideRect: boundingRect)

return rect.size.height

}

func collectionView(CollectionView: UICollectionView, heightForCaptionAt indexPath: IndexPath, with width: CGFloat) -> CGFloat {
let fact = factsArray[indexPath.item]
let topPadding = CGFloat(8)
let bottomPadding = CGFloat(8)
let captionFont = UIFont.systemFont(ofSize: 15)
let viewHeight = CGFloat(40) //-> There is view below caption which holds like button and info button its height is constant (40)
let captionHeight = self.height(for: fact.captionText, with: captionFont, width: width)
let height = topPadding + captionHeight + topPadding + viewHeight + bottomPadding + topPadding + 10

return height

}

func height(for text: String, with font: UIFont, width: CGFloat) -> CGFloat {
let nsstring = NSString(string: text)
let maxHeight = CGFloat(1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let textAttributes = [NSAttributedString.Key.font: font]
let boundingRect = nsstring.boundingRect(with: CGSize(width: width, height: maxHeight), options: options, attributes: textAttributes, context: nil)

return ceil(boundingRect.height)
}


}









share|improve this question























  • Here the width of CollectionViewCell is constant and equal to the screen's width? Then I think you don't need a custom UICollectionViewLayout. Try using the default FlowLayout.

    – Damon
    Nov 25 '18 at 12:30













  • i think this is due to attributeCache.as first cell Attribute is Already Storead in Cache.When you append new post at Top it access old Attribute From Cache For indexpath(0,0).you should try Cache.removeAll() than CollectionView.invalidLayout.

    – Bhavesh.iosDev
    Nov 26 '18 at 9:42











  • Thank You for pointing me in the direction. I solved the issue by adding attributeCache.removeAll in prepare.

    – shree bhagwat
    Nov 26 '18 at 11:36














0












0








0








Description:
I have been working on an app which displays images and caption in a CollectionView. I have made a custom Cell and CustomFlowLayout for the cell where the width of the cell, image and caption is equal to width of screen and height will change according to aspect ratio of the image height and height needed for the caption. The images which are displayed are stored on FirebaseStorage and I have also saved imagewidth and imageheight in firebaseDatabase. I Use SD_Web images to load and cache images. When the app loads for the first time the layout works perfectly fine as expected. The cells are arranged according to image height and caption height , with images being in aspect ratio.
The main problem occurs when a new post is done. I insert new post on the top of collectionview, and when the post is received the layout breaks, images becomes messed up, the caption goes on top image, lot of white space between image or caption. when I terminate the app from background and run it again , this time the layout is perfectly fine with new post present. I have to terminate app after everyone post to make the layout work.



What I feel the problem is, when I post the image. The new post is added on the top cell and the item on top cell pushed below without having the old height. how can I take this problem. I tried searching a lot but still of no use.
PS:
Using Autolayout for cell in IB.



FactsFeverLayout Class



import UIKit
protocol FactsFeverLayoutDelegate: class {
func collectionView(CollectionView: UICollectionView, heightForThePhotoAt indexPath: IndexPath, with width: CGFloat) -> CGFloat

func collectionView(CollectionView: UICollectionView, heightForCaptionAt indexPath: IndexPath, with width: CGFloat) -> CGFloat
}



class FactsFeverLayout: UICollectionViewLayout {

var cellPadding : CGFloat = 5.0
var delegate: FactsFeverLayoutDelegate?

private var contentHeight : CGFloat = 0.0
private var contentWidth : CGFloat {
let insets = collectionView!.contentInset
return (collectionView!.bounds.width - insets.left + insets.right)
}
private var attributeCache = [FactsFeverLayoutAttributes]()
override func prepare() {
if attributeCache.isEmpty {
let containerWidth = contentWidth
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0

for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)

let width = containerWidth - cellPadding * 2
let photoHeight:CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForThePhotoAt: indexPath, with: width))!
let captionHeight: CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForCaptionAt: indexPath, with: width))!


let height: CGFloat = cellPadding + photoHeight + captionHeight + cellPadding

let frame = CGRect(x: xOffset, y: yOffset, width: containerWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

// Create CEll Layout Atributes
let attributes = FactsFeverLayoutAttributes(forCellWith: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
attributeCache.append(attributes)
// Update The Colunm any Y axis
contentHeight = max(contentHeight, frame.maxY)
yOffset = yOffset + height


}


}
}

override var collectionViewContentSize: CGSize{
return CGSize(width: contentWidth, height: contentHeight)
}

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()

for attributes in attributeCache {
if attributes.frame.intersects(rect){
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
}
// UICollectionView FlowLayout
// Abstract
class FactsFeverLayoutAttributes: UICollectionViewLayoutAttributes {
var photoHeight : CGFloat = 0.0
override func copy(with zone: NSZone? = nil) -> Any {
let copy = super.copy(with: zone) as! FactsFeverLayoutAttributes
copy.photoHeight = photoHeight
return copy
}

override func isEqual(_ object: Any?) -> Bool {
if let attributes = object as? FactsFeverLayoutAttributes {
if attributes.photoHeight == photoHeight {
super.isEqual(object)

}
}
return false
}
}


CollectionViewCell Class



class NewCellCollectionViewCell: UICollectionViewCell {

var facts: Facts!
var currentUser = Auth.auth().currentUser?.uid

// IBOutlets
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var imageHeightConstraint: NSLayoutConstraint!

@IBOutlet weak var likeLable: UILabel!
@IBOutlet weak var likeButton: UIButton!
@IBOutlet weak var infoButton: UIButton!
@IBOutlet weak var buttonView: UIView!
@IBOutlet weak var captionTextView: UITextView!

override func awakeFromNib() {
super.awakeFromNib()
likeButton.setImage(UIImage(named: "noLike"), for: .normal)
likeButton.setImage(UIImage(named: "like"), for: .selected)
setupLayout()
}


func configureCell(fact: Facts){
facts = fact

imageView.sd_setImage(with: URL(string: fact.factsLink))
likeLable.text = String(fact.factsLikes.count)
captionTextView.text = fact.captionText
let factsRef = Database.database().reference().child("Facts").child(facts.factsId).child("likes")
factsRef.observeSingleEvent(of: .value) { (snapshot) in
if fact.factsLikes.contains(self.currentUser!){
self.likeButton.isSelected = true
} else {
self.likeButton.isSelected = false
}


}
}


override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
if let attributes = layoutAttributes as? FactsFeverLayoutAttributes {
imageHeightConstraint.constant = attributes.photoHeight
}
}
}


ViewController Class



    class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//MARK: Outlets

@IBOutlet weak var uploadButtonOutlet: UIBarButtonItem!
@IBOutlet weak var collectionView: UICollectionView!

//MARK:- Properties

var images: [UIImage] =
var factsArray:[Facts] = [Facts]()
var likeUsers:[String] =
let currentUser = Auth.auth().currentUser?.uid



private let refreshControl = UIRefreshControl()

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if #available(iOS 10.0, *) {
collectionView.refreshControl = refreshControl
} else {
collectionView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(refreshView), for: .valueChanged)
refreshControl.tintColor = UIColor.white

if let layout = collectionView?.collectionViewLayout as? FactsFeverLayout {
layout.delegate = self
}
collectionView.backgroundColor = UIColor.black
observeFactsFromFirebase()
}


@objc func refreshView(){
observeFactsFromFirebase()
}






//MARK:- Upload Facts

@IBAction func uploadButtonPressed(_ sender: Any) {

self.selectPhoto()
(deleted the function of selectPhoto but it works, UIImagePicker is used)

}

private func uploadImageToFirebaseStorage(image: UIImage, completion: @escaping (_ imageUrl: String) -> ()){
let imageName = NSUUID().uuidString + ".jpg"
let ref = Storage.storage().reference().child("message_images").child(imageName)

if let uploadData = image.jpegData(compressionQuality: 0.2){
ref.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(" Failed to upload Image", error)
}
ref.downloadURL(completion: { (url, err) in
if let err = err {
print("Unable to upload image into storage due to (err)")
}
let messageImageURL = url?.absoluteString
completion(messageImageURL!)

})

})
}
}

func addToDatabase(imageUrl:String, caption: String, image: UIImage){
let Id = NSUUID().uuidString
likeUsers.append(currentUser!)
let timeStamp = NSNumber(value: Int(NSDate().timeIntervalSince1970))
let factsDB = Database.database().reference().child("Facts")
let factsDictionary = ["factsLink": imageUrl, "likes": likeUsers, "factsId": Id, "timeStamp": timeStamp, "captionText": caption, "imageWidth": image.size.width, "imageHeight": image.size.height] as [String : Any]
factsDB.child(Id).setValue(factsDictionary){
(error, reference) in

if error != nil {
print(error)
ProgressHUD.showError("Image Upload Failed")
self.uploadButtonOutlet.isEnabled = true
return

} else{
print("Message Saved In DB")
ProgressHUD.showSuccess("image Uploded Successfully")
self.uploadButtonOutlet.isEnabled = true

self.observeFactsFromFirebase()
}
}
}


var imageUrl: [String] =
func observeFactsFromFirebase(){

let factsDB = Database.database().reference().child("Facts").queryOrdered(byChild: "timeStamp")
factsDB.observe(.value){ (snapshot) in
print("Observer Data snapshot (snapshot.value)")

self.factsArray =
self.imageUrl =
self.likeUsers =

if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {

if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let id = snap.key
let facts = Facts(dictionary: postDictionary)
self.factsArray.insert(facts, at: 0)
self.imageUrl.insert(facts.factsLink, at: 0)

}
}
}
self.collectionView.reloadData()

self.refreshControl.endRefreshing()


}
collectionView.reloadData()
}
// Download Image From Database
//-> Here I download image from firebase and store it locally and append it to images array (Deleted the code to remove unwanted clutter)
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

//MARK: Data Source
extension ViewController: UICollectionViewDataSource{

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return factsArray.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let facts = factsArray[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "newCellTrial", for: indexPath) as? NewCellCollectionViewCell

cell?.configureCell(fact: facts)
cell?.infoButton.addTarget(self, action: #selector(reportButtonPressed), for: .touchUpInside)

return cell!
}
}
extension ViewController: UICollectionViewDelegate {

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
let photos = IDMPhoto.photos(withURLs: imageUrl)
let browser = IDMPhotoBrowser(photos: photos)
browser?.setInitialPageIndex(UInt(indexPath.row))
self.present(browser!, animated: true, completion: nil)
}
}
extension ViewController: FactsFeverLayoutDelegate {
func collectionView(CollectionView: UICollectionView, heightForThePhotoAt indexPath: IndexPath, with width: CGFloat) -> CGFloat {
let facts = factsArray[indexPath.item]
let imageSize = CGSize(width: CGFloat(facts.imageWidht), height: CGFloat(facts.imageHeight))
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let rect = AVMakeRect(aspectRatio: imageSize, insideRect: boundingRect)

return rect.size.height

}

func collectionView(CollectionView: UICollectionView, heightForCaptionAt indexPath: IndexPath, with width: CGFloat) -> CGFloat {
let fact = factsArray[indexPath.item]
let topPadding = CGFloat(8)
let bottomPadding = CGFloat(8)
let captionFont = UIFont.systemFont(ofSize: 15)
let viewHeight = CGFloat(40) //-> There is view below caption which holds like button and info button its height is constant (40)
let captionHeight = self.height(for: fact.captionText, with: captionFont, width: width)
let height = topPadding + captionHeight + topPadding + viewHeight + bottomPadding + topPadding + 10

return height

}

func height(for text: String, with font: UIFont, width: CGFloat) -> CGFloat {
let nsstring = NSString(string: text)
let maxHeight = CGFloat(1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let textAttributes = [NSAttributedString.Key.font: font]
let boundingRect = nsstring.boundingRect(with: CGSize(width: width, height: maxHeight), options: options, attributes: textAttributes, context: nil)

return ceil(boundingRect.height)
}


}









share|improve this question














Description:
I have been working on an app which displays images and caption in a CollectionView. I have made a custom Cell and CustomFlowLayout for the cell where the width of the cell, image and caption is equal to width of screen and height will change according to aspect ratio of the image height and height needed for the caption. The images which are displayed are stored on FirebaseStorage and I have also saved imagewidth and imageheight in firebaseDatabase. I Use SD_Web images to load and cache images. When the app loads for the first time the layout works perfectly fine as expected. The cells are arranged according to image height and caption height , with images being in aspect ratio.
The main problem occurs when a new post is done. I insert new post on the top of collectionview, and when the post is received the layout breaks, images becomes messed up, the caption goes on top image, lot of white space between image or caption. when I terminate the app from background and run it again , this time the layout is perfectly fine with new post present. I have to terminate app after everyone post to make the layout work.



What I feel the problem is, when I post the image. The new post is added on the top cell and the item on top cell pushed below without having the old height. how can I take this problem. I tried searching a lot but still of no use.
PS:
Using Autolayout for cell in IB.



FactsFeverLayout Class



import UIKit
protocol FactsFeverLayoutDelegate: class {
func collectionView(CollectionView: UICollectionView, heightForThePhotoAt indexPath: IndexPath, with width: CGFloat) -> CGFloat

func collectionView(CollectionView: UICollectionView, heightForCaptionAt indexPath: IndexPath, with width: CGFloat) -> CGFloat
}



class FactsFeverLayout: UICollectionViewLayout {

var cellPadding : CGFloat = 5.0
var delegate: FactsFeverLayoutDelegate?

private var contentHeight : CGFloat = 0.0
private var contentWidth : CGFloat {
let insets = collectionView!.contentInset
return (collectionView!.bounds.width - insets.left + insets.right)
}
private var attributeCache = [FactsFeverLayoutAttributes]()
override func prepare() {
if attributeCache.isEmpty {
let containerWidth = contentWidth
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0

for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)

let width = containerWidth - cellPadding * 2
let photoHeight:CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForThePhotoAt: indexPath, with: width))!
let captionHeight: CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForCaptionAt: indexPath, with: width))!


let height: CGFloat = cellPadding + photoHeight + captionHeight + cellPadding

let frame = CGRect(x: xOffset, y: yOffset, width: containerWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

// Create CEll Layout Atributes
let attributes = FactsFeverLayoutAttributes(forCellWith: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
attributeCache.append(attributes)
// Update The Colunm any Y axis
contentHeight = max(contentHeight, frame.maxY)
yOffset = yOffset + height


}


}
}

override var collectionViewContentSize: CGSize{
return CGSize(width: contentWidth, height: contentHeight)
}

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()

for attributes in attributeCache {
if attributes.frame.intersects(rect){
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
}
// UICollectionView FlowLayout
// Abstract
class FactsFeverLayoutAttributes: UICollectionViewLayoutAttributes {
var photoHeight : CGFloat = 0.0
override func copy(with zone: NSZone? = nil) -> Any {
let copy = super.copy(with: zone) as! FactsFeverLayoutAttributes
copy.photoHeight = photoHeight
return copy
}

override func isEqual(_ object: Any?) -> Bool {
if let attributes = object as? FactsFeverLayoutAttributes {
if attributes.photoHeight == photoHeight {
super.isEqual(object)

}
}
return false
}
}


CollectionViewCell Class



class NewCellCollectionViewCell: UICollectionViewCell {

var facts: Facts!
var currentUser = Auth.auth().currentUser?.uid

// IBOutlets
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var imageHeightConstraint: NSLayoutConstraint!

@IBOutlet weak var likeLable: UILabel!
@IBOutlet weak var likeButton: UIButton!
@IBOutlet weak var infoButton: UIButton!
@IBOutlet weak var buttonView: UIView!
@IBOutlet weak var captionTextView: UITextView!

override func awakeFromNib() {
super.awakeFromNib()
likeButton.setImage(UIImage(named: "noLike"), for: .normal)
likeButton.setImage(UIImage(named: "like"), for: .selected)
setupLayout()
}


func configureCell(fact: Facts){
facts = fact

imageView.sd_setImage(with: URL(string: fact.factsLink))
likeLable.text = String(fact.factsLikes.count)
captionTextView.text = fact.captionText
let factsRef = Database.database().reference().child("Facts").child(facts.factsId).child("likes")
factsRef.observeSingleEvent(of: .value) { (snapshot) in
if fact.factsLikes.contains(self.currentUser!){
self.likeButton.isSelected = true
} else {
self.likeButton.isSelected = false
}


}
}


override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
if let attributes = layoutAttributes as? FactsFeverLayoutAttributes {
imageHeightConstraint.constant = attributes.photoHeight
}
}
}


ViewController Class



    class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//MARK: Outlets

@IBOutlet weak var uploadButtonOutlet: UIBarButtonItem!
@IBOutlet weak var collectionView: UICollectionView!

//MARK:- Properties

var images: [UIImage] =
var factsArray:[Facts] = [Facts]()
var likeUsers:[String] =
let currentUser = Auth.auth().currentUser?.uid



private let refreshControl = UIRefreshControl()

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if #available(iOS 10.0, *) {
collectionView.refreshControl = refreshControl
} else {
collectionView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(refreshView), for: .valueChanged)
refreshControl.tintColor = UIColor.white

if let layout = collectionView?.collectionViewLayout as? FactsFeverLayout {
layout.delegate = self
}
collectionView.backgroundColor = UIColor.black
observeFactsFromFirebase()
}


@objc func refreshView(){
observeFactsFromFirebase()
}






//MARK:- Upload Facts

@IBAction func uploadButtonPressed(_ sender: Any) {

self.selectPhoto()
(deleted the function of selectPhoto but it works, UIImagePicker is used)

}

private func uploadImageToFirebaseStorage(image: UIImage, completion: @escaping (_ imageUrl: String) -> ()){
let imageName = NSUUID().uuidString + ".jpg"
let ref = Storage.storage().reference().child("message_images").child(imageName)

if let uploadData = image.jpegData(compressionQuality: 0.2){
ref.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(" Failed to upload Image", error)
}
ref.downloadURL(completion: { (url, err) in
if let err = err {
print("Unable to upload image into storage due to (err)")
}
let messageImageURL = url?.absoluteString
completion(messageImageURL!)

})

})
}
}

func addToDatabase(imageUrl:String, caption: String, image: UIImage){
let Id = NSUUID().uuidString
likeUsers.append(currentUser!)
let timeStamp = NSNumber(value: Int(NSDate().timeIntervalSince1970))
let factsDB = Database.database().reference().child("Facts")
let factsDictionary = ["factsLink": imageUrl, "likes": likeUsers, "factsId": Id, "timeStamp": timeStamp, "captionText": caption, "imageWidth": image.size.width, "imageHeight": image.size.height] as [String : Any]
factsDB.child(Id).setValue(factsDictionary){
(error, reference) in

if error != nil {
print(error)
ProgressHUD.showError("Image Upload Failed")
self.uploadButtonOutlet.isEnabled = true
return

} else{
print("Message Saved In DB")
ProgressHUD.showSuccess("image Uploded Successfully")
self.uploadButtonOutlet.isEnabled = true

self.observeFactsFromFirebase()
}
}
}


var imageUrl: [String] =
func observeFactsFromFirebase(){

let factsDB = Database.database().reference().child("Facts").queryOrdered(byChild: "timeStamp")
factsDB.observe(.value){ (snapshot) in
print("Observer Data snapshot (snapshot.value)")

self.factsArray =
self.imageUrl =
self.likeUsers =

if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {

if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let id = snap.key
let facts = Facts(dictionary: postDictionary)
self.factsArray.insert(facts, at: 0)
self.imageUrl.insert(facts.factsLink, at: 0)

}
}
}
self.collectionView.reloadData()

self.refreshControl.endRefreshing()


}
collectionView.reloadData()
}
// Download Image From Database
//-> Here I download image from firebase and store it locally and append it to images array (Deleted the code to remove unwanted clutter)
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

//MARK: Data Source
extension ViewController: UICollectionViewDataSource{

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return factsArray.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let facts = factsArray[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "newCellTrial", for: indexPath) as? NewCellCollectionViewCell

cell?.configureCell(fact: facts)
cell?.infoButton.addTarget(self, action: #selector(reportButtonPressed), for: .touchUpInside)

return cell!
}
}
extension ViewController: UICollectionViewDelegate {

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
let photos = IDMPhoto.photos(withURLs: imageUrl)
let browser = IDMPhotoBrowser(photos: photos)
browser?.setInitialPageIndex(UInt(indexPath.row))
self.present(browser!, animated: true, completion: nil)
}
}
extension ViewController: FactsFeverLayoutDelegate {
func collectionView(CollectionView: UICollectionView, heightForThePhotoAt indexPath: IndexPath, with width: CGFloat) -> CGFloat {
let facts = factsArray[indexPath.item]
let imageSize = CGSize(width: CGFloat(facts.imageWidht), height: CGFloat(facts.imageHeight))
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let rect = AVMakeRect(aspectRatio: imageSize, insideRect: boundingRect)

return rect.size.height

}

func collectionView(CollectionView: UICollectionView, heightForCaptionAt indexPath: IndexPath, with width: CGFloat) -> CGFloat {
let fact = factsArray[indexPath.item]
let topPadding = CGFloat(8)
let bottomPadding = CGFloat(8)
let captionFont = UIFont.systemFont(ofSize: 15)
let viewHeight = CGFloat(40) //-> There is view below caption which holds like button and info button its height is constant (40)
let captionHeight = self.height(for: fact.captionText, with: captionFont, width: width)
let height = topPadding + captionHeight + topPadding + viewHeight + bottomPadding + topPadding + 10

return height

}

func height(for text: String, with font: UIFont, width: CGFloat) -> CGFloat {
let nsstring = NSString(string: text)
let maxHeight = CGFloat(1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let textAttributes = [NSAttributedString.Key.font: font]
let boundingRect = nsstring.boundingRect(with: CGSize(width: width, height: maxHeight), options: options, attributes: textAttributes, context: nil)

return ceil(boundingRect.height)
}


}






ios swift autolayout uicollectionviewflowlayout






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 25 '18 at 12:14









shree bhagwatshree bhagwat

56




56













  • Here the width of CollectionViewCell is constant and equal to the screen's width? Then I think you don't need a custom UICollectionViewLayout. Try using the default FlowLayout.

    – Damon
    Nov 25 '18 at 12:30













  • i think this is due to attributeCache.as first cell Attribute is Already Storead in Cache.When you append new post at Top it access old Attribute From Cache For indexpath(0,0).you should try Cache.removeAll() than CollectionView.invalidLayout.

    – Bhavesh.iosDev
    Nov 26 '18 at 9:42











  • Thank You for pointing me in the direction. I solved the issue by adding attributeCache.removeAll in prepare.

    – shree bhagwat
    Nov 26 '18 at 11:36



















  • Here the width of CollectionViewCell is constant and equal to the screen's width? Then I think you don't need a custom UICollectionViewLayout. Try using the default FlowLayout.

    – Damon
    Nov 25 '18 at 12:30













  • i think this is due to attributeCache.as first cell Attribute is Already Storead in Cache.When you append new post at Top it access old Attribute From Cache For indexpath(0,0).you should try Cache.removeAll() than CollectionView.invalidLayout.

    – Bhavesh.iosDev
    Nov 26 '18 at 9:42











  • Thank You for pointing me in the direction. I solved the issue by adding attributeCache.removeAll in prepare.

    – shree bhagwat
    Nov 26 '18 at 11:36

















Here the width of CollectionViewCell is constant and equal to the screen's width? Then I think you don't need a custom UICollectionViewLayout. Try using the default FlowLayout.

– Damon
Nov 25 '18 at 12:30







Here the width of CollectionViewCell is constant and equal to the screen's width? Then I think you don't need a custom UICollectionViewLayout. Try using the default FlowLayout.

– Damon
Nov 25 '18 at 12:30















i think this is due to attributeCache.as first cell Attribute is Already Storead in Cache.When you append new post at Top it access old Attribute From Cache For indexpath(0,0).you should try Cache.removeAll() than CollectionView.invalidLayout.

– Bhavesh.iosDev
Nov 26 '18 at 9:42





i think this is due to attributeCache.as first cell Attribute is Already Storead in Cache.When you append new post at Top it access old Attribute From Cache For indexpath(0,0).you should try Cache.removeAll() than CollectionView.invalidLayout.

– Bhavesh.iosDev
Nov 26 '18 at 9:42













Thank You for pointing me in the direction. I solved the issue by adding attributeCache.removeAll in prepare.

– shree bhagwat
Nov 26 '18 at 11:36





Thank You for pointing me in the direction. I solved the issue by adding attributeCache.removeAll in prepare.

– shree bhagwat
Nov 26 '18 at 11:36












1 Answer
1






active

oldest

votes


















1














as you have used attributeCache, layoutAttribute of First Cell is Already Stored in cache.when you add images at first place and reload your collectionView , old Attribute for First cell will be retired from Cache and applying to your collectionView layout.



so you have to remove Element From Cache before reloading



override func prepare() {

attributeCache.RemoveAll()
if attributeCache.isEmpty {
let containerWidth = contentWidth
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0

for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)

let width = containerWidth - cellPadding * 2
let photoHeight:CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForThePhotoAt: indexPath, with: width))!
let captionHeight: CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForCaptionAt: indexPath, with: width))!


let height: CGFloat = cellPadding + photoHeight + captionHeight + cellPadding

let frame = CGRect(x: xOffset, y: yOffset, width: containerWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

// Create CEll Layout Atributes
let attributes = FactsFeverLayoutAttributes(forCellWith: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
attributeCache.append(attributes)
// Update The Colunm any Y axis
contentHeight = max(contentHeight, frame.maxY)
yOffset = yOffset + height


}


}
}





share|improve this answer


























  • can you tell what's the difference between using attributed Cache in prepare and in the above method, as when I use attributed cache in prepare I get the result I want. but when I use it in the way you described, the layout still breaks

    – shree bhagwat
    Nov 29 '18 at 8:50













  • sorry it was just misconception about prepare method yes you can Clear Cache on Prepare.i Have updated Answer

    – Bhavesh.iosDev
    Nov 29 '18 at 9:22











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53467323%2fuicollectionview-cell-custom-flowlayout-breaks-when-new-post-is-added-on-top%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









1














as you have used attributeCache, layoutAttribute of First Cell is Already Stored in cache.when you add images at first place and reload your collectionView , old Attribute for First cell will be retired from Cache and applying to your collectionView layout.



so you have to remove Element From Cache before reloading



override func prepare() {

attributeCache.RemoveAll()
if attributeCache.isEmpty {
let containerWidth = contentWidth
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0

for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)

let width = containerWidth - cellPadding * 2
let photoHeight:CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForThePhotoAt: indexPath, with: width))!
let captionHeight: CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForCaptionAt: indexPath, with: width))!


let height: CGFloat = cellPadding + photoHeight + captionHeight + cellPadding

let frame = CGRect(x: xOffset, y: yOffset, width: containerWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

// Create CEll Layout Atributes
let attributes = FactsFeverLayoutAttributes(forCellWith: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
attributeCache.append(attributes)
// Update The Colunm any Y axis
contentHeight = max(contentHeight, frame.maxY)
yOffset = yOffset + height


}


}
}





share|improve this answer


























  • can you tell what's the difference between using attributed Cache in prepare and in the above method, as when I use attributed cache in prepare I get the result I want. but when I use it in the way you described, the layout still breaks

    – shree bhagwat
    Nov 29 '18 at 8:50













  • sorry it was just misconception about prepare method yes you can Clear Cache on Prepare.i Have updated Answer

    – Bhavesh.iosDev
    Nov 29 '18 at 9:22
















1














as you have used attributeCache, layoutAttribute of First Cell is Already Stored in cache.when you add images at first place and reload your collectionView , old Attribute for First cell will be retired from Cache and applying to your collectionView layout.



so you have to remove Element From Cache before reloading



override func prepare() {

attributeCache.RemoveAll()
if attributeCache.isEmpty {
let containerWidth = contentWidth
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0

for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)

let width = containerWidth - cellPadding * 2
let photoHeight:CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForThePhotoAt: indexPath, with: width))!
let captionHeight: CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForCaptionAt: indexPath, with: width))!


let height: CGFloat = cellPadding + photoHeight + captionHeight + cellPadding

let frame = CGRect(x: xOffset, y: yOffset, width: containerWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

// Create CEll Layout Atributes
let attributes = FactsFeverLayoutAttributes(forCellWith: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
attributeCache.append(attributes)
// Update The Colunm any Y axis
contentHeight = max(contentHeight, frame.maxY)
yOffset = yOffset + height


}


}
}





share|improve this answer


























  • can you tell what's the difference between using attributed Cache in prepare and in the above method, as when I use attributed cache in prepare I get the result I want. but when I use it in the way you described, the layout still breaks

    – shree bhagwat
    Nov 29 '18 at 8:50













  • sorry it was just misconception about prepare method yes you can Clear Cache on Prepare.i Have updated Answer

    – Bhavesh.iosDev
    Nov 29 '18 at 9:22














1












1








1







as you have used attributeCache, layoutAttribute of First Cell is Already Stored in cache.when you add images at first place and reload your collectionView , old Attribute for First cell will be retired from Cache and applying to your collectionView layout.



so you have to remove Element From Cache before reloading



override func prepare() {

attributeCache.RemoveAll()
if attributeCache.isEmpty {
let containerWidth = contentWidth
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0

for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)

let width = containerWidth - cellPadding * 2
let photoHeight:CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForThePhotoAt: indexPath, with: width))!
let captionHeight: CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForCaptionAt: indexPath, with: width))!


let height: CGFloat = cellPadding + photoHeight + captionHeight + cellPadding

let frame = CGRect(x: xOffset, y: yOffset, width: containerWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

// Create CEll Layout Atributes
let attributes = FactsFeverLayoutAttributes(forCellWith: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
attributeCache.append(attributes)
// Update The Colunm any Y axis
contentHeight = max(contentHeight, frame.maxY)
yOffset = yOffset + height


}


}
}





share|improve this answer















as you have used attributeCache, layoutAttribute of First Cell is Already Stored in cache.when you add images at first place and reload your collectionView , old Attribute for First cell will be retired from Cache and applying to your collectionView layout.



so you have to remove Element From Cache before reloading



override func prepare() {

attributeCache.RemoveAll()
if attributeCache.isEmpty {
let containerWidth = contentWidth
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0

for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)

let width = containerWidth - cellPadding * 2
let photoHeight:CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForThePhotoAt: indexPath, with: width))!
let captionHeight: CGFloat = (delegate?.collectionView(CollectionView: collectionView!, heightForCaptionAt: indexPath, with: width))!


let height: CGFloat = cellPadding + photoHeight + captionHeight + cellPadding

let frame = CGRect(x: xOffset, y: yOffset, width: containerWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

// Create CEll Layout Atributes
let attributes = FactsFeverLayoutAttributes(forCellWith: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
attributeCache.append(attributes)
// Update The Colunm any Y axis
contentHeight = max(contentHeight, frame.maxY)
yOffset = yOffset + height


}


}
}






share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 29 '18 at 9:22

























answered Nov 27 '18 at 5:20









Bhavesh.iosDevBhavesh.iosDev

602115




602115













  • can you tell what's the difference between using attributed Cache in prepare and in the above method, as when I use attributed cache in prepare I get the result I want. but when I use it in the way you described, the layout still breaks

    – shree bhagwat
    Nov 29 '18 at 8:50













  • sorry it was just misconception about prepare method yes you can Clear Cache on Prepare.i Have updated Answer

    – Bhavesh.iosDev
    Nov 29 '18 at 9:22



















  • can you tell what's the difference between using attributed Cache in prepare and in the above method, as when I use attributed cache in prepare I get the result I want. but when I use it in the way you described, the layout still breaks

    – shree bhagwat
    Nov 29 '18 at 8:50













  • sorry it was just misconception about prepare method yes you can Clear Cache on Prepare.i Have updated Answer

    – Bhavesh.iosDev
    Nov 29 '18 at 9:22

















can you tell what's the difference between using attributed Cache in prepare and in the above method, as when I use attributed cache in prepare I get the result I want. but when I use it in the way you described, the layout still breaks

– shree bhagwat
Nov 29 '18 at 8:50







can you tell what's the difference between using attributed Cache in prepare and in the above method, as when I use attributed cache in prepare I get the result I want. but when I use it in the way you described, the layout still breaks

– shree bhagwat
Nov 29 '18 at 8:50















sorry it was just misconception about prepare method yes you can Clear Cache on Prepare.i Have updated Answer

– Bhavesh.iosDev
Nov 29 '18 at 9:22





sorry it was just misconception about prepare method yes you can Clear Cache on Prepare.i Have updated Answer

– Bhavesh.iosDev
Nov 29 '18 at 9:22




















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53467323%2fuicollectionview-cell-custom-flowlayout-breaks-when-new-post-is-added-on-top%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Costa Masnaga

Fotorealismo

Sidney Franklin