Cannot disable Section bouncing in CollectionView - Compositional Layout - ios

I have a collectionView with compositional layout which scrolls vertically, with each section inside it scrolling horizontally.
I would need to disable bouncing on horizontal scroll, which does not seem to be possible (or maybe I'm doing something wrong?). Disabling vertical bouncing works perfectly.
What I have tried so far:
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.bounces = false
collectionView.alwaysBounceHorizontal = false
collectionView.alwaysBounceVertical = false
collectionView.isDirectionalLockEnabled = true
This does not seem to work for horizontal bouncing - it is still enabled.
My compositional layout is created in this way:
func createCompositionalLayout() {
let layout = UICollectionViewCompositionalLayout { sectionIndex, _ in
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(UIScreen.main.bounds.width), heightDimension: .absolute(UIScreen.main.bounds.height))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [item])
group.interItemSpacing = .fixed(0)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
section.orthogonalScrollingBehavior = .paging
return section
}
collectionView.collectionViewLayout = layout
}
If anyone might know how to solve the issue, any help would be much appreciated!

After spending almost 2 days with the Apple's example class OrthogonalScrollingViewController, it does look weird that the bouncing (on/off) works only for the vertical scrolling.
collectionView.bounces = false
collectionView.alwaysBounceHorizontal = false
collectionView.alwaysBounceVertical = false
This won't work because:
Short answer
Because the collection view's section (compositional layout's section) is not affected by the scroll view at all
Long answer
If you conform to UIScrollViewDelegate (UICollectionViewDelegate has it already, hence no need for that, if you have conformed to the latter), the method doesn't even get called
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
//something should happen
}
and that's because our collection view is vertical -> scroll view direction is vertical and our sections are not affected by it (ever?! really?!)
A discussion that helped me:
UICollectionView CompositionalLayout not calling UIScrollDelegate

Related

UICollectionViewCompositionalLayout with horizontal or orthogonalScrollingBehavior has inconsistent item spacing near edge of screen

I have the following layout for my compositional layout
func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let dayItemSize = NSCollectionLayoutSize(widthDimension: .absolute(74), heightDimension: .fractionalHeight(1))
// 2. Setup media group
let dayItem = NSCollectionLayoutItem(layoutSize: dayItemSize)
let dayGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),heightDimension: .fractionalHeight(1.0))
let dayGroup = NSCollectionLayoutGroup.horizontal(layoutSize: dayGroupSize, subitems: [dayItem])
let interitemSpacing = CGFloat(10)
dayGroup.interItemSpacing = .fixed(interitemSpacing)
let section = NSCollectionLayoutSection(group: dayGroup)
section.orthogonalScrollingBehavior = .continuous
return section
}
return layout
}
Then there are 30 "day" items in my collectionView.
fileprivate func makeDataSource() -> DataSource {
let dataSource = DataSource(
collectionView: collectionView,
cellProvider: { (collectionView, indexPath, YearMonthDay) ->
UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TimelineDayCell.identifier, for: indexPath) as? TimelineDayCell
cell?.configure(with: YearMonthDay)
cell?.dayLabel.text = String(indexPath.section)+","+String(indexPath.row)
return cell
})
return dataSource
}
func configureDataSource() {
self.collectionView!.register(TimelineDayCell.nib, forCellWithReuseIdentifier: TimelineDayCell.identifier)
}
func applySnapshot(animatingDifferences: Bool = true) {
// 2
var snapshot = DataSourceSnapshot()
snapshot.appendSections([.main])
snapshot.appendItems(days) # 30 of these
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
My UIcollectionViewController covers the entire width of the screen. My cells in the screen look like this:
The text on the cells is their index path. You can see that between 0-4, 5-9 the spacing is uniform, then on the edges 4-5 and 9-10, its different. I believe this is because its the "edge" between screens. But what can I do about it?
I've also tried using no orthogonal scroll behavior and just setting the layout config's scroll direction
let config = UICollectionViewCompositionalLayoutConfiguration()
config.scrollDirection = .horizontal
layout.configuration = config
return layout
It seems to achieve the same thing. Any suggestions here?
EDIT: It seems that this behavior is based on the sizing of the group. For instance when I use let dayGroup = NSCollectionLayoutGroup.horizontal(layoutSize: dayGroupSize, subitem: dayItem, count: 30), it makes all of the items lie next to each other like so:
Now there are 30 separate items. Notice how it has auto-spaced the distance between them so that it can fit 30 into 1 screen width. Thus it is still trying to force all of the items in 1 group into a single screen which is the behavior I don't want. I want the group to be the width of the entire content size and to be scrollable.
Ok I solved it. It has to do with the group sizing. I had let dayGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),heightDimension: .fractionalHeight(1.0))
The inter-item spacing is between the group items. And the group size was the size of my window. So I need to make the group size the size of the entire content (bigger than the window for instance.

UICollectionReusableView Height can't resize when one of its UITextViews grows by more than one line

I am using a UICompositionalLayout to set the layout of my collection view. For the header of each section I am using a UICollectionReusableView, which contains three buttons at the top and two textviews below them. The UI is set up in the following way
The user should be able to write some notes in both uitextviews and those views should resize their height according to their intrinsic heights, I have disabled scrolling for both textfields but whenever the user writes more than the width of the collection view, that part of the text disappears instead of the textview to resize.
The following is the code I am using for the collection view compositional layout
static func setupCollectionViewCompositionalLayout() -> UICollectionViewCompositionalLayout{
let compositionalLayout = UICollectionViewCompositionalLayout { (sectionNumber, env) -> NSCollectionLayoutSection? in
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
item.contentInsets.trailing = 16
item.contentInsets.leading = 16
let group = NSCollectionLayoutGroup.vertical(layoutSize: .init(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)), subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(200))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: TestTableCollectonViewTopHeaderCollectionReusableView.reuseIdentifier, alignment: .top)
section.boundarySupplementaryItems = [header]
return section
}
return compositionalLayout
}
The reusable view comes from a xib with very simple auto layout constraints, the buttons have fixed width and heights, all subviews have 8 points from leading and trailing and there is a spacing of 16 between subviews.
According to this constraints the layout should resize according to the content but the text keeps disappearing and the header doesn't resize as it should be, even though I'm setting the height to be calculated according to the auto layout of the view.
If someone could point me in the right way, it would be really appreciated
Thanks in advance

