Basically, I want to have an app with only one view that has an image on it. I want to be able to swipe left or right and have the first image go out of the view and the second image to come in. The images are the same and I want it to look like they are connected (like scrolling down a rope where the pattern just repeats, but it looks like a constant scroll). I need it to be able to change the image or restart after a series of swipes. I know that I need to turn pagination ON in the UIScrollView, but I am new to iOS and am having trouble.
Ultimately, I want to have the iPhone vibrate every so-and-so swipes (and restart the pattern).
I'm sure that there are a lot of ways to do this (i.e. a TableView) so feel free to just point me in the direction of some references if the answer is tedious to explain.
Thanks!
FOLLOW UP:
I found an Apple example that did very nearly what I wanted to do. I made a lot of adjustments to it, but I'm banging my head against a wall trying to get the images to cycle. Here is what I think is the offending code, but I'm not sure what the solution is, as the ScrollView is functional, it just doesn't reset the center to the current view. Any ideas?
- (void)layoutScrollImages
{
UIImageView *view = nil;
NSArray *subviews = [scrollView1 subviews];
// reposition all image subviews in a horizontal serial fashion
CGFloat curXLoc = 0;
for (view in subviews)
{
if ([view isKindOfClass:[UIImageView class]] && view.tag > 0)
{
CGRect frame = view.frame;
frame.origin = CGPointMake(curXLoc, 0);
view.frame = frame;
curXLoc += (kScrollObjWidth);
}
}
// set the content size so it can be scrollable
[scrollView1 setContentSize:CGSizeMake((kNumImages * kScrollObjWidth), [scrollView1 bounds].size.height)];
}
I'd just use a UIScrollView. Set the contentWidth to be 3 times the width/height of the view (for 3 pages) and set the contentOffset to be the center 'page' (view.bounds.size.width or view.bounds.size.height depending on whether you're scrolling horizontally/vertically respectively) . You'll need to setup a delegate for the UIScrollView (probably the view controller) and implement - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView. This will be called when the scroll view has finished decelerating. Once it has finished decelerating, reset the contentOffset back to the center view. This should give the impression of an infinite scroll. You can also set a counter to increment in the scrollViewDidEndDecelerating method to increment the counter or initiate the vibration.
You shouldn't need to keep repositioning the images. Just set the images once in the scrollView:
//Horizontal arrangement
UIImage *image = [UIImage imageNamed:#"nameOfImage.png"];
UIImageView *imageView1 = [[UIImageView alloc] initWithImage:image];
UIImageView *imageView2 = [[UIImageView alloc] initWithImage:image];
UIImageView *imageView3 = [[UIImageView alloc] initWithImage:image];
NSArray *imageViews = [NSArray arrayWithObjects:imageView1, imageView2, imageView3];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
[self.view addSubview: scrollView]; //This code assumes it's in a UIViewController
CGRect cRect = scrollView.bounds;
UIImageView *cView;
for (int i = 0; i < imageViews.count; i++){
cView = [imageViews objectAtIndex:i];
cView.frame = cRect;
[scrollView addSubview:cView];
cRect.origin.x += cRect.size.width;
}
scrollView.contentSize = CGSizeMake(cRect.origin.x, scrollView.bounds.size.height);
scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0); //should be the center page in a 3 page setup
So the images are setup, you don't need to mess with them anymore. Just reset the contentOffset when the scroll views stops (note: you need to make sure you're the delegate of the scroll view or you'll not receive the message when the scroll view stops):
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0);
}
Please forgive any typos. I wrote it out by hand.
Look on cocoacontrols.com for a custom photo album view. As for the vibration, this code snippet vibrates the phone (make sure you link to and #import <AudioToolbox/AudioToolbox.h>):
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
I need to create a main menu for my app with a UIScrollView. I have some images inside it that can be clicked. When I scroll the UIScrollView I need that on the background there are other two views that move creating a parallax effect.
Can someone provide me a sample code? I'm trying to work with
-(void) scrollViewDidScroll:(UIScrollView *)scrollView
but I cannot find any productive example about applying on my project.
How's this? The imageView will scroll up half as fast as the UIScrollView.
float y = scrollView.contentOffset.y;
CGRect imageFrame = self.imageView.frame;
imageFrame.origin.y = y/2;
self.imageView.frame = imageFrame;
This GitHub repository has an amazing implementation that works quite well:
https://github.com/ralfbernert/RBParallaxScrolling
Here's my test of the code, using a UIScrollView with pagination (in the front) and an image in the background:
http://clrk.it/211o3h0A053m
The bit of code that does this parallax trick works as follows:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == _scrollView) {
float speedFactor = _headerImageScrollView.contentSize.width / _scrollView.contentSize.width;
[_headerImageScrollView setContentOffset:CGPointMake(speedFactor * _scrollView.contentOffset.x, 0)];
}
}
In this code, I've got a UIScrollView that contains a UIImageView; I call this _headerImageScrollView.
In front of it, I have a second UIScrollView with pagination and the three labels. That one's called _scrollView.
I'd like to have a gruped table where the first section has 2 row and 1 imageview like contacts app.
something like this: screeshot
How can I do that?
Thanks,
Max
The other solutions require you to create your own background images and use two tableviews which is not convenient. What I did was subclass UITableViewCell and indented the background views as such:
#define INDENT_WIDTH 84
...
- (void)layoutSubviews
{
[super layoutSubviews];
//Indent the background views.
CGRect frame = self.backgroundView.frame;
frame.origin.x = frame.origin.x + INDENT_WIDTH;
frame.size.width = frame.size.width - INDENT_WIDTH;
self.backgroundView.frame = frame;
self.selectedBackgroundView.frame = frame;
//Also indent the UIImageview that contains like a shadow image over the backgroundviews (in grouped tableview style only).
for (UIView *subview in self.subviews) {
if ([subview isKindOfClass:[UIImageView class]]) {
CGRect frame = subview.frame;
frame.origin.x = frame.origin.x + INDENT_WIDTH;
frame.size.width = frame.size.width - INDENT_WIDTH;
subview.frame = frame;
}
}
}
Since the content view has a transparent background color you can place a UIImageView (e.g on your storyboard cell prototype) on the left side and you should get the same effect as the "Add Contact" view in the Contacts App.
Please see this question:
Is it possible to adjust the width of a UITableViewCell?
It seems there's no convenient way to actually reduce the width of a cell (or cell group).
You can achieve the same in the screenshot by:
(1) A parent view controller with a view having a background color similar to that of a UITableViewStyleGrouped.
(2) Add the photo on a UIImageView which would be a subView to (1)
(3) Add the UITableView (Grouped Style) on the right side again as a subview to (1)
Set the frames of both subviews properly and accordingly for the layout in the screenshot and use delegation to "logically connect" both subviews.
Edit: The background color can be achieved using [UIColor colorWithPatternImage:(UIImage *)image]. Just crop the background from any sample app on the iphone simulator.
I have a paging UIScrollView that pages through multiple full screen images. I am tiling the pages, queuing and dequeuing the UIViews dynamically as the scroll view pages through the collection of images, based on Apple example code.
I have a toolbar button the calls scrollRectToVisible:animated: to move the UIScrollView to a specific image. That works perfectly.
The problem is that if you then do a single touch in the UIScrollView, it scrolls back to the page it was displaying before the button was touched and the scrollRectToVisible:animated: method call scrolled the view.
If your touch is moving, the UIScrollView scrolls as expected, and subsequent touches do not cause the UIScrollView to move back to the original page.
How do I prevent this behavior?
Thanks
jk
You need to use content offset rather than scrollRectToVisible, eg:
[pagingScrollView setContentOffset:[self offsetForPageAtIndex:page] animated:YES];
where offsetForPageAtIndex looks like this:
- (CGPoint)offsetForPageAtIndex:(NSUInteger)index {
CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
CGPoint offset;
offset.x = (pagingScrollViewFrame.size.width * index);
offset.y = 0;
return offset;
}
This is based off the Apple "photoscroller" example code from WWDC 2010, which had a frameForPagingScrollView that looks like this:
- (CGRect)frameForPagingScrollView {
CGRect frame = [[UIScreen mainScreen] bounds];
frame.origin.x -= PADDING;
frame.size.width += (2 * PADDING);
return frame;
}
A full copy of that version of the Photoscroller sample code is here:
https://github.com/jogu/WWDC-2010/tree/master/PhotoScroller
Though Joseph's answer sent me the right way, I had some odd behaviour when rotating the device. I found that using offset.x = (pagingScrollView.bounds.size.width * index); instead worked better.
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.
Note that this answer is quite old. The basic concept still works but
you should not be hard coding view sizes in iOS7 and 8. Even if you ignore
that advice, you should not use 480 or 330.
Have you tried making the frame of the UIScrollView slightly 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 = kViewFrameWidth;
scrollFrame.size.height = 480;
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(kViewFrameWidth * 2,0,320,480);
UIImage* centerImage = [UIImage imageNamed:#"ScrollTestImageC.png"];
UIImageView* centerView = [[UIImageView alloc] initWithImage:centerImage];
centerView.backgroundColor = [UIColor grayColor];
centerView.frame = CGRectMake(kViewFrameWidth,0,320,480);
[myScrollView addSubview:leftView];
[myScrollView addSubview:rightView];
[myScrollView addSubview:centerView];
[myScrollView setContentSize:CGSizeMake(kViewFrameWidth * 3, 480)];
[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.
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.
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
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!
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 ?
Maybe you want to try UIScrollView's contentInset property?
myScrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 10.0);
I just thought I'd add here for posterity the solution I ended up going with. For a long time I've been using Bryan's solution of adjusting the frame in -viewDidAppear, and this has worked brilliantly. However since iOS introduced multitasking I've been running into a problem where the scroll view frame gets changed when the app resumes from the background. In this case, -viewDidAppear was not being called and I couldn't find a delegate method that would be called at the right time to reverse the change. So I decided to make my scroll view a subview of my View Controller's view, and this seemed to fix the problem. This also has the advantage of not needing to use -viewDidAppear to change the frame - you can do it right after you create the scroll view. My question here has the details, but I'll post them here as well:
CGRect frame = CGRectMake(0, 0, 320, 460);
scrollView = [[UIScrollView alloc] initWithFrame:frame];
// I do some things with frame here
CGRect f = scrollView.frame;
f.size.width += PADDING; // PADDING is defined as 20 elsewhere
scrollView.frame = f;
[self.view addSubview:scrollView];
To avoid messing with UIScrollView's frame, you could subclass UIScrollView and override layoutSubviews to apply an offset to each page.
The idea is based on the following observations:
When zoomScale !=1, the offset is zero when it is at the left / right edge
When zoomScale ==1, the offset is zero when it is at the visible rect centre
Then the following code is derived:
- (void) layoutSubviews
{
[super layoutSubviews];
// Find a reference point to calculate the offset:
CGRect bounds = self.bounds;
CGFloat pageGap = 8.f;
CGSize pageSize = bounds.size;
CGFloat pageWidth = pageSize.width;
CGFloat halfPageWidth = pageWidth / 2.f;
CGFloat scale = self.zoomScale;
CGRect visibleRect = CGRectMake(bounds.origin.x / scale, bounds.origin.y / scale, bounds.size.width / scale, bounds.size.height / scale);
CGFloat totalWidth = [self contentSize].width / scale;
CGFloat scrollWidth = totalWidth - visibleRect.size.width;
CGFloat scrollX = CGRectGetMidX(visibleRect) - visibleRect.size.width / 2.f;
CGFloat scrollPercentage = scrollX / scrollWidth;
CGFloat referencePoint = (totalWidth - pageWidth) * scrollPercentage + halfPageWidth;
// (use your own way to get all visible pages, each page is assumed to be inside a common container)
NSArray * visiblePages = [self visiblePages];
// Layout each visible page:
for (UIView * view in visiblePages)
{
NSInteger pageIndex = [self pageIndexForView:view]; // (use your own way to get the page index)
// make a gap between pages
CGFloat actualPageCenter = pageWidth * pageIndex + halfPageWidth;
CGFloat distanceFromRefPoint = actualPageCenter - referencePoint;
CGFloat numOfPageFromRefPoint = distanceFromRefPoint / pageWidth;
CGFloat offset = numOfPageFromRefPoint * pageGap;
CGFloat pageLeft = actualPageCenter - halfPageWidth + offset;
view.frame = CGRectMake(pageLeft, 0.f, pageSize.width, pageSize.height);
}
}