I have a UICollectionView in a container View. It is sized in such a way that I see only one cell at a time. I have disabled vertical scrolling so only horizontal scrolling takes place.
Everything works just peachy where I scroll horizontally across cells. The disadvantage of this scrolling is I can scroll and get to a position where I see half portion of two cells with a gap in between.
What I want to achieve is to show only one cell at a time and never show two half cells. So the scrolling should take me to the next cell one at a time.
Hope this makes sense.
Please let me know if anyone has tried or can help me to achieve the same. Thanks.
You can scroll one cell at a time by setting pagingEnabled = YES. Or for regular scrolling, you can adjust where the deceleration ends by overriding targetContentOffsetForProposedContentOffset:withScrollingVelocity: of UICollectionViewLayout. When using the later method, you would typically do the following:
Determine the nearest index path to proposedContentOffset. If there are no gaps between your cells, you can use [UICollectionView indexPathForItemAtPoint:]. Otherwise, you may have to inspect your layout in some way to determine which index path you want to scroll to.
Determine the frame of the index path you want to scroll to by getting the layout attributes for that index path
Determine the content offset that will position the given frame where you want it.
Make sure your padding between cells is set to zero as well. Paging enabled will stop on multiples of the view bounds.
// Set up flow layout
var flowLayout:UICollectionViewFlowLayout = UICollectionViewFlowLayout();
flowLayout.minimumInteritemSpacing = 0
flowLayout.minimumLineSpacing = 0
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: flowLayout)
collectionView!.pagingEnabled = true
Check out my answer over here: ScrollView or CollectionView?
It sounds like you have paging enable but the page size doesn't match you cell size plus the insets left and right of the cell.
Swift 5:
collectionView.isPagingEnabled = true
Related
Let me preface by saying that I've spent probably 100 hours over the past few months searching Google and StackOverflow trying to answer this question. I've followed many tutorials and have not been able to solve my problem. Every one is a major disappointment and is disheartening.
Expectation
Reality
Problem
I have a UITableView that contains a UIView. The UIView has auto layout constraints to pin it 15pt off the edges of the cell, and then I give that a shadow and rounded corners. Inside that UIView, I have a few labels, then a UICollectionView, and a stack view.
My UICollectionView keeps having a height of 0 and I can't figure out how to make it have intrinsic size. The only way I can seem to make it display properly is to force a specific height to it via auto layout inside the storyboard (which is what I did to take this screenshot). Unfortunately, forcing a height like this is not realistic for 2 reasons:
The height varies based on the width of the device. Since the images are squares, the height is loosely (but not exactly) half the width of the device.
If there are no images, the height of the collection view should be 0.
It really seems like it should be possible to use the intrinsic content size of the flow layout here.
Storyboard
Constraints
I've got about 15pt of space between each of the labels, collection view, and stack view (so, the vertical space). The collection view prototype cell has an image view in it. The image view has top, bottom, trailing and leading space all set to 0 to the collection view cell, and also has a 1:1 ratio.
Collection View Layout
To achieve the layout of one large image and 4 small images, I'm using SNCollectionViewLayout.
Data Source / Delegate
In my controller, in viewDidLoad, I've set the estimatedRowHeight of the UITableView to 400, and I've set the rowHeight to UITableView.automaticDimension.
In the tableView cellForRowAt, I dequeue a reusable custom UITableViewCell class, set various outlets on the table view cell, and call reloadData() on the collection view for the table view cell. In the storyboard, I've set the table view cell as the collection view's data source and delegate.
In the table view cell's awakeFromNib function, I've got it setting the corner radius, shadow, and using the following code for the flow layout on the collection view (which you can see in their example):
let snCollectionViewLayout = SNCollectionViewLayout()
snCollectionViewLayout.fixedDivisionCount = 4
snCollectionViewLayout.delegate = self
snCollectionViewLayout.itemSpacing = 10
collectionView.collectionViewLayout = snCollectionViewLayout
I've also implemented the optional SNCollectionViewLayoutDelegate protocol by writing the following function:
func scaleForItem(inCollectionView collectionView: UICollectionView, withLayout layout: UICollectionViewLayout, atIndexPath indexPath: IndexPath) -> UInt {
if indexPath.row == 0 || collectionView.numberOfItems(inSection: indexPath.section) <= 2 {
return 2
} else {
return 1
}
}
This function is why the first image is twice the height and width as the rest.
Research
In trying to figure this out, I've checked the collectionView.collectionViewLayout.collectionViewContentSize inside the table view cell's cellForRowAt and willDisplay methods. In either case, it's (0,0).
I've also tried checking it in the collection view cell's cellForItemAt and willDisplay. Those log lines don't even print unless I add a height constraint to the collection view to force it tall, but then I don't have a good way to bring the height back down to what it should be, and any time I've tried, I got conflicting constraint warnings and really wonky stretched views (such as the stack view being totally squished).
In testing, I've forced a height constraint that's large enough for the collection view to fit, and when that happens, it renders correctly and the collection view content size is correct, so I know it's capable of keeping track (which can be seen here).
Does anyone have any insight into this? I'd be happy to provide more code if you have any specifics on things to look into. I tried to give the full picture, while keeping this as short as possible (I know it's long).
I feel like the issue has something to do with the collection view being inside a table view cell.
Thanks in advance.
Keep a height constraint connection in your table view cell.
#IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
And update the height constraint value programatically as per height calculations
self. collectionViewHeightConstraint.constant = 100 // set it to 0 if nothing to display
I have a UICollectionView, called parentCollectionView, and it scrolls horizontally. Inside each parentCollectionView's cell, is another UICollectionView, called childCollectionView, which scrolls vertically. In addition, parentCollectionView is set to paging (pagingEnabled = true).
Thus, you can scroll left/right to the next parentCollectionView, and you can scroll up/down in the childCollectionView.
There is an issue, however, regarding conflicting gestures. On the first horizontal swipe, the childCollectionView scrolling gesture is recognized. It fails to swipe horizontally, and doesnt scroll down in the child collectionview. But on the second swipe horizontally, it will page to the next horizontal cell as so.
How do I prevent the child collectionview from registering the first horizontal swipe? When I set isScrollEnabled = false on the childCollectionView, it fixes the issue, but I still would like to scroll down in my child collectionViews.
Is this something that is accomplished via gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:? Ive tried subclassing the scrollView the top answer in this stack overflow question, but was unsuccessful.
Cheers
It appears as the problem was that the childCollectionView's content size was larger than the frame. Thus, the childCollectionView was given the impression that it was scrolling right within the childCV. I made sure the content size was identical to the CV frames.
//In my case, the childCV's width is exactly UIScreen.main.bounds.width - 30
let scrollSize = CGSize(width: UIScreen.main.bounds.width - 30, height: self.listCollectionView.contentSize.height)
listCollectionView.contentSize = scrollSize
I need to slightly change the way FlowLayout draws cells.
I need to set the collection to scroll horizontally and I want to draw cells from left to right, moving to the next line when the width is completely filled and taking into account pagination.
Here some images to clarify my question:
This is the current behaviour of the FlowLayout:
this is what I'd like to obtain:
Some notes:
I need paginationEnabled = YES
I want to fix the rows to a maximum of 2
I have a fixed number of cells too.
Is there a way to achieve this behaviour working with a UICollectionFlowLayout? Or is this the case to create a totally custom UICollectionLayout?
I believe you would have to manually fill the cells checking the indexpath.row and also checking the width of each cell and self.view.frame.size.width
I use a table view which has got the cells with varied set of information and which can not be contained with in the frame of the tableView. So I do need to have a horizontal scrolling capacity in the table view.
Since horizontal scrolling is not possible with tableview,I planned to use a scrollView with horizontal scroll. I added the tableView on top of the scrollView.
The problem I face is irrespective of the content size property of the scrollview, the scrollview never scrolls beyond the contentOffset value X of 256-320 pixels.
I am planning to achieve the effect as it is in the below screen shot
Try two things,
First Set the TableView height as total numberOfRows :
tableView.frame.size.y = numberOfRows * 44;
now disable the tableView Scrolling
and set he scrollView contentSize as per your need;
scollerView.contentSize = numberOfRowsintableView
Surely it will solve your problem
Set proper ContentSize for ScrollView in ViewDidLoad.
scollerView.contentSize = CGSizeMake(Width,Height);
Calculate the Width as Per Requirement And Height (you are using Tableview So You can take Height same as View Height)
Then it will Surely Scroll.
I implemented the entire functionality using code without using interface builder. It works as expected now. I did not know why it did not work when used with interface builder.
I made a UICollectionView with a vertical scroll.
The width of the cell is more than than the screen width, so I created a customFlowLayout based on UICollectionViewFlow layout returning the right calculated content size.
However, this doesn't work. When the width of the cell is less than the screen width it works. Does it mean that we can't have width more than than screen width in vertical scroll?
It is the same for horizontal scroll, but then the height of the CollectionView is limited to screen height.
Is there any way to make it work?
As others have already said, the UICollectionView can only scroll one direction using a flow layout. However you can accomplish this very easily without creating a custom layout or using a third party library.
When you lay your view out in story board, you can put your UICollectionView embedded in a UIScrollView. Have the scrollview set up to scroll horizontally and the UICollectionView to scroll Vertically. Then set the UICollectionView.delaysContentTouchesto true so touches will pass through to the UIScrollView and not think you are trying to scroll the collectionview.
When you set up the UICollectionView, set it's size and the size of the cells to be what you actually want them to be (Wider than the actual screen) and lay them out accordingly.
Now in the containing UIViewController put this code in the view lifecycle
- (void)viewDidLayoutSubviews {
self.myScrollView.contentSize = self.myCollectionView.frame.size;
}
That's literally all you have to do to accomplish what you are describing. Now your scrollview should allow you to scroll horizontally to view your entire cell and your collectionView should scroll vertically through your cells.
Happy programming.
I'm not sure I've understood your problem.
But if you have made a custom layout, make sure you have implemented :
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
and that your layout attributes frame and size are set with correct values for your "large cell" index path.
Also make sure you have implemented :
- (CGSize) collectionViewContentSize;
This method returns the contentSize of the collection View. If contentSize.width > youAppFrame.width you should have horizontal scrolling. Same for height and vertical scrolling.
Also make sure your collectionView allows scrolling and that your layout is prepared correctly using :
- (void)prepareLayout
By the way, for your layout have you overloaded UICollectionViewLayout or UICollectionViewFlowLayout ?
Before you can do that you MUST use a different type of layout. The flow layout represents its items as a list and it spans these items in cells based on the available width.
If you want to have both horizontal and vertical scrolling you need to somehow specify the number of columns for your grid. the FlowLayout doesn't have that. A simple sollution is to make a subclass of UICollectionViewLayout and override collectionViewContentSize to make it retun a width = to the added sum of the cells widths of one row (this is where knowing how many collumns you want is necessary), plus any additional spacing between them. This will work fine if your cells have the same size per column, similar to a grid.
You should embed a UITableView into a UIScrollView.
ScrollView and TableView will have the same height but different widths.
This way UITableView will scroll vertical and UIScrollView will scroll horizontal.
Xcode 11+, Swift 5.
I solved my issue, I prepared video and code