CollectionView in the storyboard is overwriting the cell size - ios

I have a collection view that has cells that change size depending on what size the user has selected: Small, medium, large. I have the protocol UICollectionViewDelegateFlowLayout and the delegate function:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
The function correctly sets the size of the area needed for the cell but doesn't resize the cell itself.
here is what my view looks like when the cells have been set to the small setting:
Here is the view with the default size:
As you can see the space for the cell changes but the cell size doesn't change.
In the storyboard, whatever the cell size is set to, is what the size will be regardless of what I do in the code. Does anyone know how I can override this:
Any help would be greatly appreciated. Thanks
EDIT
The delegate function is being called. the size is changed on a different page. The size is stored as a value in a singleton, the delegate function decides what size to use based on that value. Here is the delegate function:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
// if self.collectionView == collectionView {
let viewHeight = collectionView.frame.height
let smallSize = (viewHeight / 4) - 20
let mediumSize = (viewHeight / 2) - 20
let largeSize = viewHeight - 20
switch gridSquareSize {
case .small:
return CGSize(width: smallSize, height: smallSize)
case .standard:
return CGSize(width: mediumSize, height: mediumSize)
case .large:
return CGSize(width: largeSize, height: largeSize)
}
//}
}
EDIT 2:
Here is the function that sets up the cell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "galleryCell", for: indexPath)
as! GalleryCell
cell.layoutIfNeeded()
switch SettingsModel.instance.currentGridSquareSize {
case .small:
cell.galleryName.font = cell.galleryName.font.withSize(12)
break
case .standard:
cell.galleryName.font = cell.galleryName.font.withSize(24)
break
case .large:
cell.galleryName.font = cell.galleryName.font.withSize(36)
break
}
cell.isHidden = false
cell.background.backgroundColor = UIColor.white
cell.plusIcon.isHidden = true
cell.topLeftImage.isHidden = false
cell.galleryName.textColor = UIColor(red: 45, green: 64, blue: 83, alpha: 1)
cell.topLeftImage.image = UIImage(named: "PlaceholderImage")
cell.delete.isHidden = !isInEditMode
let position = indexPath.row
if position == mGalleriesList.count {
if isInEditMode {
cell.background.backgroundColor = UIColor.darkGray
cell.galleryName.text = "Add new gallery"
cell.galleryName.textColor = UIColor.white
cell.plusIcon.isHidden = false
cell.topLeftImage.isHidden = true
cell.delete.isHidden = true
}
else {
cell.isHidden = true
}
}
else {
let gallery = mGalleriesList[position]
cell.data = gallery
cell.plusIcon.isHidden = true
if let galleryName = gallery.galleryName {
cell.galleryName.text = galleryName
}
var mediaEntries = [MediaEntry]()
for entry in gallery.mediaEntries {
if let deleted = entry.deleted {
if !deleted {
mediaEntries.append(entry)
}
}
else {
mediaEntries.append(entry)
}
}
if mediaEntries.count > 0 {
if let downloadUrl = mediaEntries[0].downloadUrl {
setImage(storageUrl: downloadUrl, imageView: cell.topLeftImage, placeholder: placeholder!, cropToBounds: false)
}
}
}
return cell
}
Here is also a screenshot of the cell inside the storyboard:
Hope this additional information helps

Related

iOS UICollectionView Horizontal Scrolling Rectangular Layout with different size of items?

