In my iPhone app I'm looking for a solution where I have some images scrolling horizontally, automatically and infinitely around and around. So if I have four images: first image 1 is shown, then it automatically scrolls out, image 2 is shown, then it scrolls out, ... image 4 is shown, then it scrolls out, and then image 1 is shown again and around it goes... (image1->image2->image3->image4->image1...)
I have been looking for a solution where I use a UIScrollView, but I'm not sure how to implement it. Is there a tutorial or anything on how to do this? How would you solve it? Thanks!
You can implement the ScrollViewDelegate's - (void)scrollViewDidScroll:(UIScrollView *)scrollView method and use the setContentOffset of the scroll view once the content offset is larger than some maximum or less than some minimum.
Before doing that you will have to duplicate the contents of the scroll view at least 3 times, like this: .
Once the scroll offset reaches the duplicated content you will have to set the offset to the original content offset...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.infiniteScrollView) {
CGFloat xOffset = scrollView.contentOffset.x;
CGFloat yOffset = scrollView.contentOffset.y;
if (xOffset > maxOffset) {
xOffset = origOffset + (xOffset - maxOffset);
}
else if (xOffset < minOffset) {
xOffset = origOffset + (xOffset - minOffset);
}
if (xOffset != scrollView.contentOffset.x) {
[scrollView setContentOffset:CGPointMake(xOffset ,yOffset)];
}
}
}
Related
I Have four pages in single uiscrollView and pageing is enabled. Each page may have different height, I Tried to increase the content size of scrollview in scrollViewDidEndDecelerating delegate but it does't help me.
Can any one suggest how to increment contentsize of scrollview in each page differently?
Thanks in Advance.
That is not possible, the content size is the bounds of the scroll view, it is a rectangle, how could it change for each page? Why not scale the pages so they are the same size and use zooming?
I don't think you can do that natively. What you can try however is to disable paging and do it manually.
There is a useful delegate method for that:
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset;
This let's you set where the scrollView will end its scrolling.
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
float offsetY = floorf((*targetContentOffset).y);
float yGoto = 0.0;
// Find out, based on that offsetY in which page you are
// and set yGoto accordingly to the start of that page
// In the following example my pages are 320px each
// I start by only allowing to go 1 page at a time, so I limit
// how far the offsetY can be from the current scrollView offset
if(offsetY > scrollView.contentOffset.y + 160){
// Trying to scroll to more than 1 page after
offsetY = scrollView.contentOffset.y + 160;
}
if(offsetY < scrollView.contentOffset.y - 160){
// Trying to scroll to more than 1 page before
offsetY = scrollView.contentOffset.y - 160;
}
if(offsetY < 0){
// Trying to scroll to less than the first element
// This is related to the elastic effect
offsetY = 0;
}
if(offsetY > scrollView.contentSize.height-320){
// Trying to scroll to more than the last element
// This is related to the elastic effect
offsetY = scrollView.contentSize.height - 1;
}
// Lock it to offsets that are multiples of 320
yGoto = floorf(offsetY);
if((int)offsetY % 320 > 160){
int dif = ((int)offsetY % 320);
yGoto = offsetY + 320 - dif;
}else{
int dif = ((int)offsetY % 320);
yGoto = offsetY - dif;
}
yGoto = floorf(yGoto); // I keep doing this to take out non integer part
scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
(*targetContentOffset) = CGPointMake(scrollView.contentOffset.x,yGoto);
}
Hope it helps!
I have a UICollectionView with 6 pages, and paging enabled, and a UIPageControl. What I want is, when I came to the last page, if I drag to right, UICollectionView reloads from first page seamlessly.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// The key is repositioning without animation
if (collectionView.contentOffset.x == 0) {
// user is scrolling to the left from image 1 to image 10.
// reposition offset to show image 10 that is on the right in the scroll view
[collectionView scrollRectToVisible:CGRectMake(collectionView.frame.size.width*(pageControl.currentPage-1),0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
else if (collectionView.contentOffset.x == 1600) {
// user is scrolling to the right from image 10 to image 1.
// reposition offset to show image 1 that is on the left in the scroll view
[collectionView scrollRectToVisible:CGRectMake(0,0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
pageControlUsed = NO;
}
It doesn't work like I want. What can I do?
Here's what I ended up with for my UICollectionView (horizontal scrolling like the UIPickerView):
#implementation UIInfiniteCollectionView
- (void) recenterIfNecessary {
CGPoint currentOffset = [self contentOffset];
CGFloat contentWidth = [self contentSize].width;
// don't just snap to center, since this might be done in the middle of a drag and not aligned. Make sure we account for that offset
CGFloat offset = kCenterOffset - currentOffset.x;
int delta = -round(offset / kCellSize);
CGFloat shift = (offset + delta * kCellSize);
offset += shift;
CGFloat distanceFromCenter = fabs(offset);
// don't always recenter, just if we get too far from the center. Eliza recommends a quarter of the content width
if (distanceFromCenter > (contentWidth / 4.0)) {
self.contentOffset = CGPointMake(kCenterOffset, currentOffset.y);
// move subviews back to make it appear to stay still
for (UIView *subview in self.subviews) {
CGPoint center = subview.center;
center.x += offset;
subview.center = center;
}
// add the offset to the index (unless offset is 0, in which case we'll assume this is the first launch and not a mid-scroll)
if (currentOffset.x > 0) {
int delta = -round(offset / kCellSize);
// MODEL UPDATE GOES HERE
}
}
}
- (void) layoutSubviews { // called at every frame of scrolling
[super layoutSubviews];
[self recenterIfNecessary];
}
#end
Hope this helps someone.
I've been using the Street Scroller sample to create an infinite scroller for images. That works fine until I wanted to set pagingEnabled = YES; Tried tweaking around the recenterIfNecessary code and finally realized that it's the contentOffset.x that has to match the frame of the subview that i want visible when paging stops. This really isn't going to work in recenterIfNecessary since you have no way of knowing it will get called from layoutSubviews. If you do get it adjusted right, the subview may pop out from under your finger. I do the adjustment in scrollViewDidEndDecelerating. So far I haven't had problems with scrolling fast. It will work and simulate paging even when pagingEnabled is NO, but it looks more natural with YES.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[super scrollViewDidEndDecelerating:scrollView];
CGPoint currentOffset = [self contentOffset];
// find the subview that is the closest to the currentOffset.
CGFloat closestOriginX = 999999;
UIView *closestView = nil;
for (UIView *v in self.visibleImageViews) {
CGPoint origin = [self.imageContainerView convertPoint:v.frame.origin toView:self];
CGFloat distanceToCurrentOffset = fabs(currentOffset.x - origin.x);
if (distanceToCurrentOffset <= closestOriginX) {
closestView = v;
closestOriginX = distanceToCurrentOffset;
}
}
// found the closest view, now find the correct offset
CGPoint origin = [self.imageContainerView convertPoint:closestView.frame.origin toView:self];
CGPoint center = [self.imageContainerView convertPoint:closestView.center toView:self];
CGFloat offsetX = currentOffset.x - origin.x;
// adjust the centers of the subviews
[UIView animateWithDuration:0.1 animations:^{
for (UIView *v in self.visibleImageViews) {
v.center = [self convertPoint:CGPointMake(v.center.x+offsetX, center.y) toView:self.imageContainerView];
}
}];
}
I have not used UICollectionView for infinite scrolling, but when doing it with a UIScrollView you first adjust your content offset (instead of using scrollRectToVisible) to the location you want. Then, you loop through each subview in your scroller and adjust their coordinates either to the right or left based on the direction the user was scrolling. Finally, if either end is beyond the bounds you want them to be, move them to the far other end. Their is a very good WWDC video from apple about how to do infinite scrolling you can find here: http://developer.apple.com/videos/wwdc/2012/
So I have a UIScrollview that scrolls horizontally with paging through a bunch of different UIImageViews containing user avatars. But, I want to only load the avatars from their URL IFF they are actually showing on the screen (4 are showing at a time, but I can scroll through like 30 or more or less). Is there a way of detecting which UIImageView/ which page it is on? Or does anyone have any general recommendations or ideas for how I should go about this problem? Thank you in advance.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
CPoint offset = scrollView.contentOffset;
int index = offset.x / pageWidth;
switch (index) {
case 0:
//first ImageView
break;
case 1:
//second ImageView
break;
default:
break;
}
}
Or If you tag your imageViews (starting at 1) you can retrieve them via:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
CPoint offset = scrollView.contentOffset;
int index = offset.x / pageWidth;
UIImageView *visibileImageView = (id)[scrollView viewWithTag:index+1];
}
You can use scrollViewDidScroll to detect the position at any point if you use the modulus:
int index = (offset.x - offset.x % pageWidth)/pageWidth;
Does anyone know how to determine which direction a scroll view will move when the user lifts their finger, i.e. when scrollViewDidEndDragging gets called?
Specifically, when the scroll view is set to paging.
Most of the time, I can just track and check the contentOffset in scrollViewDidScroll, however, there are special cases where the user flicks the screen quickly. In some cases a LEFT-RIGHT-LEFT move will scroll to the next page, and in others, the same pattern will remain on the same page.
I'm guessing it has something to do the with acceleration (difference between the last few points) of the touch.
(I'm on iOS4.3 on an iPad)
You don't use timer, which is very expensive operation. Lock the direction of your scrollView first to move only up/down and left/right [I guess that's what you want]. And then use following code to determine...
CGPoint start, end;
- (void)scrollViewWillBeginDragging:(UIScrollView *)sender {
start = self.scrollView.contentOffset;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)sender willDecelerate:(BOOL)decelerate {
end = self.scrollView.contentOffset;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender {
NSLog(#"%d %d",(int)start.x,(int)start.y);
NSLog(#"%d %d",(int)end.x,(int)end.y);
}
Here is my solution works fine for me hope it helps
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.frame.size.width;
//Old Solution
//Switch the indicator when more than 30% of the previous/next page is visible
//You can vary the percentage
//int page = floor((scrollView.contentOffset.x - pageWidth * 0.3) / pageWidth) + 1;
//New Solution
_pageControl.currentPage = (int)scrollView.contentOffset.x / (int)pageWidth;
}
I quit the old solution as the new one seems more slick
Implement the following UIScrollViewDelegate Method.
You can compare current content offset and targetContentOffset then get the direction.
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
CGPoint newOffset = CGPointMake(targetContentOffset->x, targetContentOffset->y);
if (newOffset.x > scrollView.contentOffset.x) {
NSLog(#"Scrolling direction is left");
}else{
NSLog(#"Scrolling direction is right");
}
}
This is a tricky one, iOS 5 has the answer, but that no good for you.
Perhaps a hacky workaround is to set a NSTimer for a small time interval (say 0.1 seconds) after scrollViewDidEndDragging finished. Then compare the content offsets.
If you want to know if the scroll view will actually go to the next page, perhaps you could check if the content offset has gone more than 1/2 way. You could read the content offset on scrollViewDidScroll: and do a bit of maths to determine if its more than 1/2 way.
In addition to saurin response.
Her solution work fine but if you are using pagination and you are in the end of scroll content size you will have to test if we really have a scroll to the next 'page' or not:
CGPoint start, end;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
start = scrollView.contentOffset;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
end = scrollView.contentOffset;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (start.x > end.x && end.x>0)
{
NSLog(#"on the previous left page");
}
if (start.x < end.x && end.x < scrollView.contentOffset.x)
{
NSLog(#"on the next right page");
}
}
When somebody does a wipe gesture to scroll the content from left to right, I would like to have a background image scrolling into the same direction, but at a different speed. Much like what these classic games did do 20 years ago (remember that, anybody????)
I accomplished this by using two UIScrollView instances. The first is where the actual content is displayed, and the second (which is behind the first in z-order) is where I have my slower-moving background. From there the top UIScrollView has a delegate attached to it that gets notified when the contentOffset changes. That delegate, in turn, programatically sets the contentOffset of the background scroller, multiplied against a constant to slow the scroll down relative to the foreground. So, for instance, you might have something like:
// Defined as part of the delegate for the foreground UIScrollView
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UIScrollView* scroll_view(static_cast<UIScrollView*>(bkg_scroller_m.view));
CGPoint offset(scrollView.contentOffset);
offset.x = offset.x / 3;
offset.y = offset.y / 3;
// Scroll the background scroll view by some smaller offset
scroll_view.contentOffset = offset;
}
You can easily do this by implementing scroll view did scroll with a UIImageView under it...
You'll end up with something like this... with the backgroundImageView being a UIImageView added to the view before the subview... you can layer as much image views as you want without performance issues
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
float factor = scrollView.contentOffset.x / (scrollView.contentSize.width - 320);
if (factor < 0) factor = 0;
if (factor > 1) factor = 1;
CGRect frame = backgroundImageView.frame;
frame.origin.x = factor * (320 - backgroundImageView.frame.size.width);
backgroundImageView.frame = frame;
}
You can do it with CoreAnimation. You'll want to hook into the scrollViewDidEndDragging:willDecelerate: and scrollViewWillBeginDecelerating: UIScrollViewDelegate methods. Then begin an Animation on your image by changing the center position. See this SO article for more on animations.
For example you have multiple scrollviews, want them scroll difference speed. here is the modification code base on Salamatizm answer:
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
float factor = scrollView.contentOffset.x / (scrollView.contentSize.width - screenSize.width);
if (factor < 0) factor = 0;
if (factor > 1) factor = 1;
CGSize parralaxSize = self.parralaxBackgroundView.contentSize;
CGPoint parallaxOffset = CGPointMake(-(factor * (screenSize.width - parralaxSize.width)), 0);
[self.parralaxBackgroundView setContentOffset:parallaxOffset animated:NO];