UICollectionView defaulting to one column, how do I get two? - ios

I've been learning about UICollectionViews recently and I am currently trying to implement one. I need to display two columns but no matter what I do it defaults to one column. There are plenty of similar questions here that suggest adjusting the width of the UICollectionView cell using the collectionView function sizeForItemAt, but I can't seem to get it to work.
this is my viewcontroller code:
class PreferencesViewController: UIViewController {
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionView.ScrollDirection.vertical
let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.register(CategorySelectorCell.self, forCellWithReuseIdentifier: "categorySelector")
cv.backgroundColor = .clear
return cv
}()
override func viewDidLoad(){
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(collectionView)
collectionView.backgroundColor = .blue
collectionView.contentInsetAdjustmentBehavior = .always
collectionView.delegate = self
collectionView.dataSource = self
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
collectionView.heightAnchor.constraint(equalToConstant: view.frame.height/1.5).isActive = true
self.view = view
}
}
my delegate extension:
extension PreferencesViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "categorySelector", for: indexPath) as! CategorySelectorCell
cell.cat = categories[indexPath.section]
cell.backgroundColor = .red
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width/2.5, height: collectionView.frame.width/2)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
10
}
}
Any pointers appreciated!

Also add these UICollectionViewDelegateFlowLayout methods
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
Update
Also set inset to zero
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = .zero
layout.scrollDirection = UICollectionView.ScrollDirection.vertical
let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.register(CategorySelectorCell.self, forCellWithReuseIdentifier: "categorySelector")
cv.backgroundColor = .clear
return cv
}()

let's think your collection view UICollectionViewFlowLayout has 16 edge insets starts and end. And minimumInteritemSpacingForSectionAt,minimumLineSpacingForSectionAt to 16
Then you can calculate your cell width like this
(view.frame.width-flowlayoutInsets-sectionSpacing)/2
This is the codes
your collection view
let coll: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
let coll = UICollectionView(frame: .zero, collectionViewLayout: layout)
coll.backgroundColor = .systemPink
//other stuff
return coll
}()
Section and Line spacing
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 16
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 16
}
Collectionview cell size
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.frame.width-16-16-16)/2, height: 100)
//1st 16 - left gap - flowlayout left inset
//2nd 16 - middle gap - section spacing
// 3rd 16 = right gap - flowlayout right inset
}

Related

Implementing UICollectionView horizontally | Swift

I am implementing a collection view horizontally, and I want to achieve UI just like in app store.
I want to achieve the spacing that look, i.e UICollectionview cell is in the middle, and showing other cells on the sides. What happening to my code is that I am not able to scroll it to the centre.
Below is the code I am using to achieve what I did so far.
lazy var cardCollectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = .blue
collectionView.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
return collectionView
}()
Constraints
cardCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
cardCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
cardCollectionView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 42)
cardCollectionView.heightAnchor.constraint(equalToConstant: 250),
CollectionViewDelegate Methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCellId, for: indexPath) as! CardCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 364, height: 240)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top:0 , left: 25, bottom:0 , right:0)
}

UICollectionView centered pagination for a cell smaller than CollectionView's width

I am trying to have my cells around 0.85% the screen width so that the next and previous cells would be partially shown to tell the user that there are more cells.
I tried using many of the solutions on here along with collectionView.isPagingEnabled = true but the visible cell is never centered.
I expect the main cell that is shown to be centered regardless of whether there are more cells to its left/right or not.
This is the wrong behavior and my code. Thanks for any help.
Video:
https://imgur.com/a/LgvYFCB
override func viewDidLoad() {
super.viewDidLoad()
// View preparation
view.addSubview(mainTabBarView)
mainTabBarView.configure()
// Collection view
mainTabBarView.collectionView.dataSource = self
mainTabBarView.collectionView.delegate = self
}
extension MainTabBarController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cells.previewCell, for: indexPath) as! PreviewCell
cell.configure()
return cell
}
}
extension MainTabBarController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width*0.85, height: collectionView.frame.size.height)
}
}
extension MainTabBarController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 15
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 15
}
}
class MainTabBarView {
// MARK:- Main configuration
func configure() {
...
setupCollectionView()
...
}
private func setupCollectionView() {
let flowLayout = UICollectionViewFlowLayout()
let screenWidth = UIScreen.main.bounds.width
flowLayout.itemSize = CGSize(width: Int(screenWidth * 0.85), height: 68)
flowLayout.minimumInteritemSpacing = 15
flowLayout.minimumLineSpacing = 15
flowLayout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: CGRect(), collectionViewLayout: flowLayout)
collectionView.backgroundColor = Colors.Primary.clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(PreviewCell.self, forCellWithReuseIdentifier: Cells.previewCell)
collectionView.allowsSelection = false
collectionView.contentInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
collectionView.isPagingEnabled = true
}
}
Not a completed solution to the problem per se, but I see a couple of approaches which may help.
implement your own custom collection view layout so that you have full control on the cells layout and attributes, and you can make a logic in order to make the cell centered.
you make the collecview width 85% of the view controller width, and marking his clipToBounds as false, and finally setting the cell width to be like the collection view width. I did not test it but it could show the le left and right cell in the dequeue overflowing the boundaries of the collection view, as clipToBounds of the collection view is set to false. If you need to have a spacing between the cells, you could then actually wrap the visual content oer each cell in a view which has a certain margin from leading and trailing, and the result might be the same

UICollectionViewCell doesn't fill the screen

