How to looping a set of cells in UICollectionView? - ios

I don't know is that topic had asked before, but I do some search before asking this simple question, something like "UICollectionView cycling cells" "UIColloectionView Infinite Loop".
If the post is duplicated, I am sorry for that...
I had a Horizontal Collection View as a slider in my app, 5 image will be shown, how can I set the function when user scroll to the end (the fifth Items) and slide to right and move to the first cells? also, if the user scroll the first item and slide to left, it will show the fifth item? Thanks.
SDK: iOS 7
Thank alot~

I had wrote a stupid program to control this
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGFloat pageWidth = _sliderCV.frame.size.width;
self.pageControl.currentPage = _sliderCV.contentOffset.x / pageWidth;
NSLog(#"%f", _sliderCV.contentOffset.x);
CGFloat firstSlide = 0.f;
CGFloat lastSlide = pageWidth * 4;
NSLog(#"contentOffset = %f, firstSlide = %f, SelFirstCount = %li",_sliderCV.contentOffset.x, firstSlide, (long)selFirstCount);
if (_sliderCV.contentOffset.x == firstSlide) {
if (selFirstCount == 0) {
selFirstCount += 1 ;
NSLog(#"%li", (long)selFirstCount);
} else {
NSLog(#"Move to Last");
[_sliderCV setContentOffset:CGPointMake(lastSlide, 0) animated:YES];
selFirstCount = 0;
self.pageControl.currentPage = 4;
selLastCount = 1;
}
} else if (_sliderCV.contentOffset.x == lastSlide) {
if (selLastCount == 0) {
selLastCount += 1;
} else {
[_sliderCV setContentOffset:CGPointMake(firstSlide,0) animated:YES];
selLastCount = 0;
self.pageControl.currentPage = 0;
selFirstCount = 1;
}
} else {
NSLog(#"Clear");
selFirstCount = 0;
selLastCount = 0;
}
}

Related

Keep the first visable headerview’s uilabel’s textcolor = yellow when sliding the uicollectionview

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;
}
}
}
}

Using Masonry to Layout 9 points

I'm using Masonay to layout UI like this:
UI requirement
The size of the 9 points are the same.
And the margin between 9 points are the same.
My question is that how to make margin to left of first column point equal to the margin to the right of the last column points by using Masonay ?
My code blow is not complete.
Can anyone help me with this?
for (NSInteger i = 0; i < 9; i++) {
NSInteger row = i / 3;
NSInteger column = i % 3;
UIView * previousRowPoint;
UIView * previousColumnPoint;
UIView * firstPoint;
if (row > 0) {
previousRowPoint = hintPoints[(row - 1) * 3];
}
if (column > 0) {
previousColumnPoint = hintPoints[column - 1];
}
if (0 != i) {
firstPoint = hintPoints[0];
}
UIView * hintPoint = [[UIView alloc] init];
[hintPoints addObject:hintPoint];
hintPoint.layer.cornerRadius = 8 / 2;
hintPoint.layer.masksToBounds = YES;
hintPoint.layer.borderColor = [UIColor grayColor].CGColor;
hintPoint.layer.borderWidth = 0.5;
[self.view addSubview:hintPoint];
hintPoint.mas_key = [NSString stringWithFormat:#"hintPoint_%ld", (long)i];
[hintPoint mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(8);
make.height.mas_equalTo(8);
if (row == 0) {
make.top.equalTo(weakself.view).offset(100);
}
else {
make.top.equalTo(previousRowPoint.mas_bottom).offset(4);
}
if (column == 0) {
//TODO: how to make the trailing equal to the first point's leading
}
else if (column == 2) {
//TODO: how to make the trailing equal to the first point's leading
}
else {
make.leading.equalTo(previousColumnPoint.mas_trailing).offset(4);
}
}];
}

Star-Half image is not displaying in Custom RatingView IOS

I am using this tutorial http://www.raywenderlich.com/1768/uiview-tutorial-for-ios-how-to-make-a-custom-uiview-in-ios-5-a-5-star-rating-view for custom rating-bar. but I am not able to give half rating. I am trying to display half star when user touch on imageview. Please suggest me
Finally I fixed half-star problem.
if (!self.editable) return;
//The rating starts out as 0 and then builds from there.
CGFloat newRating = 0;
//loop through the image collection backwards so if it exits the loop it will have identified the MAX
for(int i = self.imageViews.count - 1; i >= 0; i--) {
UIImageView *imageView = [self.imageViews objectAtIndex:i];
CGFloat distance = touchLocation.x - imageView.frame.origin.x;
CGFloat frameWidth = imageView.frame.size.width;
if (distance <= 0){
//this means that the click was to the left of the frame
continue;
}
if (distance /frameWidth >.75) {
//If this ratio is >.75 then you are to the right 3/4 of the image or past th image
newRating = i + 1;
break;
} else {
//you didn't drop out or mark the entire star, so mark it half.
newRating = i + 0.5;
break;
}
}
self.rating = newRating;

UICollectionView with paging - setting page width

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];
}

Center Image in ScrollView Xcode

I have a problem with images in a scrollview. I want to scroll the images and they should be centered. There is a problem because each picture has other values in the width.
I tried it with subclassing the scrollview and, but i don't know how to get the actual image in the layoutSubviews-Method.
Status-Update:
Now i try it with scrollViewDidScroll:. But it don't stop on the next image, when i scroll fast.
The method
In arrayWithXValues stands the values where the image begins in scrollview.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGSize iOSDeviceScreenSize = [[UIScreen mainScreen] bounds].size;
float middleOfScreenX = scrollView.contentOffset.x + iOSDeviceScreenSize.width / 2;
for (int i = 1; i < 19; i++) {
if (i < 17) {
if (middleOfScreenX > [[arrayWithXValues objectAtIndex:i] floatValue] && middleOfScreenX < [[arrayWithXValues objectAtIndex:i + 1] floatValue] && actualPage != i) {
...
actualPage = i;
[self killScroll];
}
} else {
if (valMiddleX > [[arrayWithXValues objectAtIndex:i] floatValue]) {
...
actualPage = i;
[self killScroll];
}
}
}
}
- (void)killScroll {
self.horizontalScrollView.scrollEnabled = NO;
self.horizontalScrollView.scrollEnabled = YES;
}
Thanks for all your help

Resources