UICollectionView header view like table view header (not the section header) - swift

Is it possible to create UICollectionView header view like UITableView headerView? I mean header view for whole collection view, not the repeated one for each section. Like the picture1 is I want, picture which is now i have done.

I have the solution now. Add a subview in the collectionView and make the collectionView contentInset below the topImageView like below.
topImageView.frame = CGRect(x: 5*SCREEN_SCALE, y: -125*SCREEN_SCALE, width: 285*SCREEN_SCALE, height: 120*SCREEN_SCALE)
collectionView.addSubview(topImageView)
collectionView.contentInset = UIEdgeInsets(top: 130*SCREEN_SCALE, left: 0, bottom: 0, right: 0)

There's a workaround that I use when wanting to achieve this. When setting the header size, I check section number before setting it. If it's the first section, I set the height accordingly - otherwise I set the height to 0 so it isn't visible
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0 {
return CGSize(width: view.frame.width, height: 35)
} else {
return CGSize(width: view.frame.width, height: 0)
}
}

In swift like below
Register Header View
collectionView.registerClass(HeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerView")
In UICollectionViewDelegate
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
if section == 0 {
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "headerView", forIndexPath: indexPath)
headerView.frame.size.height = 100
return headerView }
else {
return nil
}
}
Important is that you are supply the flow layout with the header size
flowLayout.headerReferenceSize = CGSize(width: self.collectionView.frame.width, height: 100)
Otherwise the delegate method will not get called

Related

How to add a UIPageController and UICollectionViewController to a UIViewController programmatically

I already have a UIViewController made programmatically, and would like at the bottom to have a collection view with 4 cells, but I want to be able to swipe through different pages of the collection view to see different cells. I'm not sure where to begin with this wether to enable paging on the collection view and how that would work with setting up the cells, or to create a page controller and adding the collection view to that? There are a couple of ways that I have seen online already, but that don't really fit my needs.
I would like something as such:
Let me know if I can provide you with more information. I just created a basic page controller but am not sure how to achieve what I'm looking for.
Edit: I created a collection view and added the constraints to get the layout I want; however I'm not sure how to make it swipe like a page.
Here's the code for the collection view:
let friendsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.register(FriendsCell.self, forCellWithReuseIdentifier: "cell")
cv.backgroundColor = UIColor.blue
cv.layer.cornerRadius = 10
return cv
}()
view.addSubview(friendsCollectionView)
friendsCollectionView.anchor(top: separatorView.bottomAnchor, left: nil, bottom: nil, right: nil, paddingTop: 50, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 250, height: 250)
friendsCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 75, height: 75)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if section == 0 {
return UIEdgeInsets(top: 85, left: 10, bottom: 0, right: 0)
}
if section == 1 {
return UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
}
if section == 2 {
return UIEdgeInsets(top: 85, left: 5, bottom: 0, right: 0)
}
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 { return 1 }
if section == 1 { return 2 }
if section == 2 { return 1 }
return 0
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
All you need would be a UICollectionViewController or as I am assuming you have, a UIViewController that conforms to the collectionView protocols. Your UIViewController with one UICollectionView is a good starting point. However the cell you insert needs to be different. First of all, the collectionView within the ViewController needs to have collectionView?.isPagingEnabled = true and its layouts scrollDirection set to .horizontal. Having done that you need to create your cells.
The cells you implement should be a subclass of UICollectionViewController and UICollectionViewDelegateFlowLayout. This collectionView's layout needs to have scrollDirection set to .vertical although I believe you are not scrolling in this view anyways. The number of cells in this collectionView will be 4. You then dequeue your cell of which you want to have 4 (the one with the head in white on blue).
The UICollectionViewController needs to then be dequeued as the cell for your first collectionView. Basically:
Your main ViewController has a collectionView that scrolls horizontally. This collectionView has a UICollectionViewController as cells and scrolls vertically.
It may sound very complicated but I have done it and it works smoothly. Let me know if you have any questions.

NSCollectionView viewForSupplementaryElementOfKind not being called

In my NSCollectionViewItem I set up the code below.
func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
let view = collectionView.makeSupplementaryView(ofKind: .sectionHeader, withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sectionHeader"), for: indexPath)
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.red.cgColor
return view
}
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize {
return NSSize(width: self.frame.size.width, height: 60)
}
However, when I place a print() in the viewForSupplementaryElementOfKind function it never is executed. Also, no red header appears.
This is how I set up the NSCollectionView
public let collectionView: NSCollectionView = {
let v = NSCollectionView(frame: NSRect.zero)
v.wantsLayer = true
v.layer!.backgroundColor = NSColor.clear.cgColor
v.isSelectable = true
v.backgroundColors = [NSColor.clear]
v.allowsEmptySelection = false
v.collectionViewLayout = {
let l = NSCollectionViewFlowLayout()
l.minimumInteritemSpacing = 1
l.minimumLineSpacing = 1
l.scrollDirection = NSCollectionView.ScrollDirection.vertical
l.sectionInset = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return l
}()
return v
}()
What am I doing wrong?
The supplemental views (header and footer) are not collection view items. You need to implement those two methods on the NSCollectionViewDelgateFlowLayout object, not on your NSCollectionViewItem class.
See https://developer.apple.com/documentation/appkit/nscollectionviewdelegateflowlayout
You can also set a default for the entire collection view:
l.headerReferenceSize = NSSize(width: 0, height: 50)
l.footerReferenceSize = NSSize(width: 0, height: 50)
When using NSCollectionViewFlowLayout, the default size of the header and footer is NSSize.zero, so the method to make the supplemental view is not called unless one of these techniques is used.

