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

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

Related

How to make vertical uiCollectionViewCell Left side when CollectionView has only one item

How to make vertical uiCollectionViewCell Left side when CollectionView has only one item
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
collectionviw.delegate = self
collectionviw.dataSource = self
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 5, bottom: 10, right: 2)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionviw!.collectionViewLayout = layout
collectionviw.reloadData()
}
extension ViewController: UICollectionViewDelegate,
UICollectionViewDataSource,UICollectionViewDelegateFlowLayout
{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection
section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt i
ndexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionviw.dequeueReusableCell(withReuseIdentifier: "cell",
for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath:
IndexPath) -> CGSize {
let collectionWidth = collectionView.bounds.width / 2 - 50
return CGSize(width: collectionWidth, height: collectionWidth)
}
}
output
I think this is a bug in iOS that automatically centers the single cell in CollectionView because of UICollectionViewFlowLayoutAutomaticSize. You can create your own UICollectionViewFlowLayout
I followed the approach given here :
Stop Single UICollectionView cell Flowing to the centre of the screen

How to align these collectionview cells properly?

I am creating a page where I use collection view. The layout will be like Apple's Music app where each row displays two square-shaped cells
I want a layout like this-this layout has super equal margins around but in my app the first collection cell is stuck to the left edge and the 2nd cell is stuck to the right edge
private let cellReuseIdentifier = "cellReuseIdentifier"
class FeedViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
setupNavBar()
setupCollectionView()
}
func setupCollectionView() {
collectionView?.register(FeedCollectionViewCell.self, forCellWithReuseIdentifier: cellReuseIdentifier)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath)
return cell
}
}
extension FeedViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 25
let collectionViewSize = collectionView.frame.size.width - padding
return CGSize(width: collectionViewSize/2, height: collectionViewSize/2)
}
}
You can use the layout properties to set the collection view inset and the space you want between your cells, in your collection view setup function:
guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
layout.sectionInset = UIEdgeInsets(top: yourTopValue, left: yourLeftValue, bottom: yourBottomValue, right: yourRightValue)
layout.minimumInteritemSpacing = yourSpacing
layout.minimumLineSpacing = yourSpacing
Use following code to achieve your needs.
Padding value should be addition of all three spaces(Left, Center, Right)
Let's suppose padding value = 25 then it should consider 75 in layout calculation to get exact spacing.
And set left, top, center and right space with value 25(As you want to add space there).
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellWidth: CGFloat = (collectionView.frame.size.width / 2) - ((padding value * 3)/2)
let cellHeight: CGFloat = (collectionView.frame.size.height / 2) - ((padding value * 3)/2)
return CGSize.init(width: cellWidth, height: cellHeight)
}
Sure it will work

Swift iOS -CollectionView how to scroll cells off of screen

I have a collectionView with 4 cells. Since there are only 4 cell inside the collection view it won't scroll any further.
When scrolling up I want to scroll the 4 cells off of the screen (collectionView will appear empty). Once the cells are off screen I want the scrolling to stop, I don't want to scroll forever. When scrolling back down I want to scroll the 4 cells back onto the scene.
I tried changing the collectionView.bounds.origin and the collectionView.contentSize.height but neither worked, I still couldn't scroll the 4 cells off of the screen.
I can always add clear cells but that seems like a hack.
What is the appropriate way to accomplish this?
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0)
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.alwaysBounceVertical = true
collectionView.backgroundColor = .white
collectionView.register(MyCell.self, forCellWithReuseIdentifier: myCell)
view.addSubview(collectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: myCell, for: indexPath) as! MyCell
cell.textLabel.text = "Hello"
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 44)
}
You could use the scrollView's contentInset:
collectionView?.contentInset.bottom = collectionView?.frame.height ?? 0

Weird drawing when using UICollectionView created programmatically

