Keep size of a cell - uicollectionview with custom layout - swift

I want to reorder my cells in my "Waterfall" collectionview. But when I move my cell, the size changes. To make my layout, I used this tutorial https://www.raywenderlich.com/164608/uicollectionview-custom-layout-tutorial-pinterest-2
How can I fix the size of my item when this position changes ?
My ViewLayout:
import Foundation
import UIKit
protocol WidgetCollectionViewLayoutDelegate: class {
func collectionView(_ collectionView: UICollectionView, heightForPhotoAt indexPath:IndexPath) -> CGFloat
}
class WidgetCollectionViewLayout: UICollectionViewLayout {
weak var delegate: WidgetCollectionViewLayoutDelegate!
private let numberOfColumns: Int = 2
private let cellPadding: CGFloat = 6
private var cache = [UICollectionViewLayoutAttributes]()
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
print("prepare")
super.prepare()
guard cache.isEmpty == true, let collectionView = collectionView else {
return
}
calculContent()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
private func calculContent() {
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
for item in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
let photoHeight = delegate.collectionView(collectionView!, heightForPhotoAt: indexPath)
let height = cellPadding * 2 + photoHeight
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
}
Thank you in advance for your help

Related

how set UIView and Image size after selecting size of image and view as paper size in inches

Istrong text am making passport app in which i am getting image size for example 2x2 inches and than paper size for example 8x11 inches(A4-size). than how can i show all images in one view with those sized. i resize the image , set collection view in UIView and give count by dividing paper_size/image_size. but in some case paper_size become more bigger than device with or height. also i want to stick the collection view (non-scrollable) so that i can pass the screenshot for printing
class GridLayout: UICollectionViewLayout {
var itemSpacing: CGFloat = 5
var rowSpacing: CGFloat = 5
private var itemSize: CGSize!
private var numberOfRows: Int!
private var numberOfColumns: Int!
override func prepare() {
super.prepare()
let count = collectionView!.numberOfItems(inSection: 0)
numberOfColumns = Int(ceil(sqrt(Double(count))))
numberOfRows = Int(ceil(Double(count) / Double(numberOfColumns)))
let width = (collectionView!.bounds.width - (itemSpacing * CGFloat(numberOfColumns - 1))) / CGFloat(numberOfColumns)
let height = (collectionView!.bounds.height - (rowSpacing * CGFloat(numberOfRows - 1))) / CGFloat(numberOfRows)
itemSize = CGSize(width: width, height: height)
}
override var collectionViewContentSize: CGSize {
return collectionView!.bounds.size
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.center = centerForItem(at: indexPath)
attributes.size = itemSize
return attributes
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return (0 ..< collectionView!.numberOfItems(inSection: 0)).map { IndexPath(item: $0, section: 0) }
.flatMap { layoutAttributesForItem(at: $0) }
}
private func centerForItem(at indexPath: IndexPath) -> CGPoint {
let row = indexPath.item / numberOfColumns
let col = indexPath.item - row * numberOfColumns
return CGPoint(x: CGFloat(col) * (itemSize.width + itemSpacing) + itemSize.width / 2,
y: CGFloat(row) * (itemSize.height + rowSpacing) + itemSize.height / 2)
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}

My pictures wont show when I build and run uicollection view

import UIKit
class HomePageCell: UICollectionViewCell {
#IBOutlet private weak var containerView: UIView!
#IBOutlet weak var photos: UIImageView!
#IBOutlet weak var songname: UILabel!
#IBOutlet weak var songmaker: UILabel!
//#IBOutlet weak var pfppic: UIImageView!
#IBOutlet weak var viewcount: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
containerView.layer.cornerRadius = 6
containerView.layer.masksToBounds = true
}
var photo: Photo? {
didSet {
if let photo = photo {
photos.image = photo.image
songname.text = photo.name
songmaker.text = photo.makername
viewcount.text = photo.likecount
}
}
}
override func prepareForReuse() {
super.prepareForReuse()
self.photos.image = nil
}
}
import UIKit
protocol PinterestLayoutDelegate: AnyObject {
func collectionView(
_ collectionView: UICollectionView,
heightForPhotoAtIndexPath indexPath: IndexPath) -> CGFloat
}
class PinterestLayout: UICollectionViewLayout {
// 1
weak var delegate: PinterestLayoutDelegate?
// 2
private let numberOfColumns = 2
private let cellPadding: CGFloat = 6
// 3
private var cache: [UICollectionViewLayoutAttributes] = []
// 4
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
// 5
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
// 1
guard
cache.isEmpty,
let collectionView = collectionView
else {
return
}
// 2
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset: [CGFloat] = []
for column in 0..<numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset: [CGFloat] = .init(repeating: 0, count: numberOfColumns)
// 3
for item in 0..<collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
// 4
let photoHeight = delegate?.collectionView(
collectionView,
heightForPhotoAtIndexPath: indexPath) ?? 180
let height = cellPadding * 2 + photoHeight
let frame = CGRect(x: xOffset[column],
y: yOffset[column],
width: columnWidth,
height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
// 5
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
// 6
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
override func layoutAttributesForElements(in rect: CGRect)
-> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []
// Loop through the cache and look for items in the rect
for attributes in cache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath)
-> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
}
extension HomePageViewController: PinterestLayoutDelegate {
func collectionView(
_ collectionView: UICollectionView,
heightForPhotoAtIndexPath indexPath:IndexPath) -> CGFloat {
return photos[indexPath.item].image.size.height
}
}
import UIKit
struct Photo {
var image: UIImage
var name: String
var makername: String
var likecount: String
init(image: UIImage, name: String, makername: String, likecount: String) {
self.image = image
self.name = name
self.makername = makername
self.likecount = likecount
}
init?(dictionary: [String: String]) {
guard
let name = dictionary["Name"],
let makername = dictionary["Makername"],
let photo = dictionary["Photo"],
let likecount = dictionary["Likecount"],
let image = UIImage(named: photo)
else {
return nil
}
self.init(image: image, name: name, makername: makername, likecount: likecount)
}
static func allPhotos() -> [Photo] {
var photos: [Photo] = []
guard
let URL = Bundle.main.url(forResource: "Photos", withExtension: "plist"),
let photosFromPlist = NSArray(contentsOf: URL) as? [[String:String]]
else {
return photos
}
for dictionary in photosFromPlist {
if let photo = Photo(dictionary: dictionary) {
photos.append(photo)
}
}
return photos
}
}
import UIKit
final class HomePageViewController: UICollectionViewController {
// MARK: - Properties
var photos = Photo.allPhotos()
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
if let layout = collectionView?.collectionViewLayout as? PinterestLayout {
layout.delegate = self
}
self.collectionView.backgroundColor = .white
self.collectionView.dataSource = self
self.collectionView.delegate = self
let iconImage = UIImage(named: "name")
let imageView = UIImageView(image: iconImage)
self.navigationItem.titleView = imageView
}
private let sectionInsets2 = UIEdgeInsets(
top: 50.0,
left: 20.0,
bottom: 50.0,
right: 20.0)
private let itemsPerRow: CGFloat = 1
private let itemsPerRow1: CGFloat = 1
private let itemsPerRow2: CGFloat = 2
// MARK: - Private
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
return 1
} else {
return photos.count
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomePageHeaderCell", for: indexPath) as! HomePageHeaderCell
return cell
} else if indexPath.section == 1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomePageProfileBar", for: indexPath) as! HomePageProfileBar
return cell
} else if indexPath.section == 2 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomePageCell", for: indexPath) as! HomePageCell
cell.photo = photos[indexPath.item]
//cell.pfppic.image = UIImage(named: "pfppic")
//cell.pfppic.layer.cornerRadius = 15.0
return cell
}
return UICollectionViewCell()
}
}
extension HomePageViewController: UICollectionViewDelegateFlowLayout {
// 1
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// 2H
if indexPath.section == 0 {
let paddingSpace = 0 * (itemsPerRow + 1)
let availableWidth = view.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: 50)
} else if indexPath.section == 1 {
let paddingSpace1 = 0 * (itemsPerRow1 + 1)
let availableWidth1 = view.frame.width - paddingSpace1
let widthPerItem1 = availableWidth1 / itemsPerRow1
return CGSize(width: widthPerItem1, height: 100)
} else {
let paddingSpace2 = sectionInsets2.left * (itemsPerRow2 + 1)
let availableWidth2 = view.frame.width - paddingSpace2
let widthPerItem2 = availableWidth2 / itemsPerRow2
return CGSize(width: widthPerItem2, height: widthPerItem2)
}
}
// 3
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets2
}
// 4
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return sectionInsets2.left
}
}
I have the array built and I don't know why any pictures won't show

