Header view of UICollectionView keeps duplicating on scroll - How to create fixed (non-sticky), single collection view header? - swift

I wanted to implement a view hierarchy like the following so that the ENTIRE view would be scrollable:
UIScrollView
Image View
Collection view
But a lot of people on here have said that it is better to use the header that comes along with the collection view. I've done that but now I have a new problem: as I scroll the collection view, any configurations I've done to the header cell in the viewForSupplementaryElementOfKind function is duplicating (Eg: If I programmatically create a new view in viewForSupplementaryElementOfKind function, this view will keep creating as I scroll)
I kind of get that this is happening because I'm dequeuing the header using dequeueReusableSupplementaryView. But I've tried searching on Apple docs and there are no other codes I can use to instantiate the header view without making it reusable.
Is there any way I can create a view controller as described above without using UICollectionView?
I've tried setting the number of sections to 1 hoping that it would only be reused ONCE but it doesn't work.
Edit: Also tried setting header size using UICollectionViewDelegateFlowLayouta and using UICollectionView instead of UIViewController and UICollectionViewDataSource etc.
func collectionView(_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
switch kind {
case UICollectionView.elementKindSectionHeader:
guard
let headerView = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: "DiscoverHeader",
for: indexPath) as? DiscoverHeader
else {
fatalError("Invalid view type")
}
// Rotating arrow image
headerView.arrowImg.transform = headerView.arrowImg.transform.rotated(by: CGFloat.pi/0.67)
return headerView
default:
assert(false, "Invalid element type")
}
}

Why not UIImageView and UICollectionView inside UIScrollView?
You can definitely create a UIScrollView and add an UIImageView and a UICollectionView in it.
But that won't work as expected. This is because you're embedding a scrollView(UICollectionView) inside another scrollView. Scrolling the collectionView vertically will hamper the scrolling of the outer scrollView.
Solution:
Try giving the height of the header view using UICollectionViewDelegateFlowLayout's method,
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: 100.0) //give the height as per your requirement...
}
Also, set the contentMode of imageView as Aspect Fit / Aspect Fill.

Related

Auto-Layout for CollectionViewCell: Trailing/Leading constraints change cell size

I just can´t get familar with auto layout in Xcode. When I set trailing and leading constrains for Header Stack View, Bottom Stack View or the image between them, then the size (height/width) of my cell changes. Why does it happen and how can I avoid it? I think something isn´t right with the kind I build layouts. I am very grateful for any help.
I set the cell size with this code:
extension MainVC: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = view.frame.size.height
let width = view.frame.size.width
return CGSize(width: width * 0.42, height: height * 0.3)
}
}
My cell layout:
This is how the app looks when I use constrains for the Bottom Stack View.
This is without constrains for the Bottom Stack View (correct cell size).
Cells can now self-size with auto layout and ignore what you return in collectionView(_:layout:sizeForItemAt:). If you want to use collectionView(_:layout:sizeForItemAt:), change Estimate size to None in the storyboard.
https://stackoverflow.com/a/58369142/14351818

Vertical and Horizontal Scrolling When Embedding a Table View in a Collection View that has a Collection View Header and Horizontal Flow Layout

I am trying to implement an application that allows a user to swipe horizontally between collection view cells while also being able to scroll vertically to see the entire content of a particular cell.
I want all the collection view cells to have a table view embedded within them.
The problem I am running into as of now is that my horizontal scrolling works as I have set the collection view flow layout to horizontal. I know that collection view flow layouts can only support one direction. Because of this, I tried to implement the following solution.
As of now within my project, I have a view controller with a scroll view inside. Embedded within the scroll view, is a collection view. This collection view has its own header implemented via dequeueReusableSupplementaryView.
I know that constraints can often be an issue preventing vertical scrolling to take place so here is a picture of my constraints:
Additionally, here is some code that I have used to implement this system:
Determining the scroll view content size
override func viewDidLayoutSubviews() {
super.viewWillLayoutSubviews()
scrollView.contentSize.height = collectionView.frame.size.height
scrollView.contentSize.width = self.view.frame.size.width
}
Setting up the collection view
func setupCollectionView() {
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UINib(nibName: "TableViewHolderCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "tableViewHolderCollectionViewCell")
collectionView.register(UINib(nibName: "CustomCollectionViewHeaderView", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "CustomCollectionViewHeaderView")
collectionView.delaysContentTouches = true
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.bounces = false
collectionView.isPagingEnabled = true
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.sectionHeadersPinToVisibleBounds = true
layout.sectionInset = UIEdgeInsets(top: 0, left: -self.view.frame.size.width, bottom: 0, right: 0)
collectionView.setCollectionViewLayout(layout, animated: false)
}
Setting up the collection view data
extension TasksAndScheduleViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 8
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.view.frame.size.width, height: self.view.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tableViewHolderCollectionViewCell", for: indexPath) as! TableViewHolderCollectionViewCell
cell.backgroundColor = colorArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 343)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
//setting up header view
return headerView
}
}
After trying to debug, I realized that the collection view vertical scroll might have been overriding the vertical scroll of the scroll view I had added to my view controller. In order to solve this, I created a custom class (as seen below) which my collection view implemented. To my knowledge, this was successful at disabling the vertical scroll for the collection view but it was not successful in enabling the other scroll view's vertical scroll.
class CollectionViewVerticalScroll: UICollectionView {
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
let direction = panGestureRecognizer.direction(in: self)
if direction.contains(.Down) || direction.contains(.Up) {
return false
}
return true
}
}
My desired goal is to have the ability to swipe horizontally between these collection view cells that have table views while also being able to vertically scroll the collection view cell and the contents of the table view embedded inside of it. Ideally, the vertical scroll should allow me to scroll the table view cells while also moving the entire view upwards. The closest example to what I am trying to implement that I could find online is twitter's search page. The only difference is that my application has a collection header view and no navigation bar. I have attached a picture below:
I would appreciate any help. Please do let me know if you have any questions or if something doesn't make sense to you.

