I'm having trouble with adding a footer in UICollectionViewCompositionalLayout on top of the cells. Specifically I want to have an interactive control in my footer (in my case a UIPageControl). This is an example layout,
func generateLayout() -> UICollectionViewCompositionalLayout {
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)))
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .absolute(213)
)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
let section = NSCollectionLayoutSection(group: group)
section.boundarySupplementaryItems = [footer()]
return UICollectionViewCompositionalLayout(section: section)
}
The footer is configured like,
private func footer() -> NSCollectionLayoutBoundarySupplementaryItem {
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(20))
let item = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: size,
elementKind: "footer-kind",
alignment: .bottom,
absoluteOffset: .init(x: 0, y: -30)
)
item.zIndex = 1000
return item
}
Lets say I have a button in my Footer that I want to be tappable. I have changed the zIndex of the footer so it is higher than other views in my section.
class FooterCell: UICollectionReusableView {
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
layer.zPosition = CGFloat(layoutAttributes.zIndex)
}
}
The result of the snippets above is that the button is visible on top of the cells, but not tappable. After some inspections in the view debugger it is clear that the supplementary view is added to the view hierarchy before the cells and changing the zIndex only moves the layer to the front, not the complete view. Is there someway to change the order on how the views are added in the compositional layout? Or is there any other way to bring the footer view above the cells?
Related
Could you help me with my problems please.
I have collection view with different type of section:
Scrolling horizontally,
Tabs
Tab content.
I made tab as section and when user press tab I just call collectionView.scrollToItem(at indexPath + 1) and it is work great.
I have problems with content section. I made is like horizontal carousel and I have to turn off scroll at it.
This is the code of this section.
fileprivate static var tabContantLayout: NSCollectionLayoutSection {
get {
let sectionBackground = NSCollectionLayoutDecorationItem.background(
elementKind: "background")
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .estimated(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
section.decorationItems = [sectionBackground]
return section
}
}
If I change section.orthogonalScrollingBehavior to none all items will be located vertically one by one.
collection view screen
Please, If you know say how I can turn off scroll only in this section? Thanks
Requirement
I am trying to make a CollectionView section that has 1 large item then 4 half-width items below like this:
Each cell will contain labels that support multiline text or variable length with dynamic type support.
Approach
I have written the following compositional layout code which constructs 3 groups. A fullWidthGroup contains a single item that is full width (item 1). A halfWidthGroup will hold 2 items per "row" and a main outer group group which is made up of 1 fullWidthGroup and 2 halfWidthGroups.
private let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, environment) -> NSCollectionLayoutSection? in
let margin: CGFloat = 8
// Items
let fullWidthItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
let fullWidthItem = NSCollectionLayoutItem(layoutSize: fullWidthItemSize)
fullWidthItem.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)
let halfWidthItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(50))
let halfWidthItem = NSCollectionLayoutItem(layoutSize: halfWidthItemSize)
halfWidthItem.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)
// Groups
let fullWidthGroup = NSCollectionLayoutGroup.horizontal(layoutSize: fullWidthItemSize, subitems: [fullWidthItem])
let halfWidthGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: halfWidthItemSize.heightDimension)
let halfWidthGroup = NSCollectionLayoutGroup.horizontal(layoutSize: halfWidthGroupSize, subitems: [halfWidthItem])
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(150))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [fullWidthGroup, halfWidthGroup, halfWidthGroup])
group.interItemSpacing = .fixed(margin)
// Section
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = margin
return section
})
This gives the desired result for basic cells that do not contain any constraints (to self size them).
Problem
As soon as I create a cell that has constraints that can be used to correctly evaluate the size of the cell width of the cells is unexpectedly changed.
Resulting in the following layout.
Expectation
I would expect the last 4 items to take up half the width but instead, they appear to be taking up less space. Item 1 which should be full width with an 8 point margin on each side seems to also be sized incorrectly with ~16 point margin on the trailing edge.
Full Code
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, environment) -> NSCollectionLayoutSection? in
let margin: CGFloat = 8
// Items
let fullWidthItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
let fullWidthItem = NSCollectionLayoutItem(layoutSize: fullWidthItemSize)
fullWidthItem.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)
let halfWidthItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(50))
let halfWidthItem = NSCollectionLayoutItem(layoutSize: halfWidthItemSize)
halfWidthItem.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)
// Groups
let fullWidthGroup = NSCollectionLayoutGroup.horizontal(layoutSize: fullWidthItemSize, subitems: [fullWidthItem])
let halfWidthGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: halfWidthItemSize.heightDimension)
let halfWidthGroup = NSCollectionLayoutGroup.horizontal(layoutSize: halfWidthGroupSize, subitems: [halfWidthItem])
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(150))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [fullWidthGroup, halfWidthGroup, halfWidthGroup])
group.interItemSpacing = .fixed(margin)
// Section
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = margin
return section
})
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: "BasicCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
collectionView.collectionViewLayout = compositionalLayout
collectionView.dataSource = self
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.contentView.backgroundColor = .red
return cell
}
}
The BasicCollectionViewCell is created in a nib and contains a stack view (added to the contentView). The StackView is pinned to all edges with a priority of 1000. Inside the stackview is a label.
I have simple implementation with UICollectionViewCompositionalLayout. There are two sections, every has single row with horizontal scroll and every item at section has 1/4 width of whole container (now it's full screen).
I would like to have paging scroll, but when data source provides 4n+1 elements at the section I can't scroll to access last item.
How to fix it with pure animation for last item like with simple UICollectionViewFlowLayout?
func createCollectionViewLayout() -> UICollectionViewLayout {
UICollectionViewCompositionalLayout { sectionIndex, environment -> NSCollectionLayoutSection? in
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1 / 4), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.4))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 4)
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(44))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPaging
section.boundarySupplementaryItems = [header]
return section
}
}
Video with bug example
After complex research: seems like layout bug, one possible solution is to change programmatically the orthogonalScrollingBehavior depending on the number of items
I have this layout, where the main picture is a UICollectionView, and the thumbnails are in their own UICollectionView.
The design asks for the thumbnails to be right-aligned, so the content in the UICollectionView needs to be pushed to the right. How can I do that? I only need to support iOS 13 and up, so am using UICollectionViewCompositionalLayout.
private func createThumbsLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalHeight(1), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
section.interGroupSpacing = 16
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
return UICollectionViewCompositionalLayout(section: section)
}
I found this:
thumbsCollectionView.semanticContentAttribute = .forceRightToLeft
Which works, if I then also reverse the images and reverse all IndexPath math to deal with selection and all that. It's not ideal so would love a better solution. Surely there must be some kind of alignment property? Same would be useful for a bottom-aligned vertical collectionview?
Is it possible to use UICollectionViewCompositionalLayout to create a horizontally scrolling collection view that contains multiple sections?
I'm looking to create a layout similar to the emoji keyboard that has multiple sections, each appended to the end of the previous, in one horizontally scrolling "group", with a header stretching across each section.
With the following layout each section is stacked vertically and each scrolls horizontally:
UICollectionViewCompositionalLayout {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let leadingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
heightDimension: .fractionalHeight(1.0)))
leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
let trailingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.3)))
trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
let trailingGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3),
heightDimension: .fractionalHeight(1.0)),
subitem: trailingItem, count: 2)
let containerGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.85),
heightDimension: .fractionalHeight(0.4)),
subitems: [leadingItem, trailingGroup])
let section = NSCollectionLayoutSection(group: containerGroup)
section.orthogonalScrollingBehavior = .continuous
return section
}
You can set the primary scroll direction for the collectionViewLayout to horizontal with the following configuration:
let config = UICollectionViewCompositionalLayoutConfiguration()
config.scrollDirection = .horizontal
let sectionProvider = ...(your section provider here)
let cvLayout = UICollectionViewCompositionalLayout(sectionProvider: sectionProvider, configuration: config)
Within the sectionProvider you can create a header pinned to the top of each section as follows:
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(40))
let headerSupplementary = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerSize,
elementKind: UICollectionView.elementKindSectionHeader,
alignment: .topLeading)
section.boundarySupplementaryItems = [headerSupplementary]
This setup should achieve the scrolling behaviour you desire. Within each section you could use the following group to achieve the same grid layout as the emoji keyboard:
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: 5)