iOS UICollectionView how to Create Horizontal Scrolling rectangular layout with different size of items inside.
I want to create a Rectangular layout using UICollectionView like below. how can i achieve?
When i scroll horizontally using CollectionView 1,2,3,4,5,6 grid will scroll together to bring 7.
The Below are the dimensions of 320*480 iPhone Resolution. Updated Screen below.
First 6 items have below dimensions for iPhone 5s.
Item 1 Size is - (213*148)
Item 2 Size is - (106*75)
Item 3 Size is - (106*74)
Item 4 Size is - (106*88)
Item 5 Size is - (106*88)
Item 6 Size is - (106*88)
After item6 have same dimensions as collection View width and height like below.
Item 7 Size is - (320*237)
Item 8 Size is - (320*237)
Item 9 Size is - (320*237)
How to create a simple custom Layout Using collection view, that has horizontal scrolling?
Must appreciate for a quick solution. Thanks in advance.
I would suggest using a StackView inside CollectionViewCell(of fixed dimension) to create a grid layout as shown in your post.
Below GridStackView creates a dynamic grid layout based on the number of views added using method addCell(view: UIView).
Add this GridStackView as the only subview of your CollectionViewCell pinning all the edges to the sides so that it fills the CollectionViewCell completely.
while preparing your CollectionViewCell, add tile views to it using the method addCell(view: UIView).
If only one view added, then it will show a single view occupying whole GridStackView and so as whole CollectionViewCell.
If there is more than one view added, it will automatically layout them in the inside the CollectionViewCell.
You can tweak the code below to get the desired layout calculating the row and column. Current implementation needed rowSize to be supplied while initializing which I used for one of my project, you need to modify it a bit to get your desired layout.
class GridStackView: UIStackView {
private var cells: [UIView] = []
private var currentRow: UIStackView?
var rowSize: Int = 3
var defaultSpacing: CGFloat = 5
init(rowSize: Int) {
self.rowSize = rowSize
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
axis = .vertical
spacing = defaultSpacing
distribution = .fillEqually
}
required init(coder: NSCoder) {
super.init(coder: coder)
translatesAutoresizingMaskIntoConstraints = false
axis = .vertical
spacing = defaultSpacing
distribution = .fillEqually
}
private func preapreRow() -> UIStackView {
let row = UIStackView(arrangedSubviews: [])
row.spacing = defaultSpacing
row.translatesAutoresizingMaskIntoConstraints = false
row.axis = .horizontal
row.distribution = .fillEqually
return row
}
func removeAllCell() {
for item in arrangedSubviews {
item.removeFromSuperview()
}
cells.removeAll()
currentRow = nil
}
func addCell(view: UIView) {
let firstCellInRow = cells.count % rowSize == 0
if currentRow == nil || firstCellInRow {
currentRow = preapreRow()
addArrangedSubview(currentRow!)
}
view.translatesAutoresizingMaskIntoConstraints = false
cells.append(view)
currentRow?.addArrangedSubview(view)
setNeedsLayout()
}
}
Create a new cell that contains two views. Views have equal width.
Contstruct your data accordingly
Data
struct ItemData {
var color: [UIColor]
}
// NOTICE: 2nd item contains two colors and the rest one.
let data = [ItemData(color: [.red]), ItemData(color: [.blue, .purple]), ItemData(color: [.orange]),
ItemData(color: [.cyan]), ItemData(color: [.green]), ItemData(color: [.magenta]),
ItemData(color: [.systemPink]), ItemData(color: [.link]), ItemData(color: [.purple])]
Cell
class CollectionViewCellOne: UICollectionViewCell {
static let identifier = "CollectionViewCellOne"
var item: ItemData? {
didSet {
if let item = item {
self.leadingLabel.backgroundColor = item.color.first!
self.trailingLabel.backgroundColor = item.color.last!
}
}
}
let leadingLabel = UILabel()
let trailingLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.contentView.addSubview(leadingLabel)
self.contentView.addSubview(trailingLabel)
let width = self.frame.width / 2
leadingLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
leadingLabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
leadingLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
leadingLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
leadingLabel.translatesAutoresizingMaskIntoConstraints = false
trailingLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
trailingLabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
trailingLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
trailingLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
trailingLabel.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder: NSCoder) {
fatalError()
}
}
dequeueReusableCell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCellOne.identifier, for: indexPath) as! CollectionViewCellOne
cell.item = data[indexPath.row]
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
if let color = data[indexPath.row].color.first {
cell.backgroundColor = color
}
return cell
}
}
I have tried with Mahan's Answer and i am getting the partially Correct output. But the issue is, index1 having full width of two items.
How to split index 1 into index1 and Index2?
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setUpCollectionView()
// Do any additional setup after loading the view.
}
func setUpCollectionView() {
self.view.backgroundColor = .white
let layout = UICollectionViewFlowLayout()
// layout.minimumInteritemSpacing = 1
// layout.minimumLineSpacing = 1
layout.scrollDirection = .horizontal
let collectionView = CollectionView(frame: .zero, collectionViewLayout: layout)
view.addSubview(collectionView)
collectionView.bounces = false
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
collectionView.heightAnchor.constraint(equalToConstant: 240).isActive = true
collectionView.translatesAutoresizingMaskIntoConstraints = false
}
}
class CollectionView: UICollectionView {
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
self.register(CollectionViewCell.self, forCellWithReuseIdentifier: CollectionViewCell.identifier)
self.dataSource = self
self.delegate = self
self.isPagingEnabled = true
}
required init?(coder: NSCoder) {
fatalError()
}
}
extension CollectionView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
cell.backgroundColor = .blue
cell.label.text = "\(indexPath.row)"
let row = indexPath.row
switch row {
case 0:
cell.backgroundColor = .red
case 1:
cell.backgroundColor = .blue
case 2:
cell.backgroundColor = .purple
case 3:
cell.backgroundColor = .orange
case 4:
cell.backgroundColor = .cyan
case 5:
cell.backgroundColor = .green
case 6:
cell.backgroundColor = .magenta
case 7:
cell.backgroundColor = .white
case 8:
cell.backgroundColor = .blue
case 9:
cell.backgroundColor = .green
default:
cell.backgroundColor = .systemPink
}
return cell
}
}
extension CollectionView: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let row = indexPath.row
let width = collectionView.frame.width
let other = width / 3
let height = collectionView.frame.height
let o_height = height / 3
switch row {
case 0:
return CGSize(width: other * 2, height: o_height * 2)
case 1:
return CGSize(width: other * 2, height: o_height)
case 2:
return CGSize(width: other, height: o_height)
case 3:
return CGSize(width: other, height: o_height)
case 4:
return CGSize(width: other, height: o_height)
case 5, 6, 7:
return CGSize(width: other, height: o_height)
default:
return CGSize(width: width, height: height)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return .leastNormalMagnitude
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return .leastNormalMagnitude
}
}
class CollectionViewCell: UICollectionViewCell {
static let identifier = "CollectionViewCell"
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.contentView.addSubview(label)
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
label.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder: NSCoder) {
fatalError()
}
}
How to devide index 1 into index1 and Index2 like below?
Thanks in advance!