How to align collectionview cells right next to each other?

I am creating a profile page screen for my application. The screen displays all the user's recent posts. I used storyboard to create two UICollectionViewCells in a UICollectionView, one that displays your profile info and the other that displays your posts. See this for how I designed it in storyboard: https://i.stack.imgur.com/6TzwK.png
When I run the application, I get the following result: https://i.stack.imgur.com/XmvY3.jpg
However, I desire the cells placed in a way that it looks like the following: https://i.stack.imgur.com/nWQ14.jpg
How do I force the cells the align so it looks like the image above? Thanks!
Found an answer to the question. Implement UICollectionViewDelegateFlowLayout delegate to your class. Then, include the following method into the class:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width // get the width of the screen
let scale = (width / 3) // get the width (and height) of each cell
if indexPath.row != 0 { // check to see if the cell is the profile header
return CGSize(width: scale, height: scale) // if not, then return the cell size
}
return CGSize(width: width, height: ((238/414) * width)) // if it is the profile header, return the size for it.
}
Make sure "Min Spacing" for the UICollectionView's storyboard setting to 0,0. The cells will then align into a grid view.

CollectionView: HeaderView Bounce

pretty new to CollectionView so hope to have a direction here.
I have a CollectionView with an header enabled. All works fine but I want the HeaderView stay still while scrolling the main section.
Question:
Is that possible in iOS given a collectionview? Or I have to create a dedicated still view for the header?
I set the below in the UICollectionViewFlowLayout
if let layout: UICollectionViewFlowLayout = self.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .vertical
layout.sectionHeadersPinToVisibleBounds = true
}
which makes the header behave as below:
When scrolling down the main collection, the header goes up until it's lower margin reaches the top of the screen, then bunches in the middle of the sceen
When going up the header follows the main collection and become hided
I am pretty sure I am not the only one which has had this issue. Maybe I am doing something wrong here?
layout.sectionHeadersPinToVisibleBounds = true
that makes your header:
stick to the cells, when you scroll down
begin to hide after bottom of cells reaches bottom of the header
I see that you want the header to be still at all times. Of course, as you said, you could add a UIView on top of the collectionView, which would do the job. A different solution may be this:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath)
headerContainer.backgroundColor = .yellow
headerContainer.frame = header.frame
header.layer.masksToBounds = false // that's important
header.addSubview(self.headerContainer)
return header
}
So basically, we're adding a subview inside our header (header needs to be transparent then). Now, we're using the fact that UICollectionView is scrollable:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
if offset < 0 {
headerContainer.transform = CGAffineTransform(translationX: 0, y: offset)
}
}
So if we are scrolling down by offset value, we transform the container by adding offset value to its Y position.
Unfortunately, I haven't come up with the case of scrolling up (it still begins to hide after some time) - I guess it's doable.
I based on this topic:
TableView header bouncing
I think I'd rather make a UIView outside of UICollectionView anyway.

UICollectionView sticky header not working iOS 10

I am having an issue with a sticky header in my collection view not working in iOS 10. I have a collectionView that has a header in the second section only - implemented by setting the size in the first section as CGSize.zero and the size in the second section as the appropriate size:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0 {
return CGSize.zero
}
return CGSize(width: collectionView.width, height: 80.0)
}
I make the sticky header sticky with these lines in viewDidLoad:
theCollectionView.delegate = self
theCollectionView.dataSource = self
if let flowLayout = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.sectionHeadersPinToVisibleBounds = true
}
Everything works great in iOS 11. When you scroll the collection view up, the header sticks to the top.
However, in iOS 10, this does not work. The header sticks - but not to the top of the screen - it sticks to its initial location and the collection view cells in the second section can be seen scrolling underneath it. There is a gap at the top of the screen. I've attached two screenshots illustrating my problem. Any help would be much appreciated!
Fixed by changing the top auto layout constraint in my storyboard. It was set from the Collection View top to the Safe Area top. Changed from the Collection View top to the Superview top.