I have a CollectionView with have 2 items.
CollectionView has pagingEnabled, I want to show them circularly like:
[1] 2 -> [2] 1 -> [1] 2 -> [2] 1
Is it possible to do this with CollectionView
My code:
extension HomeViewController: UICollectionViewDelegateFlowLayout {
func configureHeaderView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
headerView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: headerHeight), collectionViewLayout: layout)
headerView?.backgroundColor = .blue
headerView?.isPagingEnabled = true
headerView?.isUserInteractionEnabled = true
headerView?.rx.setDelegate(self).addDisposableTo(disposeBag)
let nib = UINib(nibName: BannerCollectionViewCell.reuseIdentifier, bundle: nil)
headerView?.register(nib, forCellWithReuseIdentifier: BannerCollectionViewCell.reuseIdentifier)
headerView?.showsHorizontalScrollIndicator = false
tableView.tableHeaderView = headerView
if let headerView = headerView {
self.bannerItems.asObservable().bindTo(headerView.rx.items(cellIdentifier: BannerCollectionViewCell.reuseIdentifier, cellType: BannerCollectionViewCell.self)) {
row, banner, cell in
cell.banner = banner
}.addDisposableTo(disposeBag)
}
}
// MARK: UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: headerHeight)
}
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
}
}
Make use of UICollectionView's scrollToItemAtIndexPath method to move collectionView back to the first item if it reached the end and the user swiped right. Move the collectionView back to the last item if user swiped left from first item. You should set the animation property to false
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
// Calculate where the collection view should be at the right-hand end item
let offsetRight = collectionView.frame.size.width * CGFloat(yourDataArray.count)
if scrollView.contentOffset.x == offsetRight {
// user is scrolling to the right from the last item to the 'fake' item 1.
// reposition offset to show the 'real' item 1 at the left-hand end of the collection view
let newIndexPath = IndexPath(row: 1, section: 0)
collectionView.scrollToItem(at: newIndexPath, at: .left, animated: false)
} else if scrollView.contentOffset.x == 0 {
// user is scrolling to the left from the first item to the fake 'item N'.
// reposition offset to show the 'real' item N at the right end of the collection view
let newIndexPath = IndexPath(row: (yourDataArray.count-2), section: 0)
collectionView.scrollToItem(at: newIndexPath, at: .left, animated: false)
}
}
Related
I have a collection view that scrolls horizontally, but I cannot figure out how to make the cells center and stop at the center of each scroll. So for example if you swift left the next cell should be centered in the middle of the screen, how can I do this? I'll post the collection view I have created:
class TakesViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
private lazy var selectionFeedbackGenerator = UISelectionFeedbackGenerator()
private let cellId = "TakeCell"
var currentActiveIndex: Int = 0
func setUpCollectionView(){
self.collectionView.isPrefetchingEnabled = true
self.collectionView.translatesAutoresizingMaskIntoConstraints = false
self.collectionView.backgroundColor = .clear
self.collectionView.showsHorizontalScrollIndicator = false
self.collectionView.showsVerticalScrollIndicator = false
self.collectionView.contentInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15)
self.collectionView.alwaysBounceVertical = false
self.collectionView.alwaysBounceHorizontal = false
self.collectionView.decelerationRate = .fast
self.collectionView.configureForPeekingDelegate()
self.collectionView.register(UINib(nibName: "TakesCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "TakeCell")
}
init() {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 15
super.init(collectionViewLayout: flowLayout)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
setUpCollectionView()
}
}
extension TakesViewController {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellId, for: indexPath) as? TakeCollectionViewCell else {return UICollectionViewCell()}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 320, height: 575)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
//Where elements_count is the count of all your items in that
//Collection view...
let cellCount = CGFloat(12)
//If the cell count is zero, there is no point in calculating anything.
if cellCount > 0 {
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let cellWidth = flowLayout.itemSize.width + flowLayout.minimumInteritemSpacing
//20.00 was just extra spacing I wanted to add to my cell.
let totalCellWidth = cellWidth*cellCount + 20.00 * (cellCount-1)
let contentWidth = collectionView.frame.size.width - collectionView.contentInset.left - collectionView.contentInset.right
if (totalCellWidth < contentWidth) {
//If the number of cells that exists take up less room than the
//collection view width... then there is an actual point to centering them.
//Calculate the right amount of padding to center the cells.
let padding = (contentWidth - totalCellWidth) / 2.0
return UIEdgeInsets(top: 0, left: padding, bottom: 0, right: padding)
} else {
//Pretty much if the number of cells that exist take up
//more room than the actual collectionView width, there is no
// point in trying to center them. So we leave the default behavior.
return UIEdgeInsets(top: 0, left: 40, bottom: 0, right: 40)
}
}
return UIEdgeInsets.zero
}
}
It's a scroll view. Where it stops when the user lets go is up to you.
https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619385-scrollviewwillenddragging
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
I have a UICollectionView that I have implemented to scroll horizontally but for some reason when I scroll through there are only 3 cells that are presented (when there should be 9)
class BusinessPhotosViewController: UICollectionViewController ,UICollectionViewDelegateFlowLayout{
let cellId = "photos"
override func viewDidLoad() {
super.viewDidLoad()
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
}
collectionView?.register(BusinessPhotoCells.self, forCellWithReuseIdentifier: cellId)
collectionView?.contentInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0)
collectionView?.scrollIndicatorInsets = UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0)
collectionView?.isPagingEnabled = true
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 9
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! BusinessPhotoCells
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = view.frame.width / 3
return CGSize(width: width, height: width)
}
}
I present this BusinessPhotosViewController within the cell of ANOTHER UICollectionViewController called BusinessDetailViewController
class BusinessDetailViewController : UICollectionViewController {
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let scrollCell = collectionView.dequeueReusableCell(withReuseIdentifier: pictureCellId, for: indexPath)
let bizVC = BusinessPhotosViewController(collectionViewLayout: UICollectionViewFlowLayout())
scrollCell.addSubview(bizVC.view)
bizVC.view.anchor(top: scrollCell.topAnchor, left: scrollCell.leftAnchor, bottom: scrollCell.bottomAnchor, right: scrollCell.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: scrollCell.frame.width, height: scrollCell.frame.height)
return scrollCell
}
}
So I can see that the scrolling works, but the issue I'm seeing is that only 3 cells are loaded (out of 9) and the view looks something like this:
I've seen these solutions here and here and other but none helped me.
I tried your code and I can see all the 9 cells if the size of 9 cell is smaller than the size scroll cell size.
import UIKit
class ViewController: UIViewController{
override func viewDidLoad() {
view.backgroundColor = .red;
view.addSubview(photosContainer);
photosContainer.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true;
photosContainer.topAnchor.constraint(equalTo: view.centerYAnchor).isActive = true;
photosContainer.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true;
photosContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.4).isActive = true;
***let layout = UICollectionViewFlowLayout();
layout.scrollDirection = .horizontal
let bizVC = BusinessPhotosViewController(collectionViewLayout: layout)
bizVC.view.frame = photosContainer.bounds;
photosContainer.addSubview(bizVC.view)***
}
let photosContainer : UIView = {
let view = UIView();
view.backgroundColor = .green;
view.translatesAutoresizingMaskIntoConstraints = false;
return view;
}();
}
Before adding the view if you set the frame then I can see all the nine cells no matter their size.
Let me know if it works
I have a collectionView with horizontal scroll direction and paging enabled, now I want to make it scroll as smoothly as the games section on the AppStore.
To be more specific this is how I want the first cell to be:
EDIT
I actually achieved both of the next images one with the smaller width
of the cell and the next with the .centeredHorizontally in selectItem,
my problem is the non smoothly scrolling.
As you can see a little of the second cell is appearing.
And here is the next cells:
As you can see now the previous and the next cell are appearing.
I managed to do this by setting this part of code but with bouncing no smoothly as AppStore.
This is the code I used:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var visibleRect = CGRect()
visibleRect.origin = collectionView.contentOffset
visibleRect.size = collectionView.bounds.size
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath: IndexPath = collectionView.indexPathForItem(at: visiblePoint) ?? IndexPath()
let indexPath = IndexPath(item: visibleIndexPath[1], section: 0)
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally )
}
And this is my collectionView code:
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
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let frameSize = collectionView.frame.size
return CGSize(width: frameSize.width - 30, height: frameSize.height - 40)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0)
}
Example project here
In class property
var itemSize = CGSize(width: 0, height: 0)
#IBOutlet weak var collectionViewHeight: NSLayoutConstraint!
#IBOutlet weak var collectionView: UICollectionView!
fileprivate let sectionInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
In viewDidLoad or awakeFormNib(If in collectionViewCell or tableViewCell)
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
}
collectionView.contentInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 8)
collectionView.isPagingEnabled = false
layoutIfNeeded()
let width = collectionView.bounds.size.width-24
let height = width * (3/4)
itemSize = CGSize(width: width, height: height)
layoutIfNeeded()
Implement UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return itemSize
}
Implement ScrollViewDelegate
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageWidth = itemSize.width
targetContentOffset.pointee = scrollView.contentOffset
var factor: CGFloat = 0.5
if velocity.x < 0 {
factor = -factor
print("right")
} else {
print("left")
}
let a:CGFloat = scrollView.contentOffset.x/pageWidth
var index = Int( round(a+factor) )
if index < 0 {
index = 0
}
if index > collectionViewDataList.count-1 {
index = collectionViewDataList.count-1
}
let indexPath = IndexPath(row: index, section: 0)
collectionView?.scrollToItem(at: indexPath, at: .left, animated: true)
}
See example screenshot from my project at Past rides section
I have implemented uicollectionview in collectionViewCell
Set collectionView?.isPagingEnabled = true and remove your scrollViewDidEndDecelerating method
I Wanted to design the UICollectionview both portrait and landscape mode.Here i am using UICollectionViewFlowLayout
I have entered my view controller code
import UIKit
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
#IBOutlet var collectionObject: UICollectionView!
var isOrientation = true
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
layout.itemSize = CGSize(width: 90, height: 90)
layout.scrollDirection = .horizontal
collectionObject.collectionViewLayout = layout
collectionObject!.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
NotificationCenter.default.addObserver(self, selector: #selector(self.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
collectionObject.delegate = self
collectionObject.dataSource = self
self .adjustContentInsets()
}
func rotated() {
if UIDevice.current.orientation.isLandscape {
print("Landscape")
// layout.scrollDirection = .vertical
isOrientation = false
} else {
print("Portrait")
//layout.scrollDirection = .horizontal
isOrientation = true
}
collectionObject?.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: UICOLLECTIONVIEW DELEGATES AND DATASOURCE
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
cell.backgroundColor = UIColor.orange
cell.textLabel.text = String(indexPath.item)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var width :CGFloat = 0
var height :CGFloat = 0
if isOrientation == true {
width = self.collectionObject!.frame.size.width / 2
height = self.collectionObject!.frame.size.height / 2
}else{
width = self.collectionObject!.frame.size.height / 2
height = self.collectionObject!.frame.size.height / 2
}
if indexPath.row == 0 {
return CGSize(width: width, height: height)
}else if(indexPath.row == 1 || indexPath.row == 2){
return CGSize(width: width / 2, height: height / 2)
}else{
var isLwidth :CGFloat = 0
if isOrientation == true {
isLwidth = self.collectionObject!.frame.size.width / 2 - 40
}else{
isLwidth = self.collectionObject!.frame.size.width / 2 - 3
}
return CGSize(width: isLwidth, height: 100)
}
//return CGSize(width: self.view.frame.size.width-200, height: self.view.frame.size.height-250)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets{
return UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
}
func adjustContentInsets() {
let inset = UIEdgeInsetsMake(UIApplication.shared.statusBarFrame.size.height, 0, 0, 0)
self.collectionObject.contentInset = inset
self.collectionObject.scrollIndicatorInsets = inset
}
}
My output not coming as excepted:
Portrait Mode
Landscape Mode
The output is entire wrong, i am doing wrong with sizeForItemAt delegate.How to make layout like below using UICollectionview?
Actual Design:
Portrait
Landscape
Here The collection view should not scroll both direction.The array count always 10 only.
I wanted to fit CollectionViewCell with in view Bounds
The bottom blue colour view is different UIView.
Or Is there any possible way to achieve this by single control?
Thanks in advance!!!