I have setup scrollView delegate and implemented the logic into scrollViewDidScroll protocol method as follows, it never goes into the second condition. I wonder what I am missing?
-(void)scrollViewDidScroll: (UIScrollView*)scrollView
{
float scrollViewHeight = scrollView.frame.size.height;
float scrollOffset = scrollView.contentOffset.y;
if (scrollOffset == 0)
{
// then we are at the top
}
else if (scrollOffset + scrollViewHeight == 700.0)
{
// never calls here
[self loadSecondFromURL];
}
}
I think scrollOffset is zero so, its entering only first loop. I changed the code like this and it works fine for me
if (scrollOffset == 0)
{
// then we are at the top
}
if (scrollOffset + scrollViewHeight == 700.0)
{
// never calls here
[self loadSecondFromURL];
}
Related
I want to make the uicollectionview’s first visable headerview’s uilabel’s color is yellow and the other visable headerview is black when sliding the uicollectionview.
How can I achieve this?
The problem has been solved perfectly, below I uploaded the demo:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.CollectionView) {
//Calculate the section with contentOffset
CGFloat contentOffset = scrollView.contentOffset.y;
NSInteger lastSection = self.firstSection;
if (contentOffset <= 0) {
self.firstSection = 0;
} else {
for (NSInteger section = 0; section < self.playListArr.count - 1; section++) {
contentOffset = contentOffset - (HEADER_VIEW_HEIGHT + (LINE_SPACING + CELL_HEIGHT) * ceil([(NSMutableArray *)self.playListArr[section] count] / 3.0) - LINE_SPACING);
if (contentOffset <= 0 || section == self.playListArr.count - 1) {
//return the current first section:
self.firstSection = section;
break;
}
}
}
}
I am adding a UIView to the end of my UIScrollView as follows -
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
float scrollViewHeight = scrollView.frame.size.height;
float scrollContentSizeHeight = scrollView.contentSize.height;
float scrollOffset = scrollView.contentOffset.y;
if (scrollOffset == 0)
{
}
else if (scrollOffset + scrollViewHeight >= scrollContentSizeHeight)
{
UIView *paintView=[[UIView alloc]initWithFrame:CGRectMake(0, scrollOffset + scrollViewHeight + 20, self.view.frame.size.width, 200)];
[paintView setBackgroundColor:[UIColor yellowColor]];
[self.containerScrollView addSubview:paintView];
}
}
This added the view at the end, but I am not able to scroll that view. How do I enable scrolling to the newly added view as well?
You can set scrollview's contentInset
scrollView.contentInset = UIEdgeInsetsMake(0,0,extensionHeight,0);
but you probably don't wanna add subview in - (void)scrollViewDidScroll:(UIScrollView *)scrollView ,because this function would be invoked many times, your subview would be created many times.
If you have to add subview in it, I suggest you create a property paintView, and check if it is nil, if it is, create it, if not, just don't do anything
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
float scrollViewHeight = scrollView.frame.size.height;
float scrollContentSizeHeight = scrollView.contentSize.height;
float scrollOffset = scrollView.contentOffset.y;
if (scrollOffset == 0)
{
}
else if (scrollOffset + scrollViewHeight >= scrollContentSizeHeight)
{
scrollView.contentInset = UIEdgeInsetsMake(0,0,extensionHeight,0);
if (!_paintView) {
_paintView=[[UIView alloc]initWithFrame:CGRectMake(0, scrollOffset + scrollViewHeight + 20, self.view.frame.size.width, 200)];
[_paintView setBackgroundColor:[UIColor yellowColor]];
[self.containerScrollView addSubview:_paintView];
}
}
}
I have a scroll view with 11 images . I want when the last image(11) it's showed , so the scroll view is ended , to get back to the first image. Any ideea how can i do this ?
I was tryed with :
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView{
return YES;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.scrollView.contentOffset = CGPointMake(0,0);
}
but i don;t have the result wanted.
Firstly you need to check whether content in the scroll view ended or not. If its ended set its content offset to 0.
CGFloat bottomInset = scrollView.contentInset.bottom;
CGFloat bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height - bottomInset;
if (bottomEdge == scrollView.contentSize.height) {
// Scroll view is scrolled to bottom
[self.scrollView setContentOffset:CGPointMake(0, self.scrollView.contentOffset.y)];
}
use these to check you scrolled to bottom or not..i hop it helps
-(void)scrollViewDidScroll: (UIScrollView*)scrollView
{
float scrollViewHeight = self.scrollView.frame.size.height;
float scrollContentSizeHeight = self.scrollView.contentSize.height;
float scrollOffset = self.scrollView.contentOffset.y;
if (scrollOffset == 0)
{
// then we are at the top
}
else if (scrollOffset + scrollViewHeight == scrollContentSizeHeight)
{
// then we are at the end
[self.scrollView setContentOffset:CGPointMake(0, self.scrollView.contentOffset.y)];
}
}
I am using UICollectionView in such a way that collection view cell will be in centre of the screen and left and right cells are partially visible. Infect, UICollectionView width is equal to screen width and cell width is lesser so that left and right cells should partially visible.
For enabling pagination, I had implement custom code that sets the centre cell in centre of the screen. Now its creating some issues; I want to get any default way to avoid custom implementation that causing the issue.
I want to enable pagination in such a way that I can achieve described behaviour in following image.
If I disable custom implementation and enable default pagination then two cells are partially shown but its not expected behaviour as i want.
Thanks
As far as I know, you can't do this by "default", you need to customise your collectionView. The way I achieved this is by subclassing the collectionView's flow layout and implementing the following method like this:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGFloat offSetAdjustment = MAXFLOAT;
CGFloat horizontalCenter = (CGFloat) (proposedContentOffset.x + (self.collectionView.bounds.size.width / 2.0));
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array = [self layoutAttributesForElementsInRect:targetRect];
UICollectionViewLayoutAttributes *currentAttributes;
for (UICollectionViewLayoutAttributes *layoutAttributes in array)
{
if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell)
{
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offSetAdjustment))
{
currentAttributes = layoutAttributes;
offSetAdjustment = itemHorizontalCenter - horizontalCenter;
}
}
}
CGFloat nextOffset = proposedContentOffset.x + offSetAdjustment;
proposedContentOffset.x = nextOffset;
CGFloat deltaX = proposedContentOffset.x - self.collectionView.contentOffset.x;
CGFloat velX = velocity.x;
// detection form gist.github.com/rkeniger/7687301
// based on http://stackoverflow.com/a/14291208/740949
if(deltaX == 0.0 || velX == 0 || (velX > 0.0 && deltaX > 0.0) || (velX < 0.0 && deltaX < 0.0)) {
} else if(velocity.x > 0.0) {
for (UICollectionViewLayoutAttributes *layoutAttributes in array)
{
if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell)
{
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (itemHorizontalCenter > proposedContentOffset.x) {
proposedContentOffset.x = nextOffset + (currentAttributes.frame.size.width / 2) + (layoutAttributes.frame.size.width / 2);
break;
}
}
}
} else if(velocity.x < 0.0) {
for (UICollectionViewLayoutAttributes *layoutAttributes in array)
{
if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell)
{
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (itemHorizontalCenter > proposedContentOffset.x) {
proposedContentOffset.x = nextOffset - ((currentAttributes.frame.size.width / 2) + (layoutAttributes.frame.size.width / 2));
break;
}
}
}
}
proposedContentOffset.y = 0.0;
return proposedContentOffset;
}
(This piece of code is inspired heavily from something else I found here on SO, but I can't find the reference now).
In my case, the collection view isn't paginated, but this method makes it behave like it is.
Also, if you want to make the first cell of the collection view to start at the center of the screen (and same for the last one), you will have to override this method in the UICollectionViewDelegateFlowLayout
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
CGFloat leftInset = (self.view.bounds.size.width - CELL_WIDTH) / 2; // CELL_WIDTH is the width of your cell
return UIEdgeInsetsMake(0, leftInset, 0, leftInset);
}
This method assumes that the collectionView is as wide as the screen. If this is not the case for you, adapt it to your needs by computing the left and right insets for the collectionView.
I have a Collection View that can show about 3.5 cells at a time, and I want it to be paging-enabled. But I'd like it to snap to each cell (just like the App Store app does), and not scroll the full width of the view. How can I do that?
Another way is to create a custom UICollectionViewFlowLayout and override the method like so:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)offset
withScrollingVelocity:(CGPoint)velocity {
CGRect cvBounds = self.collectionView.bounds;
CGFloat halfWidth = cvBounds.size.width * 0.5f;
CGFloat proposedContentOffsetCenterX = offset.x + halfWidth;
NSArray* attributesArray = [self layoutAttributesForElementsInRect:cvBounds];
UICollectionViewLayoutAttributes* candidateAttributes;
for (UICollectionViewLayoutAttributes* attributes in attributesArray) {
// == Skip comparison with non-cell items (headers and footers) == //
if (attributes.representedElementCategory !=
UICollectionElementCategoryCell) {
continue;
}
// == First time in the loop == //
if(!candidateAttributes) {
candidateAttributes = attributes;
continue;
}
if (fabsf(attributes.center.x - proposedContentOffsetCenterX) <
fabsf(candidateAttributes.center.x - proposedContentOffsetCenterX)) {
candidateAttributes = attributes;
}
}
return CGPointMake(candidateAttributes.center.x - halfWidth, offset.y);
}
If you are looking for a Swift solution, check out this Gist
note: this will work when we are really showing preview cells (even if they have an alpha of 0.0f). This is because if the preview cells are not available at the time of scrolling their attributes object will not be passed in the loop...
Here's my implementation in Swift 5 for vertical cell-based paging:
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = self.collectionView else {
let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
return latestOffset
}
// Page height used for estimating and calculating paging.
let pageHeight = self.itemSize.height + self.minimumLineSpacing
// Make an estimation of the current page position.
let approximatePage = collectionView.contentOffset.y/pageHeight
// Determine the current page based on velocity.
let currentPage = velocity.y == 0 ? round(approximatePage) : (velocity.y < 0.0 ? floor(approximatePage) : ceil(approximatePage))
// Create custom flickVelocity.
let flickVelocity = velocity.y * 0.3
// Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)
let newVerticalOffset = ((currentPage + flickedPages) * pageHeight) - collectionView.contentInset.top
return CGPoint(x: proposedContentOffset.x, y: newVerticalOffset)
}
Some notes:
Doesn't glitch
SET PAGING TO FALSE! (otherwise this won't work)
Allows you to set your own flickvelocity easily.
If something is still not working after trying this, check if your itemSize actually matches the size of the item as that's often a problem, especially when using collectionView(_:layout:sizeForItemAt:), use a custom variable with the itemSize instead.
This works best when you set self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast.
Here's a horizontal version (haven't tested it thoroughly so please forgive any mistakes):
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = self.collectionView else {
let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
return latestOffset
}
// Page width used for estimating and calculating paging.
let pageWidth = self.itemSize.width + self.minimumInteritemSpacing
// Make an estimation of the current page position.
let approximatePage = collectionView.contentOffset.x/pageWidth
// Determine the current page based on velocity.
let currentPage = velocity.x == 0 ? round(approximatePage) : (velocity.x < 0.0 ? floor(approximatePage) : ceil(approximatePage))
// Create custom flickVelocity.
let flickVelocity = velocity.x * 0.3
// Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)
// Calculate newHorizontalOffset.
let newHorizontalOffset = ((currentPage + flickedPages) * pageWidth) - collectionView.contentInset.left
return CGPoint(x: newHorizontalOffset, y: proposedContentOffset.y)
}
This code is based on the code I use in my personal project, you can check it out here by downloading it and running the Example target.
You can snap to cells by being the delegate of the collection view and implementing the method:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
This tells you that the user has finished a drag and it allows you to modify the targetContentOffset to align with your cells (i.e. round to the nearest cell). Note that you need to be careful about how you modify the targetContentOffset; in particular, you need to avoid changing it so that the view needs to scroll in the opposite direction of the passed velocity, or you'll get animation glitches. You can probably find many examples of this if you google for that method name.
I developed my solution before looking at the ones here. I also went with creating a custom UICollectionViewFlowLayout and override the targetContentOffset method.
It seems to work fine for me (i.e. I get the same behavior as in the AppStore) even though I got much less code. Here it is, feel free to point me any drawback you can think of:
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
let inset: Int = 10
let vcBounds = self.collectionView!.bounds
var candidateContentOffsetX: CGFloat = proposedContentOffset.x
for attributes in self.layoutAttributesForElements(in: vcBounds)! as [UICollectionViewLayoutAttributes] {
if vcBounds.origin.x < attributes.center.x {
candidateContentOffsetX = attributes.frame.origin.x - CGFloat(inset)
break
}
}
return CGPoint(x: candidateContentOffsetX, y: proposedContentOffset.y)
}
The solution Mike M. presented in the post before worked for me but in my case I wanted to have the first cell starting in the middle of the collectionView. So I used the collection flow delegate method to defined an inset (collectionView:layout:insetForSectionAtIndex:). This made the scroll between the first cell and second to be stuck and not scroll correctly to the first cell.
The reason for this was that candidateAttributes.center.x - halfWidth was having a negative value. The solution was to get the absolute value so I add fabs to this line return CGPointMake(fabs(candidateAttributes.center.x - halfWidth), offset.y);
Fabs should be added by default to cover all situations.
This is my solution. Works with any page width.
Set self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast to feel a real paging.
The solution is based on one section to scroll paginated by items.
- (CGFloat)pageWidth {
return self.itemSize.width + self.minimumLineSpacing;
}
- (CGPoint)offsetAtCurrentPage {
CGFloat width = -self.collectionView.contentInset.left - self.sectionInset.left;
for (int i = 0; i < self.currentPage; i++)
width += [self pageWidth];
return CGPointMake(width, 0);
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset {
return [self offsetAtCurrentPage];
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
// To scroll paginated
/*
if (velocity.x > 0 && self.currentPage < [self.collectionView numberOfItemsInSection:0]-1) self.currentPage += 1;
else if (velocity.x < 0 && self.currentPage > 0) self.currentPage -= 1;
return [self offsetAtCurrentPage];
*/
// To scroll and stop always at the center of a page
CGRect proposedRect = CGRectMake(proposedContentOffset.x+self.collectionView.bounds.size.width/2 - self.pageWidth/2, 0, self.pageWidth, self.collectionView.bounds.size.height);
NSMutableArray <__kindof UICollectionViewLayoutAttributes *> *allAttributes = [[self layoutAttributesForElementsInRect:proposedRect] mutableCopy];
__block UICollectionViewLayoutAttributes *proposedAttributes = nil;
__block CGFloat minDistance = CGFLOAT_MAX;
[allAttributes enumerateObjectsUsingBlock:^(__kindof UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CGFloat distance = CGRectGetMidX(proposedRect) - obj.center.x;
if (ABS(distance) < minDistance) {
proposedAttributes = obj;
minDistance = distance;
}
}];
// Scroll always
if (self.currentPage == proposedAttributes.indexPath.row) {
if (velocity.x > 0 && self.currentPage < [self.collectionView numberOfItemsInSection:0]-1) self.currentPage += 1;
else if (velocity.x < 0 && self.currentPage > 0) self.currentPage -= 1;
}
else {
self.currentPage = proposedAttributes.indexPath.row;
}
return [self offsetAtCurrentPage];
}
This is paginated by sections.
- (CGPoint)offsetAtCurrentPage {
CGFloat width = -self.collectionView.contentInset.leff;
for (int i = 0; i < self.currentPage; i++)
width += [self sectionWidth:i];
return CGPointMake(width, 0);
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
return [self offsetAtCurrentPage];
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
// To scroll paginated
/*
if (velocity.x > 0 && self.currentPage < [self.collectionView numberOfSections]-1) self.currentPage += 1;
else if (velocity.x < 0 && self.currentPage > 0) self.currentPage -= 1;
return [self offsetAtCurrentPage];
*/
// To scroll and stop always at the center of a page
CGRect proposedRect = CGRectMake(proposedContentOffset.x+self.collectionView.bounds.size.width/2 - [self sectionWidth:0]/2, 0, [self sectionWidth:0], self.collectionView.bounds.size.height);
NSMutableArray <__kindof UICollectionViewLayoutAttributes *> *allAttributes = [[self layoutAttributesForElementsInRect:proposedRect] mutableCopy];
__block UICollectionViewLayoutAttributes *proposedAttributes = nil;
__block CGFloat minDistance = CGFLOAT_MAX;
[allAttributes enumerateObjectsUsingBlock:^(__kindof UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CGFloat distance = CGRectGetMidX(proposedRect) - obj.center.x;
if (ABS(distance) < minDistance) {
proposedAttributes = obj;
minDistance = distance;
}
}];
// Scroll always
if (self.currentPage == proposedAttributes.indexPath.section) {
if (velocity.x > 0 && self.currentPage < [self.collectionView numberOfSections]-1) self.currentPage += 1;
else if (velocity.x < 0 && self.currentPage > 0) self.currentPage -= 1;
}
else {
self.currentPage = proposedAttributes.indexPath.section;
}
return [self offsetAtCurrentPage];
}