UICollectionView two cells with same views. Second cell shows nothing

What would cause this behaviour? I have a collectionview with two cells. The two cells should have the same views but different text content. Nothing shows up in the second cell. Am I doing something wrong in "cellforrowat"? What am I missing?
class UpgradeController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource,UIScrollViewDelegate{
let numMonths = ["1","6","12"]
let months = ["month","months","months"]
let prices = ["$19.99","$79.99","$99.99"]
let pricePerMonth = ["","($13.33 per month)","($8.33 per month)"]
let pricesExtra = ["$29.99","119.99","149.99"]
let pricePerMonthExtra = ["","$19.99 per month","$12.49 per month"]
var collectionView:UICollectionView!
var scrollView:UIScrollView!
let cellId = "cellId"
let cellId2 = "cellId2"
override func viewDidLoad() {
super.viewDidLoad()
self.setupViews()
}
func setupViews(){
let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let navBarHeight: CGFloat = self.navigationController!.navigationBar.frame.height
let tabBarheight: CGFloat = self.tabBarController!.tabBar.frame.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = self.view.frame.height
//view.setGradientBackgroundColor(colorOne: UIColor(rgb:0x000000), colorTwo: UIColor(rgb:0x056644))
navigationController?.navigationBar.isTranslucent = false
setupCollectionView()
setupMenuBar()
scrollView = UIScrollView()
scrollView.backgroundColor = .clear //.orange
scrollView.delegate = self
scrollView.frame = CGRect(x:0,y:50,width:UIScreen.main.bounds.width,height:UIScreen.main.bounds.height)
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout{
flowLayout.scrollDirection = .horizontal //= .horizontal
}
scrollView.addSubview(self.collectionView)
self.view.addSubview(scrollView)
}
Collection view setup :
func setupCollectionView(){
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 120, right: 0)
//layout.itemSize = CGSize(width: screenWidth / 3, height: screenWidth / 3)
//layout.itemSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.collectionViewLayout = layout
collectionView.dataSource = self
collectionView.delegate = self
collectionView?.register(DragonCell.self, forCellWithReuseIdentifier: cellId)
collectionView!.backgroundColor = UIColor.clear
collectionView.contentInset = UIEdgeInsets(top: 0,left: 0,bottom: 0,right: 0)
collectionView.isPagingEnabled = true
self.view.addSubview(collectionView)
}
let titles = ["Dragon", "DragonExtra"]
lazy var menuBar: MenuBar = {
let mb = MenuBar()
mb.backgroundColor = .red
mb.translatesAutoresizingMaskIntoConstraints = false
mb.names = ["Dragon", "DragonExtra"]
mb.upgradeController = self
return mb
}()
private func setupMenuBar(){
menuBar.setupHorizontalBar()
//menuBar.multiplier = CGFloat(1/8.0)
view.addSubview(menuBar) //view.addSubview(menuBar)
view.addConstraintsWithFormat("H:[v0(\(view.frame.width*CGFloat(menuBar.names.count)/4.0))]", views: menuBar)
view.addConstraintsWithFormat("V:|[v0(50)]|", views: menuBar)
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[v1]-(<=1)-[v0]", options: NSLayoutConstraint.FormatOptions.alignAllCenterX, metrics: nil, views: ["v0":menuBar,"v1":self.view])) //center horizontally
}
func scrollToMenuIndex(menuIndex:Int){
let indexPath = NSIndexPath(item: menuIndex, section: 0)
collectionView.scrollToItem(at: indexPath as IndexPath, at: [], animated: true)
//setTitleForIndex(index:menuIndex)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//menuBar.horizontalBarLeftAnchorConstraint?.constant = scrollView.contentOffset.x/2 + view.frame.width/8.0
menuBar.horizontalBarLeftAnchorConstraint?.constant = scrollView.contentOffset.x/CGFloat(titles.count*2)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let index = targetContentOffset.pointee.x / view.frame.width
let indexPath = NSIndexPath(item: Int(index), section: 0)
menuBar.collectionView.selectItem(at: indexPath as IndexPath, animated: true, scrollPosition: [])
//setTitleForIndex(index:Int(index))
}
private func setTitleForIndex(index:Int){
/*
if let titleLabel = navigationItem.titleView as? UILabel{
titleLabel.text = titles[Int(index)]
}
*/
}
Collection view delegate methods :
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
print(indexPath.item)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! DragonCell
switch indexPath.item{
case 0:
cell.numMonths = numMonths
cell.months = months
cell.prices = prices
cell.pricePerMonth = pricePerMonth
cell.backgroundColor = .blue
return cell
case 1:
cell.numMonths = numMonths
cell.months = months
cell.prices = pricesExtra
cell.pricePerMonth = pricePerMonthExtra
cell.backgroundColor = .red
return cell
default:
let cell = UICollectionViewCell() //collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width:view.frame.width,height:view.frame.height)
}
}
From what I can see in your code you are wanting to reuse the same collection view but simply change content ?
So why not have a condition based on what menu bar item is selected?
like so...
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
print(indexPath.item)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! DragonCell
//Not exactly sure if this is correct for what you have written
//for your code. But the idea is take the index that is shown or active //from your menu bar.
//You have used a switch statement which used the collection view //index. not the menubar index
if menuBar.index == 0 {
cell.numMonths = numMonths
cell.months = months
cell.prices = prices
cell.pricePerMonth = pricePerMonth
cell.backgroundColor = .blue
return cell
} else {
cell.numMonths = numMonths
cell.months = months
cell.prices = pricesExtra
cell.pricePerMonth = pricePerMonthExtra
cell.backgroundColor = .red
return cell
}
//switch indexPath.item{
//case 0:
//cell.numMonths = numMonths
//cell.months = months
//cell.prices = prices
//cell.pricePerMonth = pricePerMonth
//cell.backgroundColor = .blue
//return cell
//case 1:
//cell.numMonths = numMonths
//cell.months = months
//cell.prices = pricesExtra
//cell.pricePerMonth = pricePerMonthExtra
//cell.backgroundColor = .red
//return cell
//default:
//let cell = UICollectionViewCell() //collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
return cell
}
}
It looks like in your switch statement you are using the indexPath of the Collectionview. But dont you want to show content based on the index path of the menu bar selected?
However, I would go through an entirely different approach and create a SECOND collection view for that other view.

