What's the simplest way to have a scroll view (with pagingEnabled set to YES) have a page width set to something other than the scroll view's bounds?
Let me give an example. Suppose I have a scroll view with 10 items, each 150 pixels wide, and my scroll view is 300 pixels wide. If I start with views 1 and 2 visible and scroll horizontally to the right, I want the next "page" to show items 2 and 3. If I scroll one more page to the right, I would see items 3 and 4.
Has anyone done this? If not, what strategy would you use?
Alexander Repty just blogged about this very topic: http://blog.proculo.de/archives/180-Paging-enabled-UIScrollView-With-Previews.html. Short answer: it's not as simple as just changing the width of the 'page', but it's not too hard.
Mike,
use the approach from my blog that Ben linked you to. Make the UIScrollView half the size of the surrounding view (i.e. 150px) and move it to the left instead of leaving it centered.
That should give you exactly the behaviour you wanted.
Cheers,
Alex
It can surely be done with the help of scrollview delegate methods & content-offset.
Below is the detailed code for the same.
int start;
int end;
int k=0;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView1
{
pageControlUsed = NO;
start = scrollView.contentOffset.x;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView1 willDecelerate:(BOOL)decelerate
{
end = scrollView.contentOffset.x;
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView1{
int diff = end-start;
if (diff>0)
{
k=k+150;
[scrollView setContentOffset:CGPointMake(k, 0) animated:YES];
}
else {
k=k-150;
[scrollView setContentOffset:CGPointMake(k, 0) animated:YES];
}
}
Simply override scrollViewWillEndDragging
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
CGFloat pageWidth = 160;
targetContentOffset->x = pageWidth * (int)(targetContentOffset->x / pageWidth);
}
UIScrollView *album = [[UIScrollView alloc] init];
album.translatesAutoresizingMaskIntoConstraints = NO;
album.pagingEnabled = YES;
album.clipsToBounds = NO;
album.showsHorizontalScrollIndicator = NO;
album.contentInset = UIEdgeInsetsMake(0, -50, 0, -50);
[view addSubview:album];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-50-[_album(220)]" options:0 metrics:metrics views:views]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_album(300)]" options:0 metrics:metrics views:views]];
this works fine on iOS 7.1~ 8.2
Related
I've a UIScrollView of size (320,160). I'm adding some UIImageView into it, which are of size (213,160). The first UImageView starting from 54 (x) and so on, I've added a space of 5.0 in between each UIImageView. I've also enabled pagingEnable in IB & in coding. What my problem is its not properly working as per its property! When I scroll it should show me UIImageViews in each single page instead it showing me something like see screenshot I want output something like this see output screenshot
Where I'm doing wrong? I also having function of< (previous) & > (next) there to show images. I've asked one question yesterday which was I accepted however my requirement is little change and it won't become my solution. see my question.
Is there any special property that I've to set, or some logic I should implement? All examples I've checked and tried but I find that my requirement is some special. Point me! Thanks.
EDITED:
- (void) setImages
{
CGFloat contentOffset = 0.0f;
for (int i=0; i<[arrImgUrls count]; i++)
{
CGRect imageViewFrame = CGRectMake(contentOffset, 0.0f, 213, scrollImages.frame.size.height);
AsyncImageView *asyncImageView = [[AsyncImageView alloc] initWithFrame:imageViewFrame];
[asyncImageView.layer setMasksToBounds:YES];
NSString *urlImage = [arrImgUrls objectAtIndex:i];
NSURL *url = [NSURL URLWithString:urlImage];
[asyncImageView loadImageFromURL:url];
[scrollImages addSubview:asyncImageView];
contentOffset += asyncImageView.frame.size.width+increment;
[asyncImageView release];
}
scrollImages.contentSize = CGSizeMake([arrImgUrls count] * scrollImages.frame.size.width, scrollImages.frame.size.height);
scrollImages.pagingEnabled = YES;
scrollImages.clipsToBounds = NO;
}
-(IBAction)prevImage:(id)sender
{
_currentImage--;
[btnNext setEnabled:YES];
if (_currentImage==0)
{
[btnPrev setEnabled:NO];
[scrollImages setContentOffset:CGPointMake((_currentImage*imageWidth), 0) animated:YES];
return;
}
NSLog(#"previous:mult %d inc %d current %d",_currentImage*imageWidth,increment*_currentImage,_currentImage);
int nextImage=_currentImage+2;
[scrollImages setContentOffset:CGPointMake((((_currentImage*imageWidth)-(increment*_currentImage)))+(nextImage*increment), 0) animated:YES];
}
-(IBAction)nextImage:(id)sender
{
_currentImage++;
NSLog(#"next:mult %d inc %d current %d",_currentImage*imageWidth,increment*_currentImage,_currentImage);
[scrollImages setContentOffset:CGPointMake((_currentImage*imageWidth)+(increment*_currentImage), 0) animated:YES];
[btnPrev setEnabled:YES];
if (_imageCount-1 == _currentImage)
{
[btnNext setEnabled:NO];
}
}
Paging scroll views alway page multiples of their frame size. So in your example paging is always +320.
This behavior is good if you have content portions matching the frame of the scroll view.
What you have to do, is giving your scroll view a width of 213 and set its clipsToBounds property to NO.
After that your scroll view pages exactly how you want and you see what's left and right outside the frame.
Additionally you have to do a trick to make this left and right area delegate touches to the scroll view.
It's greatly explained in this answer.
You are forgetting to set scrollview content size.
make sure that you have set the content size to fit N number of images.
if you want to scrollview to scroll for 10 images with an image on each page
set scrollView contentSize as
[scrollView setContentSize:CGSizeMake(320 * 10,200)];
I'm trying to adjust my subviews in a UIScrollView subclass, but I don't want to disturb the scroll indicators. There doesn't seem to be any public interface to access these and I want to check if a view is one of the scroll indicators or not (so that I can ignore it).
UIScrollView.h declares these two iVars:
UIImageView* _verticalScrollIndicator;
UIImageView* _horizontalScrollIndicator;
...but I tried the following and got a linker error:
for(UIView* v in self.subviews)
{
// Ignore the scroll-indicator views
if( (v == _horizontalScrollIndicator) ||
(v == _verticalScrollIndicator))
{
continue;
}
// View is one of mine - do stuff to it...
}
Apple obviously don't want you messing with these, in which case they should do something clever so that the array returned from subviews doesn't include them (come on Apple, it's not that hard!), but until then how can I ignore them?
This ended up being my work-around
BOOL showsVerticalScrollIndicator = self.showsVerticalScrollIndicator;
BOOL showsHorizontalScrollIndicator = self.showsHorizontalScrollIndicator;
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
// loop through self.subviews here
self.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
self.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
Presumably you're in control of the views that you put in the UIScrollView. Why not maintain your own array of just those views? That way, you are safe against any future changes in the implementation of UIScrollView.
i'm up against the same issue and i'm starting to think about looking at the values of those two UIImageViews to figure out if its them or not. they do have a pretty unique signature:
<UIImageView: 0x8ab3e00; frame = (313 812.5; 7 3.5); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x8ab9740>>
<UIImageView: 0x8ab3e90; frame = (314.5 522; 3.5 341); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x8ab3f20>>
amongst other hooks that you could listen for. not ideal but not much in the way of options at this point...
heres an example. in this case, i was looping through the scrollview's subviews to figure out the optimal contentSize height for the scrollview based on the subviews when it started coming out with some funky values which lead me to think that it was the scrollbars that were screwing with me...
CGRect contentRect = CGRectZero;
for (UIView *view in self.myScrollView.subviews) {
//exclude scroll bars, the width/height is 3.5 for non-retina or 7
//for retina. you can put some logic in here if you like to check
//the appropriate 3.5/7.0 values for the respective screen densities
if(view.frame.size.width > 7.0f && view.frame.size.height > 7.0f) {
contentRect = CGRectUnion(contentRect, view.frame);
}
}
container.contentSize = CGSizeMake(container.contentSize.width, contentRect.size.height);
if checking the frame of each UIView isn't specific enough for you, you could also ensure that it is a UIImageView.
i'm thinking this is the only "true" solution at this point. i'd personally rather not have to maintain another array of views.
EDIT:
here's my consolidated method that i use:
-(bool)isProbablyAScrollBar:(UIView*)view {
CGFloat screenScale = [[UIScreen mainScreen] scale];
return (([view isKindOfClass:[UIImageView class]]) && (view.frame.size.width <= (screenScale*3.5f) || view.frame.size.height <= (screenScale*3.5f)));
}
Are the scroll indicator views added and removed throughout the lifetime of the scroll view, or are they just added once and hidden and shown as necessary? If it's the latter, you could store your own references to them in init... instead of using the private ivars and proceed as you have tried to already.
My solution was:
UIView* MYContentView=[[UIView alloc] initWithRect: CGrectMake:(0,0,Scrollview.frame.size.width, scrollview.frame.size.height)];
[ScrollView addsubview: MYContentView];
Then i added all the images to the MYContentView and later Rescaled the MYContentView and
[ScrollView setContentSize:Cgsize];
Now you can Enumerate all Images in the MYContentView without worrying about the scrollbars.
Doing:
while ([[MYContentView subviews] count]>0) {
[[[MYContentView subviews]lastObject] removeFromSuperview];
}
Removed only the images. Hope this helps
Based on MrTristan's method. I think he mistakenly multiplied with screen scale.
+ (NSArray <UIView *> *)reservedSubviewsInUIScrollView:(UIScrollView *)view
{
BOOL (^isProbablyAScrollBar)(UIView *s) = ^BOOL(UIView *s) {
return [s isKindOfClass:[UIImageView class]] && (s.frame.size.width <= 3.5 || s.frame.size.height <= 3.5);
};
NSMutableArray <UIView *> *reserved = [NSMutableArray new];
for(UIView *s in view.subviews)
{
if(isProbablyAScrollBar(s))
{
[reserved addObject:s];
}
}
return reserved;
}
I have a view that have some images, text, and more to show and two button in left and right when user click on left or righr button page should get side and another page should get open with new info.
How i can do it please help.
try with this link.. You will get the logic to solve your problem. Place a button on each side where you want & use the logic from this thread
Enable paging for the scrollview.
When a button is tapped modify the content offset of the scrollview by the width of the scrollview using setContentOffset:animated:
Use this code
-(IBAction)prevButtonPage:(id) sender
{
CGFloat xOffset = pagingScrollView.contentOffset.x;
CGFloat yOffset = pagingScrollView.contentOffset.y;
if (pagingScrollView.contentOffset.x != pagingScrollView.frame.origin.x)
{
[pagingScrollView setContentOffset:CGPointMake(xOffset- 320, yOffset) animated:YES];
// [arrayIndia objectAtIndex:i];
}
NSLog(#" custom x==%f %f", pagingScrollView.contentOffset.x, pagingScrollView.contentSize.width);
}
-(IBAction)nextButtonPage:(id) sender
{
CGFloat xOffset = pagingScrollView.contentOffset.x;
CGFloat yOffset = pagingScrollView.contentOffset.y;
if ((pagingScrollView.contentOffset.x != pagingScrollView.frame.origin.x)&&(pagingScrollView.contentOffset.x !=[imageArray count]) )
{
[pagingScrollView setContentOffset:CGPointMake(xOffset + 320, yOffset) animated:YES];
}
NSLog(#" custom x==%f %f", pagingScrollView.contentOffset.x, pagingScrollView.contentSize.width);
}
I'm using an UIScrollView and I have an image that indicates to the user that there is more content that they can scroll through vertically. I would like this image to be hidden when the scrollview is all the way at the bottom. Is there a way to do this? Would I have to subclass UIScrollView and make my own?
your scroll view's delegate should repsond to scrollViewDidEndScrollingAnimation: and use that to check where you are
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
// Get some details about the view
CGSize size = [scrollView frame].size;
CGPoint offset = [scrollView contentOffset];
CGSize contentSize = [scrollView contentSize];
// Are we at the bottom?
if (-offset.y + size.height <= contentSize.height)
NSLog(#"bottom");
else
NSLog(#"not bottom");
}
NB The if statement was done in my head so it might be the wrong way round ;)
I'm using a UIScrollerView and I found that it tends to "snap" back to a previous position when I scroll it.
I would like to simulate the behavior if the UItableView's scroller, that stays in the position where the user released his finger.
The properties set:
[scroller setCanCancelContentTouches:NO];
scroller.clipsToBounds = YES;
[scroller setScrollEnabled:YES];
scroller.userInteractionEnabled = YES;
scroller.alwaysBounceHorizontal = YES;
scroller.alwaysBounceVertical= NO;
scroller.showsVerticalScrollIndicator = NO;
scroller.scrollsToTop = NO;
scroller.bounces = NO;
scroller.pagingEnabled = NO;
There a few possible reasons for the snap back.
One could be that you have pagingEnabled set to YES which means that when the user lets go their finger, it will snap to the closest page boundary.
Another is that you have the contentSize set incorrectly. This is not the problem if you can actually scroll to the top and bottom and leave it there without it bouncing away.
- (void)scrollViewDidScroll:(UIScrollView *)sender {
int scrollDirection;
if (lastContentOffset > self.scrollView.contentOffset.x){
scrollDirection = RIGHT;
NSLog(#"right");
}
else if (lastContentOffset < self.scrollView.contentOffset.x) {
scrollDirection = LEFT;
NSLog(#"left");
lastContentOffset = self.scrollView.contentOffset.x;
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
lastContentOffset = self.scrollView.contentOffset.x;
}