I have a UIViewController containing a UICollectionView for vertical paging. The UICollectionView (blue area in the picture) is pinned to the top of the superview, but UICollectionViewCell (yellow) is not. I tried to explicitly pin the top constraint of the UICollectionViewCell to the top of UICollectionView but the program crashes.
class HomeViewController: UIViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.register(FeedCell.self, forCellWithReuseIdentifier: reuseIdentifier)
collectionView.backgroundColor = .blue
collectionView.showsVerticalScrollIndicator = false
collectionView.isPagingEnabled = true
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
view.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor),
view.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor)
])
collectionView.dataSource = self
collectionView.delegate = self
}
override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
// MARK: UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: collectionView.bounds.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
extension HomeViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cell.backgroundColor = indexPath.item % 2 == 0 ? .yellow : .orange
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
Just pin your collection view to the top of View, not to the safeArea
collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true;
And use to just your collection view to safe area insets:
collectionView.contentInsetAdjustmentBehavior = .never;
Instead of pinning to all safe area edges using pinEdgesToSafeArea(to:) you should pin the top constraint of the collection view to the top constraint of view. I don't know where pinEdgesToSafeArea(to:) comes from so can't comment on a replacement for that but you can use this to pin your collection view to the top of the screen:
collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
Edit:
based on the clarifications you've posted, try setting automaticallyAdjustsScrollIndicatorInsets to false:
collectionView.automaticallyAdjustsScrollIndicatorInsets = false
docs for more info

Pure code builds the collection but UICollectionViewDelegateFlowLayout is not executed

Pure code (I didn't use the storyboard, just use code) builds the collection but UICollectionViewDelegateFlowLayout is not executed.
class Calendarview : UIView , UICollectionViewDelegate , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout {
var collectionview : UICollectionView!
override func didAddSubview(_ subview: UIView) {
}
override func didMoveToSuperview() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
collectionview = UICollectionView(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height) , collectionViewLayout: layout)
collectionview.frame = self.frame
collectionview.register(CalendarCell.self, forCellWithReuseIdentifier: "Cell")
collectionview.dataSource = self
collectionview.delegate = self
self.addSubview(collectionview!)
collectionview.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 42
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: self.bounds.width / 7, height: self.bounds.height / 3)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CalendarCell
cell.lbshow = UILabel(frame: cell.bounds)
cell.lbshow.text = String(indexPath.row)
cell.addSubview(cell.lbshow)
cell.backgroundColor = .blue
return cell
}
}
Set delegate as self.collectionView.delegate = self;. UICollectionViewDelegateFlowLayout inherits from UICollectionViewDelegate. So it will called all methods of UICollectionViewDelegateFlowLayout.
Hence, You need to set self.collectionView.collectionViewLayout to your flow layout.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.estimatedItemSize = CGSize(width: 10, height: 10)
layout.itemSize = CGSize(width: 200, height: 200)
layout.minimumLineSpacing = 10
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.scrollDirection = .horizontal
collectionview.collectionViewLayout = layout

Swift UICollectionViewCells too spread apart

I'm trying to fit 5 UICollectionViewCell's on a single row, and while there seems to be enough horizontal space, there's too much space between each cell. I can't figure out how to reduce it. Here is some pseudo-code along with what it looks like:
class ViewController: UIViewController {
let my_view = MyView()
override func viewDidLoad() {
super.viewDidLoad()
self.my_view.delegate = self
self.my_view.translatesAutoresizingMaskIntoConstraints = false
self.scroll_view.addSubview(self.my_view)
self.my_view.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
self.my_view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 20).isActive = true
self.my_view.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
self.my_view.bottomAnchor.constraint(equalToConstant: 100).isActive = true
}
}
extension ViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
return cell
}
}
class MyView: UIView {
var delegate: ViewController! {
didSet {
self.collection_view.delegate = self.delegate
self.collection_view.dataSource = self.delegate
}
}
var collection_view: UICollectionView!
init() {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
layout.itemSize = CGSize(width: 60, height: 40)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
self.collection_view = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.collection_view.register(AgeCell.self, forCellWithReuseIdentifier: "cell")
self.collection_view.backgroundColor = .clear
self.collection_view.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.collection_view)
self.collection_view.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.collection_view.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
self.collection_view.topAnchor.constraint(equalTo: self.title_label.bottomAnchor, constant: 15).isActive = true
self.collection_view.heightAnchor.constraint(equalToConstant: 60).isActive = true
}
}
class MyCell: UICollectionViewCell {
let button: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor(r: 0, g: 0, b: 0, a: 100)
button.setTitle("Test", for: .normal)
button.layer.borderColor = UIColor.EVO_blue.cgColor
button.layer.borderWidth = 1.0
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.button)
self.button.frame = self.frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the width of the device, and only 3 show up:
What can I do to get all 5 cells to appear? Thanks.
Use UICollectionViewDelegateFlowLayout, You need to return the correct size in sizeForItemAt method. I'm trying to write the logic to resolve you problem.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (Globals.screenWidth - 60.0) / 5
return CGSize(width: width, height: 80.0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0, 5, 0, 5)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
try this.
The problem was that in my MyCell class, I was writing
self.button.frame = self.frame
Whereas I should have written
self.button.frame = self.bounds
Use the UICollectionViewDelegateFlowLayout method for manage the cell size, LineSpacing and InterItemSpacing :-
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath:
IndexPath) -> CGSize {
return CGSize // return size of cell according to you
}

Resources