UICollectionView - scrolling not inside, but in whole screen

81 Views Asked by At

I'm building my first app in Swift. I have RestaurantViewController. Half of the screen is filled by restaurant name, description, logo, atd. And under this I have UICollectionView(), filled with restaurant Coupons. Everything works fine, but I wanna disable scrolling INSIDE the CollectionView, and allow to scroll in entire page - so when user scrolls in coupons, whole page is scrolling down and next coupons are showed.

How can I do it?

IMAGE - Screen

IMAGE - I wanna this after scroll

Coupons are loaded via API, so they are available few moments after setupUI() etc.

My code inside setupUI():

...
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
self.couponsCollectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
couponsCollectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: CouponCollectionCell.reuseIdentifier)
couponsCollectionView!.alwaysBounceVertical = true
couponsCollectionView!.isScrollEnabled = false
...

I am not using Storyboard, just all programmatically. Also with UIKit and SnapKit (external library) My SnapKit code for create constraints in screen:

...
couponsCollectionView!.snp.makeConstraints { make in
   make.top.equalTo(couponsTitle.snp.bottom).offset(0)
   make.left.equalTo(0)
   make.width.equalToSuperview()
   make.leading.trailing.bottom.equalToSuperview()
}
...

Thank you!!!

2

There are 2 best solutions below

5
Muhammad Umar On

You can start with adding the content top of UICollectionView as a header and rest as cells of UICollectionView

An example is as follow

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    let headerHeight: CGFloat = 300.0
    let cellHeight: CGFloat = 50.0
    var data = []

    lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.showsVerticalScrollIndicator = false
        return collectionView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(collectionView)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        collectionView.register(DataCell.self, forCellWithReuseIdentifier: "DataCell")

        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    }

    // MARK: - UICollectionViewDataSource

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 2 
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return section == 0 ? 1 : data.length
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if indexPath.section == 0 {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
// do some stuff here
            return cell
        } else {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DataCell", for: indexPath) as! DataCell
// Do some stuff here in your cell
            return cell
        }
    }

    // MARK: - UICollectionViewDelegateFlowLayout

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

       // Calculate dynamic height
        let dynamicHeaderHeight = xxx;
        return CGSize(width: collectionView.bounds.width, height: indexPath.section == 0 ? dynamicHeaderHeight : cellHeight)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return section == 0 ? CGSize(width: collectionView.bounds.width, height: headerHeight) : CGSize.zero
    }
}
0
devPau On

I'd add the restaurant information (logo, name, ...) inside a UICollectionViewHeader so everything scrolls smoothly, as suggested by @clawesome and @Muhammad

Following your approach and if we try to achieve the desired effect without using a header to include the restaurant information, I created a very simple project to simulate the behaviour.

Instead of constraining the collection view at the bottom of the restaurant information, you could pin it to the top, and update the contentInsets and add the necessary padding. Like this you achieve the same effect as it was starting just under the restaurant information.

Then you could implement func scrollViewDidScroll(_ scrollView: UIScrollView) and update the constraints of the restaurant information according to the amount the user has scrolled.

 private func configure() {
    view.backgroundColor = .systemBackground
    collectionView = UICollectionView(frame: .zero, collectionViewLayout: addLayout())
    collectionView.dataSource = self
    collectionView.translatesAutoresizingMaskIntoConstraints = false
    collectionView.delegate = self
    collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellReuseIdentifier)
    
    restaurantView = UIView()
    restaurantView.translatesAutoresizingMaskIntoConstraints = false
    restaurantView.backgroundColor = .systemGreen
    view.addSubview(collectionView)
    view.addSubview(restaurantView)
    
    restaurantViewAnchorConstraint = restaurantView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0)
    
    NSLayoutConstraint.activate([
        restaurantViewAnchorConstraint,
        restaurantView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        restaurantView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        restaurantView.heightAnchor.constraint(equalToConstant: 200),
        
        collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        
    ])
    
    collectionView.contentInset.top = 200
    collectionView.verticalScrollIndicatorInsets.top = 200
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let offsetY = collectionView.contentOffset.y
    restaurantViewAnchorConstraint.constant = -(200 + offsetY)
}

200 is the height of the restaurant information view and the restaurantViewAnchorConstraint is a NSLayoutConstraint declared as a property as:

var restaurantViewAnchorConstraint: NSLayoutConstraint!

Remember to first add the collection view as a subview, not after the restaurant information, if not the collection view will hide all this information.

Video proof