How to make collectionViewLayout to dynamically change height and width of cell?

Am trying to make Collection View with a specific layout like on the image below
So some cell is two rows by two columns, and some are one by one
If anybody has some tutorial or hint please share.
EDIT - This is my code for Custom Layout class
protocol CustomLayoutDelegate: class {
func collectionView(_ collectionView: UICollectionView, sizeForCellAtIndexPath indexPath:IndexPath) -> CGSize
}
class CustomLayout: UICollectionViewFlowLayout {
weak var delegate: CustomLayoutDelegate!
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)
var cnt: CGFloat = 0
var cols: CGFloat = 0
var rows: CGFloat = 0
layoutAttributes?.forEach({ (attributes) in
guard let cv = collectionView else { return }
let indexPath = IndexPath(item: attributes.indexPath.item, section: 0)
let cellSize = delegate.collectionView(cv, sizeForCellAtIndexPath: indexPath)
attributes.frame = CGRect(x: cellSize.width * (cols), y: cellSize.height * rows, width: cellSize.width, height: cellSize.height)
print(cnt, cols, rows)
cnt += 1
cols = cols < 2 ? (cols + 1) : 0
if Int(cnt.truncatingRemainder(dividingBy: 3)) == 0 {
rows += 1
}
})
return layoutAttributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
Thanks

