I have a UIScrollView which scrolls horizontally from left to right.
I want to be able to vertically position my UIScrollView on the Y axis according to the screen size of the iPhone. E.g iPhone 4 and iPhone 5.
CGFloat startX = (70.0f * ((float)[_attachments count] - 1.0f) + padding);
CGFloat startY = 295;
CGFloat width = 64;
CGFloat height = 64;
Right now I start my Y position at 295 which works okay with the iPhone 4 but not on iPhone 5.
How would I change the startY to accommodate the same position for different screen size?
CGRect screenBounds = [[UIScreen mainScreen] bounds];
if (screenBounds.size.height == 568)
{
//iphone5
}
else
{
//iphone4
}
You can set your y value accordingly
Do use layoutSubviews in your view ...
- (void)layoutSubviews {
CGRect frame = self.bounds;
frame.origin.y += frame.size.height - _scrollView.frame.size.height;
if ( ! CGRectEqualToRect( _scrollView.frame, frame ) {
_scrollView.frame = frame;
}
}
... I assume that your horizontal scroll view is in _scrollView ivar and also that the _scrollView.frame.size.height contains correct height of your _scrollView. If not, replace _scrollView.frame.size.height with some constant which does contain your UIScrollView height.
Why the condition with CGRectEqualToRect? Because layoutSubviews can be called very often and when you set new frame (even if it equals) to it, UIScrollView can stop scrolling.
you can do it like this-
first get the width and the height of the screen
CGRect screenBounds = [UIScreen mainScreen].bounds ;
CGFloat width = CGRectGetWidth(screenBounds);
CGFloat height = CGRectGetHeight(screenBounds) ;
now create a method which will do some calculations and return an int
-(NSInteger) getwidth:-(NSInteger *) value
{
int temp = (value * 100) / 480;
return int((height * temp) / 100);
}
-(NSInteger) getheight:-(NSInteger *) value
{
var temp = (value * 100) / 320;
return int((width * temp) / 100);
}
now set the bounds of view according to these function
actind.frame=CGRectMake(getWidth(20),getheight(20),16,16);
this will work on all the devices and the views will arrange themselves according to the width and height of the screen.
hope this will help for you. :)
I am using UIScrollView with paging enabled. My code is based on PageControl example.
Pages in UIScrollView stay close to each other. But I need to add some space between pages (like when you browsing photos in Camera Roll). Is there any way to add some horizontal indentation between pages?
Find the method loadScrollViewWithPage:(int)page in PhoneContentController.m and look at this line:
frame.origin.x = frame.size.width * page;
It is setting the x position of the content frame within the scrollView by the width of the content * the number of the page. So if you want to add a gutter between content you could do something like:
int xOffset = 20;
frame.origin.x = (frame.size.width * page) + xOffset;
You have to make the scrollView's frame wider so it extends by gap/2 pixels out to the left and right of the screen.
CGFloat gap = 20.0; //20 pixels spacing between pages
CGRect frame = self.view.frame;
frame.size.width += gap;
frame.origin.x -= gap/2;
scrollView.frame = frame;
You then add the content viewcontroller's like normal, centered and filling the screen, so make sure to properly offset them by gap/2 in loadScrollViewWithPage:
Edit: if you are targeting iOS 6 only, you could instead use a UIPageViewController with UIPageViewControllerTransitionStyleScroll and using the UIPageViewControllerOptionInterPageSpacingKey option.
I've got a UICollectionView, which works ok, until I start scrolling.
Here some pics first:
As you can see it's great. As I start scrolling (paging enabled) the first one goes a bit offscreen:
This is the problem. Originaly my view have 3 views and I want to scroll and show 3 views only. But as it scrolls (paging enabled) it hides a little bit of the first view and show little bit of the next first view from the next page.
And here is a video, because it's kinda hard to explain:
Video of the problem (Dropbox)
Here is a picture of my UICollectionView settings:
It's going to be great if someone can help!
The fundamental issue is Flow Layout is not designed to support the paging. To achieve the paging effect, you will have to sacrifice the space between cells. And carefully calculate the cells frame and make it can be divided by the collection view frame without remainders. I will explain the reason.
Saying the following layout is what you wanted.
Notice, the most left margin (green) is not part of the cell spacing. It is determined by the flow layout section inset. Since flow layout doesn't support heterogeneous spacing value. It is not a trivial task.
Therefore, after setting the spacing and inset. The following layout is what you will get.
After scroll to next page. Your cells are obviously not aligned as what you expected.
Making the cell spacing 0 can solve this issue. However, it limits your design if you want the extra margin on the page, especially if the margin is different from the cell spacing. It also requires the view frame must be divisible by the cell frame. Sometimes, it is a pain if your view frame is not fixed (considering the rotation case).
The real solution is to subclass UICollectionViewFlowLayout and override following methods
- (CGSize)collectionViewContentSize
{
// Only support single section for now.
// Only support Horizontal scroll
NSUInteger count = [self.collectionView.dataSource collectionView:self.collectionView
numberOfItemsInSection:0];
CGSize canvasSize = self.collectionView.frame.size;
CGSize contentSize = canvasSize;
if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
{
NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;
NSUInteger page = ceilf((CGFloat)count / (CGFloat)(rowCount * columnCount));
contentSize.width = page * canvasSize.width;
}
return contentSize;
}
- (CGRect)frameForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGSize canvasSize = self.collectionView.frame.size;
NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;
CGFloat pageMarginX = (canvasSize.width - columnCount * self.itemSize.width - (columnCount > 1 ? (columnCount - 1) * self.minimumLineSpacing : 0)) / 2.0f;
CGFloat pageMarginY = (canvasSize.height - rowCount * self.itemSize.height - (rowCount > 1 ? (rowCount - 1) * self.minimumInteritemSpacing : 0)) / 2.0f;
NSUInteger page = indexPath.row / (rowCount * columnCount);
NSUInteger remainder = indexPath.row - page * (rowCount * columnCount);
NSUInteger row = remainder / columnCount;
NSUInteger column = remainder - row * columnCount;
CGRect cellFrame = CGRectZero;
cellFrame.origin.x = pageMarginX + column * (self.itemSize.width + self.minimumLineSpacing);
cellFrame.origin.y = pageMarginY + row * (self.itemSize.height + self.minimumInteritemSpacing);
cellFrame.size.width = self.itemSize.width;
cellFrame.size.height = self.itemSize.height;
if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
{
cellFrame.origin.x += page * canvasSize.width;
}
return cellFrame;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes * attr = [super layoutAttributesForItemAtIndexPath:indexPath];
attr.frame = [self frameForItemAtIndexPath:indexPath];
return attr;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect];
NSMutableArray * attrs = [NSMutableArray array];
[originAttrs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attr, NSUInteger idx, BOOL *stop) {
NSIndexPath * idxPath = attr.indexPath;
CGRect itemFrame = [self frameForItemAtIndexPath:idxPath];
if (CGRectIntersectsRect(itemFrame, rect))
{
attr = [self layoutAttributesForItemAtIndexPath:idxPath];
[attrs addObject:attr];
}
}];
return attrs;
}
Notice, above code snippet only supports single section and horizontal scroll direction. But it is not hard to expand.
Also, if you don't have millions of cells. Caching those UICollectionViewLayoutAttributes may be a good idea.
You could disable paging on UICollectionView and implement a custom horizontal scrolling/paging mechanism with a custom page width/offset like this:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
float pageWidth = 210;
float currentOffset = scrollView.contentOffset.x;
float targetOffset = targetContentOffset->x;
float newTargetOffset = 0;
if (targetOffset > currentOffset)
newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth;
else
newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth;
if (newTargetOffset < 0)
newTargetOffset = 0;
else if (newTargetOffset > scrollView.contentSize.width)
newTargetOffset = scrollView.contentSize.width;
targetContentOffset->x = currentOffset;
[scrollView setContentOffset:CGPointMake(newTargetOffset, 0) animated:YES];
}
This answer is way late, but I have just been playing with this problem and found that the cause of the drift is the line spacing. If you want the UICollectionView/FlowLayout to page at exact multiples of your cells width, you must set:
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
flowLayout.minimumLineSpacing = 0.0;
You wouldn't think the line spacing comes into play in horizontal scrolling, but apparently it does.
In my case I was experimenting with paging left to right, one cell at a time, with no space between cells. Every turn of the page introduced a tiny bit of drift from the desired position, and it seemed to accumulate linearly. ~10.0 pts per turn. I realized 10.0 is the default value of minimumLineSpacing in the flow layout. When I set it to 0.0, no drift, when I set it to half the bounds width, each page drifted an extra half of the bounds.
Changing the minimumInteritemSpacing had no effect.
edit -- from the documentation for UICollectionViewFlowLayout:
#property (nonatomic) CGFloat minimumLineSpacing;
Discussion
...
For a vertically scrolling grid, this value represents the minimum
spacing between successive rows. For a horizontally scrolling grid,
this value represents the minimum spacing between successive columns.
This spacing is not applied to the space between the header and the
first line or between the last line and the footer.
The default value of this property is 10.0.
The solution from the following article is elegant and simple. The main idea is creation the scrollView on top of your collectionView with passing all contentOffset values.
http://b2cloud.com.au/tutorial/uiscrollview-paging-size/
It should be said by implementing this method:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;
I didn't achieve a smooth animation like it's happening with pagingEnabled = YES.
I know this question is old, but for anyone who happens to stumble upon this.
All you have to do to correct this is set the MinimumInterItemSpacing to 0 and decrease the content's frame.
#devdavid was spot on on the flowLayout.minimumLineSpacing to zero.
It can also be done in the layout editor, setting the Min Spacing for Lines to 0:
I think I understand the problem. I'll try and make you understand it too.
If you look closely, then you will see that this issue happens only gradually and not just on the first page swipe.
If I understand correctly, in your app, currently, every UICollectionView item are those rounded boxes which we see, and you have some offset/margin between all of them which is constant. This is what is causing the issue.
Instead, what you should do, is make a UICollectionView item which is 1/3rd of the width of the whole view, and then add that rounded image view inside it. To refer to the image, the green colour should be your UICollectionViewItem and not the black one.
Do you roll your own UICollectionViewFlowLayout?
If so, adding -(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
withScrollingVelocity:(CGPoint)velocity will help you to calculate where the scrollview should stop.
This might work (NB: UNTESTED!):
-(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
withScrollingVelocity:(CGPoint)velocity
{
CGFloat offsetAdjustment = MAXFLOAT;
CGFloat targetX = proposedContentOffset.x + self.minimumInteritemSpacing + self.sectionInset.left;
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
for(UICollectionViewLayoutAttributes *layoutAttributes in array) {
if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
CGFloat itemX = layoutAttributes.frame.origin.x;
if (ABS(itemX - targetX) < ABS(offsetAdjustment)) {
offsetAdjustment = itemX - targetX;
}
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
My answer is based on answer https://stackoverflow.com/a/27242179/440168 but is more simple.
You should place UIScrollView above UICollectionView and give them equal sizes:
#property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
#property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
Then configure contentInset of collection view, for example:
CGFloat inset = self.view.bounds.size.width*2/9;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);
And contentSize of scroll view:
self.scrollView.contentSize = CGSizeMake(self.placesCollectionView.bounds.size.width*[self.collectionView numberOfItemsInSection:0],0);
Do not forget to set delegate of scroll view:
self.scrollView.delegate = self;
And implement main magic:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.scrollView) {
CGFloat inset = self.view.bounds.size.width*2/9;
CGFloat scale = (self.placesCollectionView.bounds.size.width-2*inset)/scrollView.bounds.size.width;
self.collectionView.contentOffset = CGPointMake(scrollView.contentOffset.x*scale - inset, 0);
}
}
your UICollectionView's width should be an exact multiplication of the cell size width + the left and right insets. In your example, if the cell width is 96, then the UICollectionView's width should be (96 + 5 + 5) * 3 = 318.
Or, if you wish to keep UICollectionView's 320 width, your cell size width should be 320 / 3 - 5 - 5 = 96.666.
If this does not help, your UICollectionView's width might be different than what is set in the xib file, when the application runs. To check this - add an NSLog statement to printout the view's size in runtime:
NSLog(#"%#", NSStringFromCGRect(uiContentViewController.view.frame));
This is the same problem that I was experiencing and i posted my solution on another post, so I'll post it again here.
I found a solution to it, and it involved subclassing the UICollectionViewFlowLayout.
My CollectionViewCell size is 302 X 457 and i set my minimum line spacing to be 18 (9pix for each cell)
When you extend from that class there are a few methods that need to be over-ridden. One of them is
(CGSize)collectionViewContentSize
In this method, I needed to add up the total width of what was in the UICollectionView. That includes the ([datasource count] * widthOfCollectionViewCell) + ([datasource count] * 18)
Here is my custom UICollectionViewFlowLayout methods....
-(id)init
{
if((self = [super init])){
self.itemSize = CGSizeMake(302, 457);
self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
self.minimumInteritemSpacing = 0.0f;
self.minimumLineSpacing = 18.0f;
[self setScrollDirection:UICollectionViewScrollDirectionHorizontal];
}
return self;
}
-(CGSize)collectionViewContentSize{
return CGSizeMake((numCellsCount * 302)+(numCellsCount * 18), 457);
}
This worked for me, so I hope someone else finds it useful!
you also can set view's width to '320+spacing', and then set page enable to yes. it will scroll '320+spacing' for every time. i think it because page enable will scroll view's width but not screen's width.
I think I do have a solution for this issue. But I do not if it's the best.
UICollectionViewFlowLayout does contain a property called sectionInset. So you could set the section Inset to whatever your need is and make 1 page equalling one section. Therefore your scrolling should automatically fit properly in the pages ( = sections)
I had a similar problem with paging. Even though the cell insets were all 0 and the cell was exactly the same size in width and height as the UICollectionView, the paging wasn't proper.
What I noticed sounds like a bug in this version (UICollectionView, iOS 6): I could see that if I worked with a UICollectionView with width = 310px or above, and a height = 538px, I was in trouble. However, if I decreased the width to, say, 300px (same height) I got things working perfectly!
For some future reference, I hope it helps!
I encountered a similar issue when trying to get horizontal paging working on a 3 x 3 grid of cells with section insets and cell & line spacing.
The answer for me (after trying many of the suggestions - including subclassing UICollectionViewFlowLayout and various UIScrollView delegate solutions) was simple. I simply used sections in the UICollectionView by breaking my dataset up into sections of 9 items (or fewer), and utilising the numberOfSectionsInCollectionView and numberOfItemsInSection UICollectionView datasource methods.
The UICollectionView's horizontal paging now works beautifully. I recommend this approach to anyone currently tearing their hair out over a similar scenario.
If you're using the default flow-layout for your UICollectionView and do NOT want any space between each cell, you can set its miniumumLineSpacing property to 0 via:
((UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout).minimumLineSpacing = 0;
I have an image for user's icon and a label for user's name
And I want the image and the label align center horizontally in the screen.
Because the length of the label changes as the length of user's name, (the size of image is fixed)
I can't set the positon as a fixed value.
Now I change the positon of the image and lalel at runtime,
It's not convenient.
Is there any good way to do this?
Thank you:)
Here is the snapshot:
All right.
I know there is no a very convenient way to do this just with IB.
And I learn the function of [label sizetoFit].
It's very helpful.
In android, it's very convenient to do this just with the xml layout.
But in ios I have to write code to control the positon of image and label.
Yes...Not too bad.
Thanks everybody :)
[label sizetoFit];
label.center =imageView.center;
this will make the label center on the image center..and it will appear on top...
now you can just use CGRect manipulation to move the label origin downwards based on your image height.
Edit .. now you can do this ..
CGRect frame = label.frame;
frame.origin.x -= imageview.frame.size.width/2 - 15; // you can change 15 to a more appropriate number based on your preference..
label.frame = frame;
Do the above after you do the 2 lines of code given on top in my answer.
Do some calculation
Width of UIImageView + Offset width (Space b/n UIImageView and UILabel "Keep this as constant value") + UILabel Width = You Will get some 'X' width.
iPhone screen width is 460.
320-X = Remaining width.
Remaining width/2 pass this value to UIImageView's X value. This might help you.
#define MARGIN 10.0f
In your -layoutSubviews method, do this:
[ label sizeToFit ] ;
CGRect r = label.frame ;
r.origin = (CGPoint){ CGRectGetMaxX( imageView.frame ) + MARGIN, CGRectGetMidY( imageView.frame ) - 0.5 * r.size.height } ;
label.frame = CGRectIntegral( r ) ;
update here's how to center the the image view and label unit
bounds should be set to bounds of enclosing view
//
// center an image view + label in parent view, with label to right of image view
//
[ label sizeToFit ] ;
CGSize size = (CGSize){ imageView.image.size.width + MARGIN + label.bounds.size.width,
MAX( imageView.image.size.height, label.bounds.size.height } ;
CGRect r = (CGRect){
{ CGRectGetMidX( bounds ) - 0.5 * size.width,
CGRectGetMidY( bounds ) - 0.5 * size.height },
size }
} ;
CGRect imageFrame, labelFrame ;
CGRectDivide( r, &imageFrame, &labelFrame, imageView.image.size.width, CGRectMinXEdge ) ;
imageView.frame = imageFrame ;
labelView.frame = labelFrame ;
I am loading in images with varying sizes and putting them in UIScrollViews, all the images are larger than the UIScrollView.
The user can scroll and zoom as they please, but initially I would like for the image
to be centered and scaled so the largest side of the image aligns with the edge of the scrollView, i.e. if the picture is in landscape I would like to size and scale it so that the left and right side goes all the way to the edge of the UIScrollVIew and vice versa
I found a formula in a utility function in the Programming guide but it does not quite fit my needs.
My approach is to use:
CGrect initialPos = ?
[self.scrollView zoomToRect:initialPos animated:YES];
I know the size of my scrollView and the size of my image, what I need to figure out is the scale and CGRect to apply to the scrollView to center and size my image.
Hope someone can help out:) Thanks
Edit: previous version was sizing the image to the view rather than the other way around. I think this should correct that:
double imgRatio = imageSize.width / imageSize.height;
double viewRatio = viewSize.width / viewSize.height;
if ( imgRatio >= viewRatio )
{
initialPos.size.width = imageSize.width;
initialPos.size.height = imageSize.width / viewRatio;
initialPos.origin.x = 0;
initialPos.origin.y = (imageSize.height - initialPos.size.height) / 2;
}
else
{
initialPos.size.height = imageSize.height;
initialPos.size.width = imageSize.height * viewRatio;
initialPos.origin.y = 0;
initialPos.origin.x = (imageSize.width - initialPos.size.width) / 2;
}