views:

5121

answers:

7

UIScrollView in paging mode assumes the pages are located right next to each other, with no gap. However if you open a photo in the Photos app and swipe through photos, you can see that it has some gap between pages. I want these gaps too.

I'm looking for existing solutions if any, or for some more bizarre ideas about implementing the page gaps besides the one I have explained below. Or maybe there's some obvious easy way I am missing?

To be clear: I want the gap to only be visible while scrolling, so I cannot simply inset the page content.


My plan is to try moving the page content from inside scrollViewDidScroll callback, so that (assuming you're scrolling to the right) initially the target page is slightly offset to the right of its page boundaries, and by the time you arrive at the target page it's back at its proper location, and the source page is slightly offset to the left of its boundaries. (Or maybe instead of moving things continuously, I'll be better off shifting the offsets, say, exactly halfway between pages.)

I'm the author of the ScrollingMadness article+example that I've been referring some people to here. I've implemented progammatic zooming, and got in-photo zooming+scrolling working together with inter-photo paging. So I know how to play with UIScrollView, and am looking for the advanced stuff.

Please don't point me at TTScrollView. I've already pointed many people to it myself, but I consider it's feel too far from the native UIScrollView behaviour, and do not want to use it in my projects.

+1  A: 

This is just a hunch, so apologies if completely wrong, but is it possible that the contentSize is just set to slightly wider than the screen width.

The correct information is then rendered within the view to the screen width and UIScrollView takes care of the rest ?

Dean Smith
Thanks for the suggestion. The problem is that contentSize specifies the size of all pages together; there is no way to control the size of a single page — it is always the width of the scroll view. (We are talking about pagingEnabled mode here!) I could make the scroll view wider to make the page wider, but then the scroll bars would be slightly offscreen, which is ugly.
Andrey Tarantsov
Turns out I've described the correct solution in a comment to your answer. There are no scroll bars in paging mode, so making the scroll view larger than the screen is the answer. I only realized it just now after another person suggested this, but it was your answer that inspired me to think about the right idea in the first place. So keep posting hunches, they do help! :)
Andrey Tarantsov
Glad to be of some help ;)
Dean Smith
A: 

Maybe you want to try UIScrollView's contentInset property?

myScrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 10.0);
digdog
Thanks for the suggestion; contentInset looks promising indeed, and was one of the first things I tried. Unfortunately, it adds inset around all of the content, not around every page (and it actually does not play well with the paging mode at all).
Andrey Tarantsov
+4  A: 

