swiftuicollectionviewuicollectionviewcelluipickerviewscroll-paging

Swift - Paging UICollectionView by cell while keeping the cell horizontally centered


First, not sure if the title is correct or offers the best description but I'm not sure what else to use.

So, i'm working on an app and I reached a section where I got stuck while implementing the UI. Basically, I have a VC (image below) that can segue to its self based on the info I get from a JSON file.

enter image description here

The thing is I need to have a carousel-like menu in the upper side with an undefined number of cells (again, depends on what I get from the JSON file). I decided to go for a UICollectionView for this and I managed to implement the basics without any problem.

But here is the part where I got stuck:

  1. Since the selected cell must ALWAYS be centered, when the first and the last cell gets selected, I need to have an empty space between the cell and the safe area (see the image above).
  2. The scroll needs to be paged. Normally this wouldn't be a problem if the UICollectionView cell would have a width almost equal to the one of the screen but the requirement is to be able to scroll one element at a time (see second screen above).

I tried finding something similar but maybe I'm not looking for the right thing because all I could find was Paging UICollectionView by cells, not screen

Also, to be honest I've never seen an app / UICollectionView with this behaviour.

I posted parts of the code below but it's not really gonna help much since it's just standard UICollectionView methods.

Any suggestions?

class PreSignupDataVC : UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UIPickerViewDelegate, UIPickerViewDataSource

@IBOutlet weak var cvQuestions: UICollectionView!

var questionCell : PreSignupDataQuestionCellVC!
var screenData : Array<PreSignupScreenData> = Array<PreSignupScreenData>()
var pvDataSource : [String] = []
var numberOfComponents : Int = 0
var numberOfRowsInComponent : Int = 0
var currentScreen : Int = 1
var selectedType : Int?
var selectedCell : Int = 0
var initialLastCellInsetPoint : CGFloat = 0.0

override func viewDidLoad()
{
    super.viewDidLoad()

    print("PreSignupDataVC > viewDidLoad")

    initialLastCellInsetPoint = (self.view.frame.width - 170)/2
    screenData = DataSingleton.sharedInstance.returnPreSignUpUIArray()[selectedType!].screenData
    numberOfComponents = screenData[currentScreen - 1].controls[0].numberOfComponents!
    numberOfRowsInComponent = screenData[currentScreen - 1].controls[0].controlDataSource.count
    pvDataSource = screenData[currentScreen - 1].controls[0].controlDataSource

    cvQuestions.register(UINib(nibName: "PreSignupDataQuestionCell",
                               bundle: nil),
                         forCellWithReuseIdentifier: "PreSignupDataQuestionCellVC")
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
    print("PreSignupDataVC > collectionView > numberOfItemsInSection")

    return screenData[currentScreen - 1].controls.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
    print("PreSignupDataVC > collectionView > cellForItemAt")

    questionCell = (cvQuestions.dequeueReusableCell(withReuseIdentifier: "PreSignupDataQuestionCellVC",
                                                    for: indexPath) as? PreSignupDataQuestionCellVC)!
    questionCell.vQuestionCellCellContainer.layer.cornerRadius = 8.0
    questionCell.lblQuestion.text = screenData[currentScreen - 1].controls[indexPath.row].cellTitle
    questionCell.ivQuestionCellImage.image = UIImage(named: screenData[currentScreen - 1].controls[indexPath.row].cellUnselectedIcon!)

    return questionCell
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
    print("PreSignupDataVC > collectionView > didSelectItemAt")

    numberOfComponents = screenData[currentScreen - 1].controls[indexPath.row].numberOfComponents!
    numberOfRowsInComponent = screenData[currentScreen - 1].controls[indexPath.row].controlDataSource.count
    pvDataSource = screenData[currentScreen - 1].controls[indexPath.row].controlDataSource
    selectedCell = indexPath.row

    pvData.reloadAllComponents()
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
{
    print("PreSignupDataVC > collectionView > insetForSectionAt")

    return UIEdgeInsets(top: 0.0, left: initialLastCellInsetPoint, bottom: 00.0, right: initialLastCellInsetPoint)
}

Solution

  • You can use insetForSectionAtIndex to add spacing at first and last cell

    UPDATE
    You can use scroll view.
    First: add leading and trailing to scroll view:
    enter image description here

    Second: scrollView.clipsToBounds = false

    Third: add view to scroll view

    func setupScrollView() {
        DispatchQueue.main.async {
            self.scrollView.layoutIfNeeded() // in order to get correct frame
    
            let numberItem = 6
            let spaceBetweenView: CGFloat = 20
            let firstAndLastSpace: CGFloat = spaceBetweenView / 2            
            let width = self.scrollView.frame.size.width
            let height = self.scrollView.frame.size.height
            let numberOfView: CGFloat = CGFloat(numberItem)
            let scrollViewContentSizeWidth = numberOfView * width
    
            self.scrollView.contentSize = CGSize(width: scrollViewContentSizeWidth, height: height)
    
            for index in 0..<numberItem {
                let xCoordinate = (CGFloat(index) * (width)) + firstAndLastSpace
                let viewFrame = CGRect(x: xCoordinate, y: 0, width: width - spaceBetweenView, height: height)
                let view = UIView()
                view.backgroundColor = .red
    
                view.frame = viewFrame
                self.scrollView.addSubview(view)
            }
        }
    }