I have a collection view which cells are 1/2 of screen width. The problem is that ONLY on iPhone X and Xs I have a line, that shows content beneath collection view. Why can it be so ?
import UIKit
import SnapKit
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(UICollectionViewCell.self), for: indexPath)
cell.backgroundColor = .white
return cell
}
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(UICollectionViewCell.self))
layout.itemSize = CGSize(width: (UIScreen.main.bounds.width / 2), height: UIScreen.main.bounds.width / 2)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
if #available(iOS 11.0, *) {
collectionView.contentInsetAdjustmentBehavior = .always
}
collectionView.snp.makeConstraints({ make in
make.edges.equalTo(self.view)
})
}
}
Related
I am a noob to SWIFT but I am trying to create a CollectionView. When I run the following code on an iPhone 11 simulator the CollectionView gives me three images per row, which is the result that I want. However, when I run the code on any other device I get what seems like a blank cell between the first and third cell on each row. The image below shows the difference between the working version (11 max pro) and the flawed version (SE). I am not sure what I am doing wrong or how to correct it. I am open to any suggestions on how to fix it or a better way to write the code.
var collectionViewFlowLayout: UICollectionViewFlowLayout!
let cellIdentifier = "ItemCollectionViewCell"
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
setupCollectionViewItemSize()
}
private func setupCollectionView(){
collectionView.delegate = self
collectionView.dataSource = self
let nib = UINib(nibName: "ItemCollectionViewCell", bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: cellIdentifier)
}
private func setupCollectionViewItemSize(){
if collectionViewFlowLayout == nil {
let numberOfItemsPerRow: CGFloat = 3
let lineSpacing: CGFloat = 5
let interItemSpacing: CGFloat = 5
let width = (collectionView.frame.width - (numberOfItemsPerRow - 1) * interItemSpacing) / numberOfItemsPerRow
let height = width
collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.itemSize = CGSize(width: width, height: height)
collectionViewFlowLayout.sectionInset = UIEdgeInsets.zero
collectionViewFlowLayout.scrollDirection = .vertical
collectionViewFlowLayout.minimumLineSpacing = lineSpacing
collectionViewFlowLayout.minimumInteritemSpacing = interItemSpacing
collectionView.setCollectionViewLayout(collectionViewFlowLayout, animated: true)
}
}
I was able to solve it by adding UICollectionViewDelegateFlowLayout to the view controller as well as a function that I found on SO.
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! ItemCollectionViewCell
cell.imageView.image = UIImage(named: items[indexPath.item].imageName)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = items[indexPath.item]
performSegue(withIdentifier: viewImageSegueIdentifier, sender: item)
}
//****This is the function that I added ****//
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let noOfCellsInRow = 3
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let totalSpace = flowLayout.sectionInset.left
+ flowLayout.sectionInset.right
+ (flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1))
let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(noOfCellsInRow))
return CGSize(width: size, height: size)
}
}
I have a UIcollectionview that allows scrolling the content horizontally only. Each UIcollectionviewCell is a custom view showing two labels one below the other. I have set constraints in the custom cell to position these labels. Irrespective of the screen width, I always want to show 7 UICollection cells. Is this possible?
You need to change itemSize by calculating from device width
let itemSpacing: CGFloat = 5
let itemsInOneLine: CGFloat = 7
let flow = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
flow.sectionInset = UIEdgeInsets(top: itemSpacing, left: itemSpacing, bottom: itemSpacing, right: itemSpacing)
flow.minimumInteritemSpacing = itemSpacing
flow.minimumLineSpacing = itemSpacing
let cellWidth = (UIScreen.main.bounds.width - (itemSpacing * 2) - ((itemsInOneLine - 1) * itemSpacing)) / itemsInOneLine
flow.itemSize = CGSize(width: cellWidth, height: 120)
Hope this is What you want,
Here is the code for this.
//
// ViewController.swift
// AssignmentSO
//
// Created by Anuradh Caldera on 4/24/19.
// Copyright © 2019 personal. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
private var mycollectionview: UICollectionView!
private let cellidentifier = "cellIdentifier"
private let minimuminterspace: CGFloat = 2
override func viewDidLoad() {
super.viewDidLoad()
setCollectionView()
}
}
extension ViewController {
fileprivate func setCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = minimuminterspace
layout.minimumInteritemSpacing = minimuminterspace
mycollectionview = UICollectionView(frame: .zero, collectionViewLayout: layout)
mycollectionview.translatesAutoresizingMaskIntoConstraints = false
mycollectionview.register(MyCell.self, forCellWithReuseIdentifier: cellidentifier)
mycollectionview.dataSource = self
mycollectionview.delegate = self
mycollectionview.backgroundColor = .white
mycollectionview.isPagingEnabled = true
// mycollectionview.contentInset = UIEdgeInsets(top: 0, left: minimuminterspace, bottom: 0, right: minimuminterspace)
view.addSubview(mycollectionview)
let collectionviewConstraints = [mycollectionview.leftAnchor.constraint(equalTo: view.leftAnchor, constant: minimuminterspace),
mycollectionview.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
mycollectionview.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -minimuminterspace),
mycollectionview.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height/4)]
NSLayoutConstraint.activate(collectionviewConstraints)
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellidentifier, for: indexPath) as! MyCell
cell.index = indexPath.item
cell.backgroundColor = .purple
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellwidth = mycollectionview.frame.width/7 - minimuminterspace
let cellheight = mycollectionview.frame.height
return CGSize(width: cellwidth, height: cellheight)
}
}
class MyCell: UICollectionViewCell {
private var mainlabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setLabel()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var index: Int! {
didSet {
mainlabel.text = "\(index+1)"
}
}
}
extension MyCell {
fileprivate func setLabel() {
mainlabel = UILabel()
mainlabel.translatesAutoresizingMaskIntoConstraints = false
mainlabel.textColor = .white
mainlabel.textAlignment = .center
addSubview(mainlabel)
let mainlabelConstraints = [mainlabel.centerXAnchor.constraint(equalTo: centerXAnchor),
mainlabel.centerYAnchor.constraint(equalTo: centerYAnchor)]
NSLayoutConstraint.activate(mainlabelConstraints)
}
}
Note: if you want to show 7 cells, then it is better to enable pagination. you can change the height as your need. hope this will help to someone. cheers!
Step 1 - Implement UICollectionViewDelegateFlowLayout delegate
Step 2 - Add below method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (collectionViewSize/7) , height: 50 )
}
Step 3 - pass size according to your requirement
Implement the collectionView layout delegate.
And divide the screen width in 7 parts as below.
please note following code is with respect to the collectionView width, considering the auto layout of collectionView width is set to screen margin.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width/7, height:collectionView.bounds.height)
}
Though this will suffice your need, you will also need to handle the Cell's subviews to fit in the cell and display the content properly.
Situlation
I made a horizontal scrolling collection view with UICollectionViewFlowLayout.automaticSize.
The collection view has height of 40
The cell has height of 40 which I set by autolayou on init.
Problem
The problem is that ONLY cells displayed on first displaying the collection view
has size of 50x50 which overridden autolayout height of 40 I manually set by code.
I checked the size on willDisplayCell delegate method.
When I scroll the collection view to display other cells, each newly displayed
cells have proper size with heigh of 40.
Although the cells with 50x50 size(as logged) are being displayed correctly(with height 40 it seems) on the screen.
From this, I get error messaged(logged) when I run the app on simulator and
the app crashed on if I run on my real device(iPhone8 iOS 11).
Code
ViewController
import UIKit
class HorizontalTabNavigationViewController: UIViewController {
// MARK: - Collection View
private lazy var navigationCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
cv.dataSource = self
cv.delegate = self
return cv
}()
private typealias Cell = HorizontalTabNavigationCell
private let cellId: String = "cell"
// MARK: - Property
var items: [HorizontalTabNavigationItem] = []
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.view.translatesAutoresizingMaskIntoConstraints = false
navigationCollectionView.register(Cell.self, forCellWithReuseIdentifier: cellId)
navigationCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
setupSubviews()
}
private func setupSubviews() {
view.addSubview( navigationCollectionView )
[
navigationCollectionView.topAnchor .constraint(equalTo: view.topAnchor),
navigationCollectionView.leftAnchor .constraint(equalTo: view.leftAnchor),
navigationCollectionView.rightAnchor .constraint(equalTo: view.rightAnchor),
navigationCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
].forEach { $0.isActive = true }
}
}
extension HorizontalTabNavigationViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! Cell
//cell.setProperties(item: items[indexPath.row])
//print("Cell for item: \(items[indexPath.row].title)")
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
print("Cell Size on willDispaly cell")
print(cell.bounds.size)
print("\n")
}
}
extension HorizontalTabNavigationViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5 // Defines horizontal spacing between cells
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return .zero
}
}
Cell
class HorizontalTabNavigationCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupSelfHeight()
self.backgroundColor = .orange
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupSelfHeight() {
self.heightAnchor.constraint(equalToConstant: 40).isActive = true
self.widthAnchor.constraint(equalToConstant: 120).isActive = true
}
}
I solved the problem by setting custom size to flowlayout.
Inspired by Manav's anwer on this question below.
the behavior of the UICollectionViewFlowLayout is not defined, because the cell width is greater than collectionView width
private lazy var navigationCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
// [Here is the answer]
// Set Height equal or less than the height of the collection view.
// This is applied on first display of cells and
// will also not affect cells auto sizing.
layout.estimatedItemSize = CGSize(width: 200, height: 40) // UICollectionViewFlowLayoutAutomaticSize
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = .white
cv.dataSource = self
cv.delegate = self
return cv
}()
I am trying to add a headerCell to a UICollectionView and am trying to call the referenceSizeForHeaderInSection method. I have created a collectionViewCell (ProfileCell) and it builds correctly, until I call the the referenceSizeForHeaderInSection method, then it crashes...not sure why. Thanks in advance for any and all help!
// UICollectionViewCell class *******
class ProfileCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let wordLabel: UILabel = {
let label = UILabel()
label.text = "test test test"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setupViews() {
backgroundColor = .red
addSubview(wordLabel)
wordLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
wordLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
wordLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
wordLabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// UIViewController *******
class HomeController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, FBSDKLoginButtonDelegate {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.dataSource = self
cv.isPagingEnabled = true
return cv
}()
let cellId = "cellId"
let headerId = "headerId"
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
// use autolayout instead
collectionView.anchorToTop(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
collectionView.backgroundColor = .white
collectionView.register(ProfileCell.self, forCellWithReuseIdentifier: cellId)
collectionView.register(UICollectionViewCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId)
fileprivate func registerCells() {
collectionView.register(ProfileCell.self, forCellWithReuseIdentifier: cellId)
}
// set up collectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = 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: 150)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableCell(withReuseIdentifier: headerId, for: indexPath)
return header
}
// crashes when I call this method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 50)
}
}
I created a collectionView programmatically (without storyboard). I set the background color to red, and it is showing properly. But the cells are not showing. Does anyone know why the cells are not showing?
private let cellId = "cellId"
class InfoViewShow: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
private let cellHeight:CGFloat = 80
var collectionView: UICollectionView?
let layout = UICollectionViewFlowLayout()
override init() {
super.init()
setupCollectionview()
collectionView?.registerClass(InfoCell.self, forCellWithReuseIdentifier: cellId)
}
private func setupCollectionview() {
if let view = UIApplication.sharedApplication().keyWindow {
//let y = (view.frame.width * 10 / 16) + 34 + 26
collectionView = UICollectionView(frame: .zero , collectionViewLayout: layout)
collectionView?.backgroundColor = UIColor.redColor()
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.frame = CGRectMake(0, 0, view.frame.width, view.frame.height) // y to y
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath)
cell.backgroundColor = UIColor.blackColor()
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(collectionView.frame.width, cellHeight)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(5, 5, 5, 5); //top,left,bottom,right
}
}
I dont' see you add collectionView to view.
if let view = UIApplication.sharedApplication().keyWindow {
//let y = (view.frame.width * 10 / 16) + 34 + 26
collectionView = UICollectionView(frame: .zero , collectionViewLayout: layout)
collectionView?.backgroundColor = UIColor.redColor()
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.frame = CGRectMake(0, 0, view.frame.width, view.frame.height) // y to y
///try add
self.view.addSubview(collectionView)
}
You must implement numberOfSectionsInCollectionView of UICollectionViewDataSource delegate method.
This method is optional but you must implement it and return at least one section.
I know I am 6 years late to answer this question but I also struggled with it for 2 days before finding the mistake I was making. Hope this answer helps someone.
Check whether you have written the following two lines in your override func viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
self.collectionView.reloadData()