UICollectionView Lag/frame drop

Good afternoon all my app is currently dropping frames and i am not sure how to fix it...im a bit lost.
hopefully you guys/gals can point me in the right direction.
I am currently using Kingfisher to download the images from my backend, everything works well with the exception of the scrolling it's a bit choppy when you scroll down.
the controller code is as followed maybe there's something wrong with it.
i am using storyboard and have removed and added the constraints...but ive had no luck.
import UIKit
import Kingfisher
class WallpaperCVC: UICollectionViewController , UIViewControllerTransitioningDelegate {
var categoryId: String!
var categoryName: String!
var wallpaperBackend: Backend!
var result:CGSize!
// Header peralax
fileprivate let headerId = "headerId"
fileprivate let padding: CGFloat = 16
#IBOutlet var wpMain: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.title = self.categoryName // Name to the controller or section selected
// go go gadget data...
getData()
buttonGray()
setupCollectionViewLayout()
setupCollectionView()
}
func getData(){
GLOBAL_BACKEND_DATA.removeAll()
self.wallpaperBackend = Backend()
self.wallpaperBackend.getWallByIdData(id: self.categoryId) { (god) in
for post in god {
let postData = post as! NSDictionary
let wallpaperCVCId = postData.value(forKey: "id") as! String
let wallpaperCVCFile = postData.value(forKey: "file") as! String
let wallpaperCVCCategory = postData.value(forKey: "category") as! String
let wallpaperCVCDownload = Int(postData.value(forKey: "download") as! String)
GLOBAL_BACKEND_DATA.append(WallpaperModel(wallpaperModelId: wallpaperCVCId, wallpaperModelFile: wallpaperCVCFile, wallpaperModelCategoryId: wallpaperCVCCategory, wallpaperModelDownload: wallpaperCVCDownload!))
}
self.collectionView?.reloadData()
}
}
#IBAction func pressedAction(_ sender: UIButton) {
// do your stuff here
self.dismiss(animated: true, completion: nil)
print("you clicked on button \(sender.tag)")
}
func buttonGray(){
let myButton = UIButton() // if you want to set the type use like UIButton(type: .RoundedRect) or UIButton(type: .Custom)
let img = UIImage(named: "buttonA")
myButton.setImage(img, for: .normal)
// myButton.setTitleColor(UIColor.blue, for: .normal)
myButton.frame = CGRect(x: 15, y: 50, width: 100, height: 100)
// shadow
myButton.layer.shadowOpacity = 0.5
myButton.layer.shadowRadius = 4
myButton.layer.shadowOffset = CGSize(width: 0, height: 5)
myButton.addTarget(self, action: #selector(pressedAction(_:)), for: .touchUpInside)
// haptic feedback
myButton.isHaptic = true
myButton.hapticType = .impact(.medium)
//floatbutton like this
view.addSubview(myButton)
myButton.translatesAutoresizingMaskIntoConstraints = false
myButton.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 10).isActive = true
myButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// You only need one section for now
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// Here call the wallpapers
return GLOBAL_BACKEND_DATA.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// pulling & going to the controller
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "wallpapers", for: indexPath) as! WallpaperCVCell
let godData = GLOBAL_BACKEND_DATA[indexPath.row]
let imageURL = String(format:"%#uploads/image/%#", BASE_BACKEND_URL,godData.wallpaperModelFile) // calling wallpaperModel controller
let escapedURL = imageURL.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let urls = URL(string:escapedURL!)
// if nothing load this...
let loadImage = UIImage(named: "skullLoading")
cell.collWallpaperImage.kf.setImage(with: urls, placeholder: loadImage)
cell.collWallpaperImage.kf.indicatorType = .activity // Showing a loading indicator while downloading
// lets count the downloads
let count = godData.wallpaperModelDownload as Int // calling wallpaperModel controller
cell.collectionCount.text = String(count) //collection view cell count label
cell.configure() // calling a function from the cell
return cell
}
// Lets move over to the next controller
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Tapped....Tapped go to the next controller..")
collectionView.deselectItem(at: indexPath, animated: true)
let vc = self.storyboard!.instantiateViewController(withIdentifier: "page") as! PageViewController
vc.position = indexPath.row
vc.categoryName = self.categoryName
vc.transitioningDelegate = self
vc.modalPresentationStyle = .custom
self.present(vc, animated: true, completion: nil)
}
}
// MARK: - CollectionView Delegate
extension WallpaperCVC {
// size images
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
result = CGSize(width: 187, height: 314)
if DeviceType.IS_IPHONE_5 {
result = CGSize(width: 160, height: 280)
}
if DeviceType.IS_IPHONE_6 {
result = CGSize(width: 187, height: 314)
}
if DeviceType.IS_IPHONE_6P {
result = CGSize(width: 207, height: 320)
}
if DeviceType.IS_IPHONE_4_OR_LESS {
result = CGSize(width: 151, height: 200)
}
return result;
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 15, left: 8, bottom: 5, right: 8)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
//size to all model screens to one.....NOW!!!!!!!!
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width
return CGSize(width: (width - 28)/2, height: 350) // width & height are the same to make a square cell
}
}
// MARK: - CollectionView Layout Delegate
extension WallpaperCVC : UICollectionViewDelegateFlowLayout {
// Header peralax
fileprivate func setupCollectionViewLayout() {
let padding: CGFloat = 16
if let layout = wpMain.collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = .init(top: padding, left: 0, bottom: padding, right: 0)
}
}
// Header peralax
fileprivate func setupCollectionView(){
wpMain.contentInsetAdjustmentBehavior = .never
wpMain.register(headerView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerId)
}
// Header peralax
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath)
return headerView
}
// Header peralax
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 400)
}
}
the cell looks like this
import UIKit
import Spring
import UICountingLabel
class WallpaperCVCell: UICollectionViewCell {
override func layoutSubviews() {
super.layoutSubviews()
// self.textLabel?.frame = self.bounds
}
func configure() {
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
contentView.layer.cornerRadius = 7
contentView.layer.masksToBounds = true
// contentView.layer.borderWidth = 1.0
// contentView.layer.borderColor = UIColor.black.cgColor
}
#IBOutlet weak var collectionCount: UICountingLabel!
#IBOutlet weak var collWallpaperImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
}
&& here is the video...the initial scroll lags then it goes away but it does it everytime
iPhone app lag video
EDIT: Ok in the end i remode Kingfisher & installed SDWebImage this took care of the problem.
Based on the given code there is nothing that should cause the lag. If you could share a short video of the lag that might help understand the problem better.
And also if you could share the code that is in your cell.configure() method that would also help.
I do have a few suggestions for you that might help, you can try them on.
first of all you can move your device check
if DeviceType.IS_IPHONE_5 {
result = CGSize(width: 160, height: 280)
}
if DeviceType.IS_IPHONE_6 {
result = CGSize(width: 187, height: 314)
}
if DeviceType.IS_IPHONE_6P {
result = CGSize(width: 207, height: 320)
}
if DeviceType.IS_IPHONE_4_OR_LESS {
result = CGSize(width: 151, height: 200)
}
in viewDidLoad which will help your collection view layout to size your cells faster and not having to check this every time the user scrolls
Something like this
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
return result;
}
and
override func viewDidLoad() {
super.viewDidLoad()
self.title = self.categoryName // Name to the controller or section selected
setupDeviceSize()
// go go gadget data...
getData()
buttonGray()
setupCollectionViewLayout()
setupCollectionView()
}
func setupDeviceSize(){
if DeviceType.IS_IPHONE_5 {
result = CGSize(width: 160, height: 280)
}
if DeviceType.IS_IPHONE_6 {
result = CGSize(width: 187, height: 314)
}
if DeviceType.IS_IPHONE_6P {
result = CGSize(width: 207, height: 320)
}
if DeviceType.IS_IPHONE_4_OR_LESS {
result = CGSize(width: 151, height: 200)
}
}
Second is you can create a global variable for your place holder image
let loadImage = UIImage(named: "skullLoading")
and use that in your cellForItemAtIndexpath, which will again save loading of the placeholder image every time your cellForItemAtIndexpath is called.
You should always keep your cellForItemAtIndexpath as light as possible for a smooth scrolling experience.
Hope this helps.