I am creating a UICollectionView and its cells programatically. In my custom UICollectionView the only method that is implemented is layoutSubviews. Here is the code below for setting up my collection view and handling the delegation. As you can see from the gif once in a while a cell loads correctly, but the majority are misaligned or off screen. I suspect it is something wrong with how they are reused. I usually use tableviews and scrollviews, so I'm not as comfortable with collection views. How can I fix this?
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.itemSize = CGSize(width: 160, height: 200)
featuredCollectionView = UICollectionView(frame: frame), collectionViewLayout: layout)
featuredCollectionView.delegate = self
featuredCollectionView.dataSource = self
featuredCollectionView.register(ItemCollectionViewCell.self, forCellWithReuseIdentifier:
"cell")
// MARK: CollectionView Delegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
{
return 8
}
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: 160, height: featuredCollectionView.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return featuredItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ItemCollectionViewCell
cell.item = featuredItems[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
{
return UIEdgeInsetsMake(0, 0, 0, 0)
}
To see your main issue, check the frame of backgroundCardView, you're setting it based on the cell's origin within the collection view.
backgroundCardView.frame = CGRect(x: self.frame.origin.x + 8, y: self.frame.origin.x + 8, width: self.frame.size.width - 16, height: self.frame.size.height - 16)
This is going to be way outside the cell's bounds, which is why it appears to be laying out incorrectly. Also, you should add subviews to the cell's contentView instead of directly to it's view. As noted in other comments, you shouldn't add views in layoutSubviews. A better place would be inside an init method.

UICollectionView Custom Cell to fill width In Swift

I've been trying to figure-out how can i make the cell fill the width, as you can see in the picture the width between the cells is too big. i am using custom cell with only one imageView.
I tried to customize it from the storyboard but i guess there is no option for that or it should be done programmatically.
my UICollectionViewController :
#IBOutlet var collectionView2: UICollectionView!
let recipeImages = ["angry_birds_cake", "creme_brelee", "egg_benedict", "full_breakfast", "green_tea", "ham_and_cheese_panini", "ham_and_egg_sandwich", "hamburger", "instant_noodle_with_egg.jpg", "japanese_noodle_with_pork", "mushroom_risotto", "noodle_with_bbq_pork", "starbucks_coffee", "thai_shrimp_cake", "vegetable_curry", "white_chocolate_donut"]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return recipeImages.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! RecipeCollectionViewCell
// Configure the cell
cell.recipeImageView.image = UIImage(named: recipeImages[indexPath.row])
return cell
}
You need to do this programatically.
Implement UICollectionViewDelegateFlowLayout in your view controller and provide the size in collectionView:layout:sizeForItemAtIndexPath:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let kWhateverHeightYouWant = 100
return CGSizeMake(collectionView.bounds.size.width, CGFloat(kWhateverHeightYouWant))
}
You will also want to call collectionView.collectionViewLayout.invalidateLayout() inside your view controller's viewWillLayoutSubviews() so that when the main view's dimensions change (on rotation, for example), the collection view is re-laid out.
Swift 4 Update
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let kWhateverHeightYouWant = 100
return CGSize(width: collectionView.bounds.size.width, height: CGFloat(kWhateverHeightYouWant))
}
Inside your view controller override viewDidLayoutSubviews method
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
let itemWidth = view.bounds.width / 3.0
let itemHeight = layout.itemSize.height
layout.itemSize = CGSize(width: itemWidth, height: itemHeight)
layout.invalidateLayout()
}
}
(collectionView property is your collectionView)
Use Following code for set the width of UICollectionViewCell.
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: screenWidth/3, height: screenWidth/3);
}
Also in Swift 3,
Make sure your view controller complies with the following:
UICollectionViewDelegate,
UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout
Swift 3
If you are using swift 3, use this method:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
}
Notice the changes:
add _ before collectionView in the method name
NSIndexPath changes to IndexPath
I have the same requirement, in my case below solution is worked. I set UIImageView top, left, bottom and right constraints to 0 inside UICollectionviewCell
#IBOutlet weak var imagesCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// flowlayout
let screenWidth = UIScreen.main.bounds.width
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 10, right: 0)
layout.itemSize = CGSize(width: screenWidth/3 - 5, height: screenWidth/3 - 5)
layout.minimumInteritemSpacing = 5
layout.minimumLineSpacing = 5
imagesCollectionView.collectionViewLayout = layout
}
For my case, I wanted to show two columns and 3 rows in the UICollectionView
I added UICollectionViewDelegateFlowLayout delegate to my class
then I override sizeForItemAt indexPath
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellHeight = (collectionView.bounds.size.height - 30) / 3 // 3 count of rows to show
let cellWidth = (collectionView.bounds.size.width - 20) / 2 // 2 count of colomn to show
return CGSize(width: CGFloat(cellWidth), height: CGFloat(cellHeight))
}
The 30 is the Line spacing, the 20 is the insent between cells
Programatically set the itemSize to [UIScreen mainScreen].bounds.size.width/3
In my case, assuming every cell has a width of 155 and a height of 220. If I want to show 2 cells per row in a portrait mode and 3 for landscape.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var itemsCount : CGFloat = 2.0
if UIApplication.sharedApplication().statusBarOrientation != UIInterfaceOrientation.Portrait {
itemsCount = 3.0
}
return CGSize(width: self.view.frame.width/itemsCount - 20, height: 220/155 * (self.view.frame.width/itemsCount - 20));
}
The best solution is to change the itemSize of your UICollectionViewFlowLayout in viewDidLayoutSubviews. That is where you can get the most accurate size of the UICollectionView. You don't need to call invalidate on your layout, that will be done for you already.
For my case, autolayout was only allowing the cell to grow to its content size even though I overrode the cell size in the storyboard, conformed to UICollectionViewDelegateFlowLayout, and implemented sizeForItemAt. So I made a dummy UIView the width of the cell's bounds.
UICollectionViewController:
extension CollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width - 40
return CGSize(width: width, height: collectionView.bounds.height / 3)
}
}
Cell (I'm calling this method on dependency injection):
private func updateViews() {
let padding:CGFloat = 8
let innerView = UIView()
innerView.translatesAutoresizingMaskIntoConstraints = false
innerView.layer.cornerRadius = 8
innerView.layer.borderColor = UIColor.black.cgColor
innerView.layer.borderWidth = 1
contentView.addSubview(innerView)
NSLayoutConstraint.activate([
innerView.widthAnchor.constraint(equalToConstant: bounds.width - padding*2),
innerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: padding),
innerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -padding),
innerView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: padding),
innerView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -padding)
])
}

Resources