Actual performance of UICollectionView is really bad

I am currently working on a project and basically, I am still learning.
I was using at the beginning a UITableView, but I needed automatic height and 2 or 3 columns (depending on the device).
For the columns, I am using UICollectionViewFlowLayout.
In general, I am using only Labels and StackViews; a few warnings like Unable to simultaneously satisfy constraints (but I changed in a few ones priorities and is working but the performance is still really bad.
I am not retrieving info from backend, nothing spectacular.
The CustomLayout as follows:
public protocol CollectionViewFlowLayoutDelegate: class {
func numberOfColumns() -> Int
func height(at indexPath: IndexPath) -> CGFloat
}
public class CustomLayout: UICollectionViewFlowLayout {
private let cellPadding: CGFloat = 4
public var cache: [IndexPath : UICollectionViewLayoutAttributes] = [:]
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
public weak var flowDelegate: CollectionViewFlowLayoutDelegate?
public override var collectionViewContentSize: CGSize {
return CGSize(width: self.contentWidth, height: self.contentHeight)
}
public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributesArray = [UICollectionViewLayoutAttributes]()
if cache.isEmpty {
self.prepare()
}
for (_, layoutAttributes) in self.cache {
if rect.intersects(layoutAttributes.frame) {
layoutAttributesArray.append(layoutAttributes)
}
}
return layoutAttributesArray
}
public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return self.cache[indexPath]
}
public override func prepare() {
super.prepare()
cache.removeAll()
guard let collectionView = self.collectionView else {
return
}
let numberOfColumns = self.flowDelegate?.numberOfColumns() ?? 1
self.contentHeight = 0
let columnWidth = UIScreen.main.bounds.width / CGFloat(numberOfColumns) - CGFloat(numberOfColumns-1) * cellPadding
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
let photoHeight = self.flowDelegate?.height(at: indexPath) ?? 1
let height = cellPadding * 2 + photoHeight
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
self.cache[indexPath] = attributes
self.contentHeight = max(self.contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
I do need the 2 or 3 columns with the automatic height. But what is really important as in any other project: Performance.
Thank you very much in advance.
Turns out that on my ViewControllers, on the func height(at indexPath: IndexPath) -> CGFloat protocol, I was calculating the height each time, instead of only retrieving it from an array.

ambiguous reference to member 'subscript' swift 3

I am trying to create a UICollectionViewFlowLayout subclass and i am getting ambiguous reference to member 'subscript' error when i try to return the layoutAttributesForItem:
import UIKit
class FLCollectionViewFlowLayout: UICollectionViewFlowLayout {
var items = 0
let CELL_HEIGHT : CGFloat = 306.0
let CELL_WIDTH : CGFloat = 219.0
var cellInformation = Dictionary<NSIndexPath,UICollectionViewLayoutAttributes>()
var contentSize = CGSize.zero
override var collectionViewContentSize: CGSize {
return self.contentSize
}
override func prepare() {
super.prepare()
print("preparing layout")
items = 20
let sections = self.collectionView?.numberOfSections
var section = 0
var item = 0
var indexPath = NSIndexPath()
var contentWidth : CGFloat = 0.0
let contentHeight : CGFloat = CELL_HEIGHT + self.sectionInset.top + self.sectionInset.bottom
while section < sections! {
while item < (self.collectionView?.numberOfItems(inSection: section))! {
indexPath = NSIndexPath(item: item, section: section)
let xPos = CGFloat(item) * CELL_WIDTH
let yPos = CGFloat(section) * CELL_HEIGHT
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
cellInformation[indexPath] = cellAttributes
item+=1
}
contentWidth = CGFloat((self.collectionView?.numberOfItems(inSection:section))!) * CELL_WIDTH
section+=1
}
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesInRect = [UICollectionViewLayoutAttributes]()
for cellAttributes in cellInformation.values {
if rect.intersects(cellAttributes.frame) {
attributesInRect.append(cellAttributes)
}
}
return attributesInRect
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellInformation[indexPath]! // <---- This is where i get the error
}
}
Any suggestion would be greatly appreciated .
In Swift 3 NSIndexPath has been replaced with native struct IndexPath
Change
...
var cellInformation = Dictionary<IndexPath,UICollectionViewLayoutAttributes>()
...
var indexPath = IndexPath()
...
indexPath = IndexPath(item: item, section: section)
...
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)