Left Align UICollectionViewCells only works when cells are not correct height, and stops working when scrolled

I am trying to left align the cells in a UICollectionView, and I have used a GitHub repo to try and obtain this, link here:
https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout
I set up the CollectionView and its cells like so:
lazy var filterCollectionView: UICollectionView = {
let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .center)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.register(filterCell.self, forCellWithReuseIdentifier: "filterCellId")
cv.backgroundColor = .white
cv.isScrollEnabled = true
cv.dataSource = self
cv.delegate = self
cv.showsHorizontalScrollIndicator = false
return cv
}()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return genrArray.count
}
let genArray = ["Test123", "LongTextTest", "Short", "S", "#LongLongLongLong"]
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "filterCellId", for: indexPath) as! filterCell
cell.genText.text = genArray[indexPath.row]
cell.genText.sizeToFit()
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: cell.genText.frame.width + 25, height: 24)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let label = UILabel(frame: .zero)
label.text = genArray[indexPath.row]
label.frame = CGRect(x: 0, y: 0, width: label.intrinsicContentSize.width, height: 0)
return CGSize(width: label.frame.width + 25, height: 18)
}
This did nothing to the cells, so I added these lines of code into ViewDidLoad():
let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .center)
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
self.filterCollectionView.collectionViewLayout = layout
The line that actually made a change in the CollectionView was layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize. But this still did not achieve the desired effect. This squished my cells and made them have a height of much smaller than 18 (the size declared in sizeForItem. When loaded up, the collectionView looks like this:
But when I scroll through the collectionView, it reverts back to normal as if the left alignment layout is not even there. It then looks like this:
The cells are the correct shape in that image, but the alignment/layout/spacing is off.
What am I doing wrong?

UICollectionView equal cell spacing

I am displaying several images in a collection view. They are all the same orientation (landscape) except for one, which is portrait (see image below):
I am trying to make the portrait image more centered, so that everything is evenly spaced. Any ideas on how to do this?
PhotoGalleryViewController.swift:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PhotoGalleryCell
let image = UIImage(named: images[indexPath.row])
cell.imageView.image = image
cell.captionLabel.text = captions[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var size = CGSize()
if (indexPath.row == 3)
{
size = CGSize(width: 450, height: 600)
}
else {
size = CGSize(width: 940, height: 600)
}
return size
}
My suggestion would be not altering the cell size based on the fact that a portrait image will be in item 3.
Instead update the layout of your cell so that whatever image it gets assigned to it's image view, it will centre the image in itself and centralise the label underneath the image.
In ViewDidLoad method put this code :
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionview.collectionViewLayout = layout
Also add this into cellForRow Methods:
cell.imageview.contentmode = .aspectfit
Hope it helps to resolve your issue.

Collections View with drop down menu

I wish to create a drop down menu which will only appear at the spot where the blank empty space is when the 'seasonal' tab is selected and remain hidden when the 'Latest' tab is selected.
How do I achieve that?
viewController image:
Do I have to use UISearchDelegeate for this?
Edit: how to make the headerView of the collectionsView respond to the segmented control buttons up top: "Latest and Seasonal"? For eg. when "Latest" is selected, the header view will resize to width: 0, height: 0 and when "Seasonal" is selected, the header will be resized to width: 320 and height: 50?
Here's an example, but so far my code is wrong. I'm not sure how to return CGSize.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if segmentedControlValue.selectedSegmentIndex == 1 {
let size = CGSize (width: 0, height: 50)
}
if segmentedControlValue.selectedSegmentIndex == 0 {
let size = CGSize (width: 0, height: 0)
}
}
#IBAction func selectionButtons(_ sender: UISegmentedControl) {
collectionView?.reloadData()
}
here's the new code I just added:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let selectionCell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "selectionCell", for: indexPath)
if toggleState.currentTitle == "View" {
selectionCell.frame = CGRect(x: 0, y: 0, width: 0, height: 100)
}
if toggleState.currentTitle == "NoView" {
selectionCell.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
}
return selectionCell
You can use the collectionView viewForSupplementaryElementOfKind method to handle header with UICollectionElementKindSectionHeader and footer with UICollectionElementKindSectionFooter kind view.
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let view = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "myHeaderView", forIndexPath: indexPath)
// configure header view
if segmentedControlValue.selectedSegmentIndex == 1 {
view.frame = // set frame
}
if segmentedControlValue.selectedSegmentIndex == 0 {
view.frame = // set frame
}
return view
}
So when you call reloadData() method this takes as a part of execution and you can take appropriate action to view inside.