UIcollection view crashes when i change the number of items in data Source

self.collectionView.delegate = self
self.collectionView.dataSource = self
self.collectionView.isPrefetchingEnabled = false
self.collectionView.layer.shadowColor = UIColor.black.cgColor
self.collectionView.layer.shadowOffset = CGSize(width: -1, height: 1)//CGSizeMake(0, 1)
self.collectionView.layer.shadowOpacity = 0.5
self.collectionView.layer.shadowRadius = 3.0
self.collectionView.clipsToBounds = false
self.collectionView.layer.masksToBounds = false
this is what i did with collection view in didLoad
now the problem is when i set my data source array lets say
var myDataSource:[Custom Model] = []
when i change myDataSourceArray it has 10 values initially after changes this array got only 1 value now when i reload collection view it gives this...
*** Assertion failure in -[UICollectionViewData validateLayoutInRect:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3698.54.4/UICollectionViewData.m:435
2018-10-29 12:28:27.884302+0500 MyBetterDealsv2[14203:147469] Task <A3EE0E68-F980-47FD-A5B2-941C1D233A34>.<7> finished with error - code: -1001
2018-10-29 12:28:27.904908+0500 MyBetterDealsv2[14203:145748] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist: <NSIndexPath: 0xc000000000200016> {length = 2, path = 0 - 1}'
i tried invalidate layout also but it gives same error didnt get the solution yet :(
below are my delegate methods for collectionview
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
// tell the collection view how many cells to make
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(self.myallDeals != nil && self.myallDeals?.count != 0){
self.collectionViewHeightConstraint.constant = 220
}
return self.myallDeals?.count ?? 0
}
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "dealCell", for: indexPath ) as! DealsCell
let cellData = self.myallDeals?[indexPath.row]
cell.dealTitle.text = cellData?.dealTitle
cell.dealDescription.text = cellData?.dealLocation
cell.discount.text = "$\(cellData?.discountedPrice ?? "") now"
cell.dealDistanceLabel.text = "\(cellData?.distance ?? 0.0) km away"
cell.ratingView.rating = cellData?.averageRating ?? 0.0
cell.dealImage.sd_setImage(with: URL(string: cellData?.dealPicture ?? ""), placeholderImage: nil)
if(cellData?.isliked == true){
cell.likeButton.backgroundColor = UIColor(red: 251/255, green: 106/255, blue: 74/255, alpha: 1.0)
cell.likeButton.setImage(UIImage(named: "liked"), for: .normal)
}else{
cell.likeButton.backgroundColor = UIColor(red:255, green: 255, blue:255, alpha: 1.0)
cell.likeButton.setImage(UIImage(named: "heart"), for: .normal)
}
//===========================
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap event
if(Constants.isGuestUser){
self.present(Utilities.SelectionButtonActionSheet(), animated: true, completion: nil)
}else{
let deal = self.myallDeals?[indexPath.row]
//dealdetail
let vc = self.storyboard?.instantiateViewController(withIdentifier: "dealdetail") as! DealDetailVC
vc.mydeal = deal
Utilities.animationOnPush(sender: self, nxtVC: vc)
print("You selected cell.")
}
}
this is the orignal cell
this is what i got using sateesh code
I hope this help you
let layOut = UICollectionViewFlowLayout()
layOut.layout.scrollDirection = //.horizontal or .vertical
self.collectionView?.collectionViewLayout = layOut
And use below delegate method of "UICollectionViewDelegateFlowLayout"
extension yourViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
// your code here
//width and height as per your requirement
return CGSize(width: self.view.frame,size.width - 24, height: height)
}
}

