Afternoon all,
Has anyone worked with collection views in iOS with an image fullscreen and horizontal paging?
I am currently experiencing difficulties when I rotate to landscape. I have read many posts on this subject on here and can't seem to find anything that helps the issue.
When I rotate the item i was looking at changes and the images are displaced on the screen. Is there anything short of turning orientations off that will fix this?
Thanks
ps if you need to see any of the code let me know and i'll edit my question
Code used:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_startingIndexPath) {
NSInteger currentIndex = floor((scrollView.contentOffset.x - scrollView.bounds.size.width / 2) / scrollView.bounds.size.width) + 1;
if (currentIndex < [self.marbleImages count]) {
self.title = self.marbleImages[currentIndex][#"name"];
}
}
}
- (BOOL) shouldAutorotate
{
return YES;
}
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
layout.itemSize = self.view.bounds.size;
[self.collectionView scrollToItemAtIndexPath:self.startingIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
Related
EDIT: I have attached a simple demo project dropbox link at bottom
I have a UI with a UICollectionView at top and a scrollview at the bottom. I want the scrolling in collectionview to scroll the scrollview too in sync. I have disabled user-interaction in scrollview so only the collectionview can effect the scrolling in it.
Each collectionview item is 150px for this testing purpose.
The UIViews in the scrollview are screen width in size. So for every scroll of a collectionview item, I need to scroll the scrollview by screen width. To achieve this, I am calculating the distance the collectionview offset has changed by and then dividing it by the cell width (150) and multiplying it by scrollview's width.
I am trying to achieve the following UI:
Start:
Scroll collectionview to cell 1:
Scroll collectionview to cell 2:
This all works fine the first few times but as I scroll the collectionview back and forth a few times to longer distances (let's say cell 10 -> 0 -> 10 -> 0 and so on), the scrollview goes out of sync by "tiny" distances. To illustrate this, notice how there is the "yellow" color from the second UIView on the right edge of the scrollview:
I can see this issue by NSLogging the contentOffset of the scrollview too (notice how it starts getting out of sync by 0.5 after few times):
2018-11-25 19:24:28.273278-0500 ScrollViewMatchTest[19412:1203912] Finished: 0
2018-11-25 19:24:31.606521-0500 ScrollViewMatchTest[19412:1203912] Finished: 0.5
2018-11-25 19:24:55.173709-0500 ScrollViewMatchTest[19412:1203912] Finished: 1.5
2018-11-25 19:25:03.007528-0500 ScrollViewMatchTest[19412:1203912] Finished: 1.5
2018-11-25 19:25:07.841096-0500 ScrollViewMatchTest[19412:1203912] Finished: 2.5
2018-11-25 19:26:57.634429-0500
I am not really sure what's causing this problem and I have tried quite a few ways to fix it but in vain. I can sort of figure out a workaround (to reset the offset and bring it back in sync when scrolling finishes) but I would like to know why exactly this out of sync issue is caused.
Workaround solution by resetting the contentOffset of scrollView to closes multiple of screen width:
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (scrollView==self.myCollectionView) {
NSLog(#"Finished: %g",self.myScrollView.contentOffset.x);
NSLog(#"Closest: %g",RoundTo(self.myScrollView.contentOffset.x, self.myScrollView.frame.size.width));
[self.myScrollView setContentOffset:CGPointMake(RoundTo(self.myScrollView.contentOffset.x, self.myScrollView.frame.size.width), self.myScrollView.contentOffset.y) animated:YES];
}
}
float RoundTo(float number, float to)
{
if (number >= 0) {
return to * floorf(number / to + 0.5f);
}
else {
return to * ceilf(number / to - 0.5f);
}
}
END OF WORKAROUND SOLUTION
I have attached a simple demo project to illustrate this issue as well (run the app and scroll back and forth aggresively on the top scrollview a few times): https://www.dropbox.com/s/e2bzgo6abq5wmgw/ScrollViewMatchTest.zip?dl=0
Here's the code:
#import "ViewController.h"
#define countOfItems 50
#interface ViewController (){
CGFloat previousOffset_Header;
CGFloat previousOffset_Scrollview;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.myCollectionView registerNib:[UINib nibWithNibName:#"MyCell" bundle:nil] forCellWithReuseIdentifier:#"cell"];
[self.myCollectionView reloadData];
for (NSInteger i=0; i<countOfItems; i++) {
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(i*self.myScrollView.frame.size.width, 0, self.myScrollView.frame.size.width, self.myScrollView.frame.size.height)];
myView.backgroundColor=i%2==0?[UIColor blueColor]:[UIColor yellowColor];
[self.myScrollView addSubview:myView];
}
self.myScrollView.contentSize=CGSizeMake(countOfItems*self.myScrollView.frame.size.width, self.myScrollView.frame.size.height);
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return countOfItems;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
cell.backgroundColor = indexPath.item%2==0?[UIColor blueColor]:[UIColor yellowColor];
cell.myLabel.text=[NSString stringWithFormat:#"%ld",indexPath.item];
return cell;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
if (scrollView==self.myCollectionView) {
previousOffset_Header = self.myCollectionView.contentOffset.x;
previousOffset_Scrollview = self.myScrollView.contentOffset.x;
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView==self.myCollectionView) {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:#selector(scrollViewDidEndScrollingAnimation:) withObject:scrollView afterDelay:0.1 inModes:#[NSRunLoopCommonModes]];
CGFloat offsetToMoveBy = (self.myCollectionView.contentOffset.x-previousOffset_Header)*(self.myScrollView.frame.size.width/150);
previousOffset_Scrollview = previousOffset_Scrollview +offsetToMoveBy;
[self.myScrollView setContentOffset:CGPointMake(previousOffset_Scrollview, self.myScrollView.contentOffset.y) animated:NO];
previousOffset_Header = self.myCollectionView.contentOffset.x;
}
}
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSLog(#"Finished: %g",self.myScrollView.contentOffset.x);
}
#end
Be aware in viewDidLoad method myScrollView frame is not really setted to the real device.
So in init of the view, the views's width and height might be wrong in your code.
There are a couple of things you should be doing differently, the way you calculate the offset and not using the UIScrollViewDelegate callbacks scrollViewDidEndDragging:willDecelerate: and scrollViewDidEndDecelerating:. I've rewritten the relevant code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView==self.myCollectionView) {
[self calculateScrollviewOffset:self.myCollectionView];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (scrollView == self.myCollectionView && !decelerate) {
[self calculateEndPosition: self.myCollectionView];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView == self.myCollectionView) {
[self calculateEndPosition: self.myCollectionView];
}
}
- (void) calculateScrollviewOffset:(UICollectionView *) collectionView {
CGFloat cellWidth = 150.0;
CGFloat percentageMoved = collectionView.contentOffset.x / cellWidth;
[self setScrollViewOffset:percentageMoved animated:false];
}
- (void) calculateEndPosition:(UICollectionView*) collectionView {
CGFloat cellWidth = 150.0;
// NOTE: Add 0.5 to play around with the end animation positioning
CGFloat percentageMoved = floor(collectionView.contentOffset.x / cellWidth);
CGFloat collectionViewFixedOffset = (percentageMoved * cellWidth);
[collectionView setContentOffset:CGPointMake(collectionViewFixedOffset, collectionView.contentOffset.y) animated:true];
[self setScrollViewOffset:percentageMoved animated:true];
}
- (void) setScrollViewOffset:(CGFloat) percentageMoved animated:(BOOL) animated {
CGFloat newOffsetX = percentageMoved * self.myScrollView.frame.size.width;
[self.myScrollView setContentOffset:CGPointMake(newOffsetX, self.myScrollView.contentOffset.y) animated: animated];
}
I have an Extended UICollectionFlowLayout. This vertically centres the UIcollectionViewCell by translating the attribute.frame by required amount and also shifting the visible Rect of collection view to show the transformed cells.
This works perfectly fine in ios7. However in ios6 the visible Rect of collection view does not change , hence forth cells are shown shifted but clipped.
Eg : -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect rect = (0,0,320,500) and I shift cells by 200 then cells with start showing from (0,0,320,200) to (0,0,320,500) and those below 500 will be clipped. Any reason why this would happen in ios6 when it work perfectly in iOS7 ?
#implementation VerticallyCenteredFlowLayout
-(id)init
{
if (!(self = [super init])) return nil;
[self setMinimumLineSpacing:5.0];
[self setMinimumInteritemSpacing:0.0];
[self setItemSize:CGSizeMake(10, 10)];
[self setSectionInset:UIEdgeInsetsMake(0, 11, 11, 11)];
[self setScrollDirection:UICollectionViewScrollDirectionVertical];
return self;
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* array = [super layoutAttributesForElementsInRect:rect];
UICollectionViewLayoutAttributes* att = [array lastObject];
if (att){
CGFloat lastY = att.frame.origin.y + att.frame.size.height;
CGFloat diff = self.collectionView.frame.size.height - lastY;
if (diff > 0){
for (UICollectionViewLayoutAttributes* a in array){
a.frame = CGRectMake(a.frame.origin.x, a.frame.origin.y + diff/2, a.frame.size.width, a.frame.size.height) ;
}
}
}
return array;
}
The contentSize was not being automatically Adjusted in ios6.
Overiding following method in VerticallyCenteredFlowLayout Class fixed the issue
-(CGSize)collectionViewContentSize {
CGSize size = [super collectionViewContentSize];
if (size.height < MIN(_maxHeight,self.collectionView.frame.size.height)) {
size.height = MIN(_maxHeight,self.collectionView.frame.size.height);
}
return size;
}
I have a problem managing my UICollectionsView with flowlayout and orientation. Basically, the problems happens when I switch to landscape, some of the UICollectionViewCells are missing. They reappear once I start scrolling. It should be able to show all the cell since there is so much more room on the contentSize.
I even tried in my layout:
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
With the same results.
Anyone have any ideas on this problem? I included a image on how my layout is organized and the expected behavior:
To add more details, each of the color coded cells belongs to its own section and I am shifting my 2nd section to the right on orientation change using:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
NSIndexPath* indexPath = attributes.indexPath;
if (nil == attributes.representedElementKind) {
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
} else {
NSString *kind = attributes.representedElementKind;
attributes.frame = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath].frame;
}
}
return attributesToReturn;
}
It seems the number of items in the layoutAttributesForElementsInRect are completely different in landscape and in portrait.
Finally figure it out. I increased my the rect to a large size when it's on landscape on:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
CGRect *newRect = CGRectMake(0,0, screensize.width, screensize.height);
NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:newRect];
...
}
I have a UIScrollView which contains a UIView, that in turn has an UIImageView and several UIButtons in it.
The problem I have is that the image is displayed about 20 px underneath my navigation bar.
People who seem to have similar problems had solutions like setting
self.automaticallyAdjustsScrollViewInsets = NO;
or just hide the status bar
- (BOOL)prefersStatusBarHidden {
return YES;
}
However, both solutions don't work for me. The first one sets the contents too high and adds the 20px space at the bottom instead. The second one hides the status bar , but the offset still remains.
Since I have a lot of the code from this tutorial by Ray Wenderlich and I have no idea where exactly in my code the problem lies, this is the link to code on github.
Can anyone help me?
I was able to figure out what is going on. Thanks for posting the link to your app.
The issue is this following code:
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
It turns out the contentsFrame.size.height is in fact less than bounds.Size.height when the map is first displayed. So your code is centering the image vertically. That's why you see that offset at the top. 17.279 pixels to be exact.
I've been playing around with your app (which seem very interesting btw) and I believe that you can get rid of the two calls to centerScrollViewContents altogether and it will work just as you expected.
Let me know if you have more questions.
Hope this helps!
The right solution is the one from #LuisCien
I'm adding here that I have found it useful when using UIScrollView with a MPMediaPickerController, so:
- (void)fixViewFrameBoundSize {
CGRect contentsFrame = self.view.frame;
CGSize boundsSize = self.view.bounds.size;
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
[self.view setFrame:contentsFrame];
}
and
- (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker {
[self dismissViewControllerAnimated:YES completion:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self fixViewFrameBoundSize];
});
}];
}
and the same for the selected items callback:
[self dismissViewControllerAnimated:YES
completion:^{
//LP : get item url and play
dispatch_async(dispatch_get_main_queue(), ^{
[self fixViewFrameBoundSize];
});
MPMediaItem *item = [collection representativeItem];
This solution works well when you have a XIB with a UIView, and you transform it in a UISCrollView as follows:
- (void)scrollViewMakeScrollable {
CGSize scrollableSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height+44.0);
[((UIScrollView*)self.view) setContentSize:scrollableSize];
[((UIScrollView*)self.view) setAlwaysBounceHorizontal:NO];
[((UIScrollView*)self.view) setAlwaysBounceVertical:NO];
self.automaticallyAdjustsScrollViewInsets = NO;
}
So normally when you are doing like
- (void) viewDidLoad {
[super viewDidLoad];
[self scrollViewMakeScrollable];
I am trying to offset the center on y axis, of all cells below selected cell. I added a property to CollectionViewFlowLayout subclass, called extendedCell, which marks the cell below which I should offset everything else.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
if(!self.extendedCell)
{
return attributes;
}
else
{
return [self offsetCells:attributes];
}
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attribute = [super layoutAttributesForItemAtIndexPath:indexPath];
if(!self.extendedCell)
{
return attribute;
}
else
{
if(![attribute.indexPath isEqual:self.extendedCell] &&
attribute.center.y >= [super layoutAttributesForItemAtIndexPath:self.extendedCell].center.y)
{
CGPoint newCenter = CGPointMake(attribute.center.x,
attribute.center.y + 180.f);
attribute.center = newCenter;
}
return attribute;
}
}
-(NSArray *)offsetCells:(NSArray *)layoutAttributes
{
for(UICollectionViewLayoutAttributes *attribute in layoutAttributes)
{
if(![attribute.indexPath isEqual:self.extendedCell] &&
attribute.center.y >= [super layoutAttributesForItemAtIndexPath:self.extendedCell].center.y)
{
CGPoint newCenter = CGPointMake(attribute.center.x,
attribute.center.y + 180.0f);
attribute.center = newCenter;
}
}
return layoutAttributes;
}
Turns out that something bad happens on the way, as cells at the bottom disappear. I have a feeling that this has something to do with cells being outside UICollectionView content size, but setting the size while generating layout does not help. Any ideas how to fix that disappearance?
OK turns out I found the bug. Seems that overriding -(CGSize)collectionViewContentSize helps. If any cells lie outside the content size of the collection view they simply disappear. As the content size is set for good before any layout attributes calls, and cells are not allowed to be placed outside of it, collection view just gets rid of them. I thought the content size is based upon cells attributes after they've been set, this is not the case.
-(CGSize)collectionViewContentSize
{
if(self.extendedCell)
{
CGSize size = [super collectionViewContentSize];
return CGSizeMake(size.width, size.height + 180);
}
else
{
return [super collectionViewContentSize];
}
}
Solved it by breaking big cells into minor cells an connected them with a view. Very hacky, but iOS7 will hopefully help.
Check if any returned size of cell have one of border size equals 0.
In my case disappear cause was sizeForItem returned {320,0}
Why UICollectionView with UICollectionViewFlowLayout not show cells, but ask for size?
And for correct size view from nib (with used autolayouts) i use:
+ (CGSize)sizeForViewFromNib:(NSString *)nibName width:(CGFloat)width userData:(id)userData {
UIView *view = viewFromNib(nibName, nil);
[view configForUserData:userData];
CGSize size = [view systemLayoutSizeFittingSize:CGSizeMake(width, SOME_MIN_SIZE) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
return CGSizeMake(width, size.height);
}