Preventing UICollectionView from scrolling outside of contentOffset

I created a collectionView with 4 cells, the cell frames are equal to the entire window frame so that I can scroll through them as they were a PageViewController.
lazy var collectionView : PagesCollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let collectionView = PagesCollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isPagingEnabled = true
collectionView.alwaysBounceHorizontal = false
return collectionView
}()
The goal I want to accomplish is that when I am in the first cell and scroll on the left (contentOffset negative) the collectionView scrolling stops, and when I am on the last cell the collectionView stops scrolling in the right direction, I want to see the cell still.
I tried different procedures:
This one blocked the scrollView only after having scrolled it in the first place, never re-enabling the scrolling in the other direction:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(scrollView.contentOffset.x == 0){
scrollView.isScrollEnabled = false
}
}
As suggested on other StackOverflow topics:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if(scrollView.contentOffset.x == 0){
collectionView.isScrollEnabled = false
}
collectionView.isScrollEnabled = true
}
But this just blocks the scrolling in both directions.
I actually don't know how to solve this.
I actually figured out how to solve this problem, it's really easy.
In my viewDidLoad method where I set the collectionView I set bounces equal to false:
collectionView.bounces = false

UICollectionViewCompositionalLayout setting section.orthogonalScrollingBehavior page programmatically?

I am implementing a UICollectionViewCompositionalLayout and using
section.orthogonalScrollingBehavior = .groupPagingCentered
to scroll a section horizontally.
This lets a user scroll from page to page in a section horizontally.
I use
section.visibleItemsInvalidationHandler = {...}
to get the page a user scrolls to.
How can I scroll to a page in this section programmatically?
I actually got it working. This code works XCode 12 Beta 2 / Target iOS14 / Swift 5.1.
In this piece of code, pay special attention to spacing, insets and orthogonalScrollingBehavior !!!
fileprivate func configureCollectionViewLayout() {
func createLayout() -> UICollectionViewLayout {
self.collectionView.canCancelContentTouches = false
let layout = UICollectionViewCompositionalLayout {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
guard let sectionKind = Section(rawValue: sectionIndex) else { return nil }
let spacing = CGFloat(10)
switch sectionKind {
case .someOtherSection:
// configure the layout for this section
case .yetAnotherSection:
// configure the layout for this section
case .sectionWithOrthogonalScrollingBehaviour:
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(1.0*9.0/16.0))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
group.interItemSpacing = .fixed(0) // should (probably) be 0 otherwise there is an offset when scrollingToIndex
section.interGroupSpacing = 0 // should (probably) be 0 otherwise there is an offset when scrollingToIndex
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 0) // leading and trailing should (certainly) be 0 otherwise the pages are offsetted in relation to the view
section.orthogonalScrollingBehavior = .continuous // should be continous, otherwise it won't work
return section
}
}
return layout
}
collectionView.collectionViewLayout = createLayout()
}
To scroll programatically, I implemented a segmented control. When user taps on a segment, the orthogonally scrolling section scrolls automatically to the cell corresponding with the selected segment.
#IBAction func segmentedControlValueChanged(_ sender: UISegmentedControl) {
// determine which Item belongs to the selected segment. In my case Items in a Section are represented by an enum called 'Item'
let dataSourceIndexPath = self.dataSource.indexPath(for: .itemCase)
let pressentationIndexPath = self.collectionView.presentationIndexPath(forDataSourceIndexPath: dataSourceIndexPath)! // This step is necessary, otherwise the next line does not have have wanted behaviour (I'm force unwrapping here to simplify. Don't do that in your final code)
self.collectionView.scrollToItem(at: pressentationIndexPath, at: .centeredHorizontally, animated: true) // make sure you use .centeredHorizontally
}
I'm not sure this is how Apple intended this to be used. It might brake again in next Xcode beta versions.
Sidenote: In case you were wondering why I need/want a segmented
control to scroll a collection view: It's because the content of the
cells in the orthogonally scrolling section need to consume the
horizontal pan gestures (the cells are all charts that you can move your finger
along and then display the chart value at the touched location).
Yes... I know this can also be done in other ways, but I wanted to use
the compositional layout because it fits together nicely with the
other sections, and I wanted to keep this sweet built in animation of
the orthogonalscrollbehavior (even though this is now partially lost because orthogonalScrollingBehavior needs to be .continues for it to work) and I just wanted to see if it can be done :)

How these UITableView section index lays over the cells

I am trying to get UITbaleView section index same as in screenshot, the section index is without any background and is laying over the UITableView cells, or in other words there is no UITableView margin on right:
But my UITableView have some about 15-20px contentView margin on right.
I tried self.tableView.layoutMargins = .zero, self.tableView.separatorInset = .zero but it didn't help.
That was actually easy done with following 2 lines:
tableView.sectionIndexTrackingBackgroundColor = .clear
tableView.sectionIndexBackgroundColor = .clear

Resources