Swift Collection View custom layout change cell width in cell side

Hello i have working collectionview custom layout codes but some cells i need to change cell width in cell side comming varible from json dynamicly.
My CustomCollectionViewLayout
import UIKit
public var CELL_HEIGHT = 22.0
public var CELL_WIDTH = 40.0 // HERE WIDTH SOME CELLS MUST BE 80
class CustomCollectionViewLayout: UICollectionViewLayout {
// Used for calculating each cells CGRect on screen.
// CGRect will define the Origin and Size of the cell.
let STATUS_BAR = UIApplication.sharedApplication().statusBarFrame.height
// Dictionary to hold the UICollectionViewLayoutAttributes for
// each cell. The layout attribtues will define the cell's size
// and position (x, y, and z index). I have found this process
// to be one of the heavier parts of the layout. I recommend
// holding onto this data after it has been calculated in either
// a dictionary or data store of some kind for a smooth performance.
var cellAttrsDictionary = Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>()
// Defines the size of the area the user can move around in
// within the collection view.
var contentSize = CGSize.zero
// Used to determine if a data source update has occured.
// Note: The data source would be responsible for updating
// this value if an update was performed.
var dataSourceDidUpdate = true
override func collectionViewContentSize() -> CGSize {
return self.contentSize
}
override func prepareLayout() {
// Only update header cells.
if !dataSourceDidUpdate {
// Determine current content offsets.
let xOffset = collectionView!.contentOffset.x
let yOffset = collectionView!.contentOffset.y
if collectionView?.numberOfSections() > 0 {
for section in 0...collectionView!.numberOfSections()-1 {
// Confirm the section has items.
if collectionView?.numberOfItemsInSection(section) > 0 {
// Update all items in the first row.
if section == 0 {
for item in 0...collectionView!.numberOfItemsInSection(section)-1 {
// Build indexPath to get attributes from dictionary.
let indexPath = NSIndexPath(forItem: item, inSection: section)
// Update y-position to follow user.
if let attrs = cellAttrsDictionary[indexPath] {
var frame = attrs.frame
// Also update x-position for corner cell.
if item == 0 {
frame.origin.x = xOffset
}
frame.origin.y = yOffset
attrs.frame = frame
}
}
// For all other sections, we only need to update
// the x-position for the fist item.
} else {
// Build indexPath to get attributes from dictionary.
let indexPath = NSIndexPath(forItem: 0, inSection: section)
// Update y-position to follow user.
if let attrs = cellAttrsDictionary[indexPath] {
var frame = attrs.frame
frame.origin.x = xOffset
attrs.frame = frame
}
}
}
}
}
// Do not run attribute generation code
// unless data source has been updated.
return
}
// Acknowledge data source change, and disable for next time.
dataSourceDidUpdate = false
var maxItemInASection: Int?
// Cycle through each section of the data source.
if collectionView?.numberOfSections() > 0 {
for section in 0...collectionView!.numberOfSections()-1 {
// Cycle through each item in the section.
if collectionView?.numberOfItemsInSection(section) > 0 {
for item in 0...collectionView!.numberOfItemsInSection(section)-1 {
// Build the UICollectionVieLayoutAttributes for the cell.
let cellIndex = NSIndexPath(forItem: item, inSection: section)
let xPos = Double(item) * CELL_WIDTH
let yPos = Double(section) * CELL_HEIGHT
let cellAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: cellIndex)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
// Determine zIndex based on cell type.
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
}
// Save the attributes.
cellAttrsDictionary[cellIndex] = cellAttributes
if maxItemInASection < item {
maxItemInASection = item
}
}
}
}
}
// Update content size.
let contentWidth = Double(maxItemInASection ?? 0) * CELL_WIDTH
let contentHeight = Double(collectionView!.numberOfSections()) * CELL_HEIGHT
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Create an array to hold all elements found in our current view.
var attributesInRect = [UICollectionViewLayoutAttributes]()
// Check each element to see if it should be returned.
for cellAttributes in cellAttrsDictionary.values.elements {
if CGRectIntersectsRect(rect, cellAttributes.frame) {
attributesInRect.append(cellAttributes)
}
}
// Return list of elements.
return attributesInRect
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttrsDictionary[indexPath]!
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
}
And here my cell codes.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("customCell", forIndexPath: indexPath) as! CustomCollectionViewCell
if items[indexPath.section].base == 2 {
cell.label.text = items[indexPath.section].base
cell.frame.size.width = 80
cell.layer.frame.size.width = 80
CELL_WIDTH = 80.0 // HERE I CHANGED SOME CELLS WIDTH 80 BUT DONT CHANGE THEM !!
}else{
cell.label.text = items[indexPath.section].base
}
You can see top side
CELL_WIDTH = 80.0 // HERE I CHANGED SOME CELLS WIDTH 80 BUT DONT CHANGE THEM !!
You could implement collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) instead of subclassing UICollectionViewLayout - unless you have other reasons to do that.
It would go somewhat like:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.item % 3 == 0 { // <-- your condition here
return CGSize(width: 40, height: 22)
}
return CGSize(width: 80, height: 22)
}
By the way, don't forget to set your collectionView's delegate in order for this method to be called. 😁
UPDATED
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if items[indexPath.section].base == 2 {
return CGSize(width: 80, height: 22)
} else {
return CGSize(width: 44, height: 22)
}
}
This is how I think your method should look like.
At your cellForItem you only need to worry about setting the labels, let the above code take care of the size of the cells.
sizeForItemAtIndexPath is not present for UICollectionViewLayout. So your CustomCollectionViewLayout inherited from UICollectionViewLayout does not have a protocol. You can create a protocol widthForItemAtIndexPath method.
protocol CustomCollectionViewDelegateLayout: NSObjectProtocol {
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, widthForItemAtIndexPath indexPath: NSIndexPath) -> CGFloat
}
In your viewController implemented this protocol method. Return value will be the width for the given indexPath
extension CustomCollectionViewController: CustomCollectionViewDelegateLayout {
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, widthForItemAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//from indexPath.section and indexPath.item identify double width item
if sirano == 2 && satimdurum == 0 {
return regularWidth * 2
}
return regularWidth
}
}
In prepareLayout() method of your CustomCollectionViewLayout you can calculate position of each item you need to check which item should be double.
guard let width = delegate?.collectionView(collectionView!, layout: self, widthForItemAtIndexPath: cellIndex) else {
print("Please conform to CustomCollectionViewDelegateLayout protocol")
return
}
Made a demo project with Xcode 7.2 here at https://github.com/rishi420/UICollectionviewsample
Do not change cell size inside the cellForItemAtIndexPath method. The cell layout is already set after dequeueReusableCellWithReuseIdentifier.
Change it in the sizeForItemAtIndexPath. This is called before the above delegate method.

Resources