Have you tried making the frame of the UIScrollview slighly larger than the screen (assuming that you want to display your images fullscreen and then arranging your subviews on the same slightly-larger-than-the-screen boundaries.

#define kViewFrameWidth 330; // i.e. more than 320

CGRect scrollFrame;
scrollFrame.origin.x = 0;
scrollFrame.origin.y = 0; 
scrollFrame.size.width = 480;
scrollFrame.size.height = kViewFrameWidth;

UIScrollView* myScrollView = [[UIScrollView alloc] initWithFrame:scrollFrame];
myScrollView.bounces = YES;
myScrollView.pagingEnabled = YES;
myScrollView.backgroundColor = [UIColor redColor];

UIImage* leftImage = [UIImage imageNamed:@"ScrollTestImageL.png"];
UIImageView* leftView = [[UIImageView alloc] initWithImage:leftImage];
leftView.backgroundColor = [UIColor whiteColor];
leftView.frame = CGRectMake(0,0,320,480);

UIImage* rightImage = [UIImage imageNamed:@"ScrollTestImageR.png"];
UIImageView* rightView = [[UIImageView alloc] initWithImage:rightImage];
rightView.backgroundColor = [UIColor blackColor];
rightView.frame = CGRectMake(0, kViewFrameWidth * 2 ,320,480);

UIImage* centerImage = [UIImage imageNamed:@"ScrollTestImageC.png"];
UIImageView* centerView = [[UIImageView alloc] initWithImage:centerImage];
centerView.backgroundColor = [UIColor grayColor];
centerView.frame = CGRectMake(0, kViewFrameWidth ,320,480);

[myScrollView addSubview:leftView];
[myScrollView addSubview:rightView];
[myScrollView addSubview:centerView];

[myScrollView setContentSize:CGSizeMake(kViewFrameWidth * 3, 0)];
[myScrollView setContentOffset:CGPointMake(kViewFrameWidth, 0)];

[leftView release];
[rightView release];
[centerView release];

Apologies if this doesn't compile, I tested it in a landscape app and hand edited it back to portrait. I'm sure you get the idea though. It relies on the superview clipping which for a full screen view will always be the case.

Roger Nolan
Hm. Thanks a lot. The funny thing is that I've described the same very thing in a comment to another answer, saying that “I could make the scroll view wider to make the page wider, but then the scroll bars would be slightly offscreen, which is ugly”. However thanks to your post I thought about it a second time, and realized I don't need scroll bars in paging mode. Stupid stupid me. Thanks again, I'll credit you in the ScrollingMadness sample when I add this trick to it.
Andrey Tarantsov
Thanks I also deleted my slightly bogus first answer. Glad it worked.
Roger Nolan
Just a quick note about Andrey's comment: you can move the scroll indicators with UIScrollView's scrollIndicatorInsets property.
Jesse Rusak
+2  A: 

Hi, The way to do this is like you said, a combination of a few things.

If you want a gap of 20px between your images, you need to:

First, expand your scroll view's total width by 20px and move it left by 10px.

Second, when you lay out the xLoc of your images, add 20px for each image so they're spaced 20px apart. Third, set the initial xLoc of your images to 10px instead of 0px.

Fourth, make sure you set the content size of your scroll view to add 20px for each image. So if you have kNumImages images and each is kScrollObjWidth, then you go like this:

[scrollView setContentSize:CGSizeMake((kNumImages * (kScrollObjWidth+20)),  kScrollObjHeight)];

It should work after that!

Paul Shapiro
Paul, I'm afraid you are a bit late: this has already been suggested and does work indeed, but thanks anyway.
Andrey Tarantsov
There is one problem with this that I've noticed. If the scrollview has a size smaller than the full size of the screen then you will notice when you scroll that the pages go outside of the specified width. Again this is a problem if your scroll view is smaller than the entire screen width. If anyone has any workarounds, it'd be a big help to hear them.
Paul Shapiro
A clipping mask would work but I haven't been able to find any. It may be possible to overload drawRect for the scroll view to tell it to draw only within a certain rectangle.
Paul Shapiro
Hi again, just so people know how I solved this, I explored subclassing the UIScrollView and overloading drawRect to add a clipping mask via the context drawing one can do there, but it didn't work out. UIScrollView considers its bounds to be the entire length of all the images, so that's probably why. So what I ended up doing since I didn't need the clipped areas to be totally transparent was I created images of what's underneath, and layered them in via UIImageViews in Interface Builder.
Paul Shapiro
+8  A: 

So I don't have enough "rep" to post a comment on the answer above. That answer is correct, but there is a BIG issue to be aware of:

If you're using a UIScrollView in a viewController that's part of a UINavigationController, the navigation controller WILL resize the frame of your scrollView.

That is, you have an app that uses a UINavigationController to switch between different views. You push a viewController that has a scrollView and you create this scrollView in the viewController's -init method. You assign it a frame of (0, 0, 340, 480).

Now, go to your viewController's -viewDidAppear method, get the frame of the scrollView you created. You'll find that the width has been reduced to 320 pixels. As such, paging won't work correctly. You'll expect the scrollView to move 340 pixels but it will, instead, move 320.

UINavigationController is a bit notorious for messing with subviews. It moves them and resizes them to accommodate the navigation bar. In short, it's not a team player -- especially in this case. Other places on the web suggest that you not use UINavigationController if you need precise control over your views' size and locations. They suggest that, instead, you create your own navigationController class based on UINavigationBar.

Well that's a ton of work. Fortunately, there's an easier solution: set the frame of the scrollView in your viewController's -viewDidAppear method. At this point, UINavigationController is done messing with the frame, so you can reset it to what it should be and the scrollView will behave properly.

This is relevant for OS 3.0. I have not tested 3.1 or 2.2.1. I've also filed a bug report with Apple suggesting that they modify UINavigationController with a BOOL such as "-shouldAutoarrangeSubviews" so that we can make that class keep its grubby hands off subviews.

Until that comes along, the fix above will give you gaps in a paginated UIScrollView within a UINavigationController.

Brian, thanks for your warning. Personally, I think it's a good thing that UINavigationController resizes my views to accomodate e.g. interface rotation or in-call status bar. So the approach I prefer is to have my UIScrollView as a child of a generic UIView managed with UINavigationController. This way I'm still in control of positioning, but can take advantage of automatic resizing.
Andrey Tarantsov
You just ended 4 hours of frustration and saved me 8 more. Would upvote twice if I could. This is still an issue in OS 4.0.
alexantd
A: 

I tried to implement the spacing suggested by Roger. I have a method to set the image frame for every image - (void) SetImageViewFrame:(UIImageView *)imageView withPageNumber:(NSInteger)pageNumber { CGSize pageSize = [self pageSize]; float deviceHWRatio = pageSize.height / pageSize.width; float hwRatio = imageView.image.size.height / imageView.image.size.width; float gutterSize;

if ( deviceHWRatio > hwRatio ) { gutterSize = (pageSize.height - pageSize.width*hwRatio)/2; imageView.frame = CGRectMake((pageSize.width + kViewPicSpacing) * pageNumber, gutterSize, pageSize.width, pageSize.width*hwRatio); } else { gutterSize = (pageSize.width - pageSize.height/hwRatio)/2; imageView.frame = CGRectMake((pageSize.width + kViewPicSpacing) * pageNumber + gutterSize, 0, pageSize.height / hwRatio, pageSize.height); } }

I have also set the frame size to

  • (void)viewDidAppear:(BOOL)animated { self.frame = CGRectMake(0.0f, 0.0f, 320 + kViewPicSpacing, 480); }

kViewPicSpacing = 20

The spacing did appear between pictures, however, when i scroll, it will only advance 320px. I did look at self.frame (I subclassed the UIScrollView), and it's 340X480.

Can anybody think of a reason why the paging won't advance 340px?

I have spent 2 days in looking at it and still don't know why.

DaiLak
+4  A: 

Apple has released the 2010 WWDC session videos to all members of the iphone developer program. One of the topics discussed is how they created the photos app!!! They build a very similar app step by step and have made all the code available for free.

It does not use private api either. Here is a link to the sample code download. You will probably need to login to gain access.

http://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645

And, here is a link to the iTunes WWDC page:

http://insideapple.apple.com/redir/cbx-cgi.do?v=2&la=en&lc=&a=kGSol9sgPHP%2BtlWtLp%2BEP%2FnxnZarjWJglPBZRHd3oDbACudP51JNGS8KlsFgxZto9X%2BTsnqSbeUSWX0doe%2Fzv%2FN5XV55%2FomsyfRgFBysOnIVggO%2Fn2p%2BiweDK%2F%2FmsIXj

Jonah
Yup, UIScrollViews within UIScrollViews, simple, nice.
jbm