I am having some troubles making my UIScrollView to exhibit both scrolling and zooming. My class (which sub-classes UIScrollView) has a UIView as a sub-view, and in this sub-view I draw on a CALayer. As soon as the app launches, I can scroll the view, but as soon as I zoom using the pinch gesture, the scrolling feature stops working, and only the zoom works. My code:
bpmGraphView = [[UIView alloc] init];
//--- configure the scroll-view and add the graph-view as a subview
[self setZoomScale:1.0];
[self setContentSize:CGSizeMake(1000.0, 169.0)];
[self setMultipleTouchEnabled:YES];
[self setScrollEnabled:YES];
self.maximumZoomScale = 20.0;
self.minimumZoomScale = 0.05;
self.clipsToBounds = YES;
self.delegate = self; //--- set the scroll-view delegate to self
[self addSubview:bpmGraphView];
And the delegate method:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return [self bpmGraphView];
}
Any ideas?
EDIT
Some dimensions: My UIScrollView is 320x169 and it is positioned 7 pixels to the right of the left edge of the screen. The (scrollable) UIView inside it should have the same height, but start 30 pixels to the left of the UIScrollView, and end somewhere far to the right - 1000 is just a test number. The sublayer I add to UIView should have the same size and position as it's superview.
I will init the UIView with the proper frame and report any changes in the behavior of the app.
at a first look i just notice a big ContentSize width and a small height... are you sure you need it to be 1000 x (just) 169?
And which are the dimensions of bpmGraphView?
And why don't you init bpmGraphView with a dimension? It's a UIView, you should init it giving it a frame, with initWithFrame:, not just init.
And also: give us the dimension of your UIScrollView, the only dimension you give us is the UIScrollView.contentSize...
Refer this apple sample code, it contains everything about scrollview
http://developer.apple.com/library/ios/#samplecode/ScrollViewSuite/Introduction/Intro.html
Related
So after figuring out how scrollView works, I've implemented it with the following code:
self.scrollView.delegate = self;
self.scrollView.userInteractionEnabled = YES;
CGRect view = CGRectMake(0, 0, 320, 750);
self.scrollView.contentSize = view.size;
The above code works as intended on ALL simulators in Xcode 6. However, when I run it my phone (iphone4s on ios7), the scroll does not function at all. Are people experiencing the same problems since the new release? Or am I missing something I've learned from the documentation?
Had the same issue here. Just need to resize the scrollview's frame size in viewDidLayoutSubviews which overrides auto layout.
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[scrollView setContentSize:CGSizeMake(320, 2600)];
// Adjust frame for iPhone 4s
if (self.view.bounds.size.height == 480) {
scrollView.frame = CGRectMake(0, 0, 320, 436); // 436 allows 44 for navBar
}
}
In AutoLayout
In general, Auto Layout considers the top, left, bottom, and right edges of a view to be the visible edges. That is, if you pin a view to the left edge of its superview, you’re really pinning it to the minimum x-value of the superview’s bounds. Changing the bounds origin of the superview does not change the position of the view.
The UIScrollView class scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the top, left, bottom, and right edges within a scroll view now mean the edges of its content view.
The constraints on the subviews of the scroll view must result in a size to fill, which is then interpreted as the content size of the scroll view. (This should not be confused with the intrinsicContentSize method used for Auto Layout.) To size the scroll view’s frame with Auto Layout, constraints must either be explicit regarding the width and height of the scroll view, or the edges of the scroll view must be tied to views outside of its subtree.
Note that you can make a subview of the scroll view appear to float (not scroll) over the other scrolling content by creating constraints between the view and a view outside the scroll view’s subtree, such as the scroll view’s superview.
Here are two examples of how to configure the scroll view, first the mixed approach, and then the pure approach
Mixed Approach
Position and size your scroll view with constraints external to the scroll view—that is, the translatesAutoresizingMaskIntoConstraints property is set to NO.
Create a plain UIView content view for your scroll view that will be the size you want your content to have. Make it a subview of the scroll view but let it continue to translate the autoresizing mask into constraints:
- (void)viewDidLoad {
UIView *contentView;
contentView = [[UIView alloc] initWithFrame:CGRectMake(0,0,contentWidth,contentHeight)];
[scrollView addSubview:contentView];
// DON'T change contentView's translatesAutoresizingMaskIntoConstraints,
// which defaults to YES;
// Set the content size of the scroll view to match the size of the content view:
[scrollView setContentSize:CGSizeMake(contentWidth,contentHeight)];
/* the rest of your code here... */
}
Create the views you want to put inside the content view and configure their constraints so as to position them within the content view.
Alternatively, you can create a view subtree to go in the scroll view, set up your constraints, and call the systemLayoutSizeFittingSize: method (with the UILayoutFittingCompressedSize option) to find the size you want to use for your content view and the contentSize property of the scroll view
Pure Auto Layout Approach
To use the pure autolayout approach do the following:
Set translatesAutoresizingMaskIntoConstraints to NO on all views involved.
Position and size your scroll view with constraints external to the scroll view.
Use constraints to lay out the subviews within the scroll view, being sure that the constraints tie to all four edges of the scroll view and do not rely on the scroll view to get their size.
A simple example would be a large image view, which has an intrinsic content size derived from the size of the image. In the viewDidLoad method of your view controller, you would include code similar to the code shown in the listing below:
- (void)viewDidLoad {
UIScrollView *scrollView;
UIImageView *imageView;
NSDictionary *viewsDictionary;
// Create the scroll view and the image view.
scrollView = [[UIScrollView alloc] init];
imageView = [[UIImageView alloc] init];
// Add an image to the image view.
[imageView setImage:[UIImage imageNamed:"MyReallyBigImage"]];
// Add the scroll view to our view.
[self.view addSubview:scrollView];
// Add the image view to the scroll view.
[scrollView addSubview:imageView];
// Set the translatesAutoresizingMaskIntoConstraints to NO so that the views autoresizing mask is not translated into auto layout constraints.
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
// Set the constraints for the scroll view and the image view.
viewsDictionary = NSDictionaryOfVariableBindings(scrollView, imageView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
/* the rest of your code here... */
}
I did not try Vishu's answer, but what I did was update to iOS 8 so it's compatible with Xcode 6 and it worked!
Hi, guys!
I have a question about nested scroll view.
There is a scroll view containing nested scroll views. I will just call outer-scrollview and inner-scrollview.
outer-scrollview is horizontal scrollview, and inner-scrollviews are vertical-scrollview.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_outerScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
_outerScrollView.pagingEnabled = YES;
_outerScrollView.contentSize = CGSizeMake(_outerScrollView.frame.size.width * 3, _outerScrollView.frame.size.height);
[self.view addSubview:_outerScrollView];
for(int i=0; i<3; i++) {
UIScrollView *innerScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(i * _outerScrollView.frame.size.width,
0,
_outerScrollView.frame.size.width,
_outerScrollView.frame.size.height)];
[_outerScrollView addSubview:innerScrollView];
UIView *contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, innerScrollView.frame.size.width, innerScrollView.frame.size.height * 2.0)];
contentView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"imagefile"]];
[innerScrollView addSubview:contentView];
innerScrollView.contentSize = contentView.frame.size;
}
}
Basically, if only 1 scroll view will be scrolled at the same time.
If I scroll to left or right, then outer-scrollview will be scrolled.
If I scroll to top or bottom, then inner-scrollview will be scrolled,
AND, if I scroll diagonally, one of both will be scrolled. It is depend on the direction of the scrolling.
If the angle is 0~45 degree, inner-scrollview will be scrolled.
If the angle is 45~90 degree, outer-scrollview will be scrolled.
Is it possible to change the ANGLE?
For example, even though the angle is 30 degree, I want to scroll horizontally.
Thank you!
Any help would be appreciated :)
If you want a simple way to achieve this kind of nested scrollview action, you could try simply disabling horizontal scrolling for the inner views and disabling vertical scrolling for the outer view. However, if that does not give you the result you are looking for, you will need to do a lot of custom coding.
First of all, you will need to start your own subclass of UIScrollView. Start by overwriting these functions:
touchesBegan:
touchesMoved:
touchesEnded:
touchesCancelled:
and also
touchesShouldBegin:
From there, you will have to do your own calculations to determine the angle of the drag and send messages to the scrollviews accordingly, possibly be calling super on one or more of the above functions. You might also consider just writing your own scrollview from scratch, you could simply have a UIView as a subview of your subclass.
I have a uiview at the top of the interface (below the status bar) that only the bottom part of it is shown.
Actually, I want to make the red uiview to slide down to be entirely shown by drag such as the notificationcenter in the native iOS and not just by taping a button.
What should I use to "touch and pull down" the uiview so it could be shown entirely ?
No needs to find a workaround of drag-n-drop. An UIScrollView can do it without any performance loss brought by listening on touches.
#interface PulldownView : UIScrollView
#end
#implementation PulldownView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) {
return self;
}
self.pagingEnabled = YES;
self.bounces = NO;
self.showsVerticalScrollIndicator = NO;
[self setBackgroundColor:[UIColor clearColor]];
double pixelsOutside = 20;// How many pixels left outside.
self.contentSize = CGSizeMake(320, frame.size.height * 2 - pixelsOutside);
// redArea is the draggable area in red.
UIView *redArea = [[UIView alloc] initWithFrame:frame];
redArea.backgroundColor = [UIColor redColor];
[self addSubview:redArea];
return self;
}
// What this method does is to make sure that the user can only drag the view from inside the area in red.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (point.y > height)
{
// Leaving useless touches to the views under it.
return nil;
}
return [super hitTest:point withEvent:event];
}
#end
How to use:
1. Initialize an instance of PulldownView.
2. Add any content you want to display to the instance using [addSubview:].
3. Hide the area in red.
[pulldownView setContentOffset:CGPointMake(0, heightOfTheView - pixelsOutside)];
This is a simple example. You can add any features to it like adding a titled button bar on the bottom of the draggable area to implement click-n-drop, or adding some method to the interface to reposition it by the caller.
Make a subclass of UIView.
Override touchesBegan:withEvent and touchesMoved:withEvent.
In the touchesBegan perhaps make a visual change so the user knows they are touching the view.
In the touchesMoved use
[[touches anyObject] locationInView:self]
and
[[touches anyObject] previousLocationInView:self]
to calculate the difference between the current touch position and the last touch position (detect drag down or drag back up).
Then if you're custom drawing, call [self setNeedsDisplay] to tell your view to redraw in it's drawRect:(CGRect)rect method.
Note: this assumes multiple touch is not used by this view.
Refer to my answer in iPhone App: implementation of Drag and drop images in UIView
You just need to use TouchesBegin and TouchesEnded methods. In that example, I have shown how to use CGPoint, Instead of that you have to try to use setFrame or drawRect for your view.
As soon as TouchesMoved method is called you have to use setFrame or drawRect (not sure but which ever works, mostly setFrame) also take the height from CGPoint.
This should be a pretty common thing to do, but I haven't been able to get it to work exactly right.
I have rectangular content. It normally fits in 320x361: portrait mode minus status bar minus ad minus tab bar.
I have put that content in a UIScrollView and enabled zooming. I also want interface rotation to work. The content will always be a tall rectangle, but when zoomed users might want to see more width at a time and less height.
What do I need to do in Interface Builder and code to get this done? How should I set my autoresizing on the different views? How do I set my contentSize and contentInsets?
I have tried a ton of different ways and nothing works exactly right. In various of my solutions, I've had problems with after some combination of zooming, interface rotation, and maybe scrolling, it's no longer possible to scroll to the entire content on the screen. Before you can see the edge of the content, the scroll view springs you back.
The way I'm doing it now is about 80% right. That is, out of 10 things it should do, it does 8 of them. The two things it does wrong are:
When zoomed in portrait mode, you can scroll past the edge of the content, and see a black background. That's not too much to complain about. At least you can see all the content. In landscape mode, zoomed or not, seeing the black background past the edge is normal, since the content doesn't have enough width to fill the screen at 1:1 zoom level (the minimum).
I am still getting content stuck off the edge when it runs on a test device running iOS 3.0, but it works on mine running 4.x. -- Actually that was with the previous solution. My tester hasn't tried the latest solution.
Here is the solution I'm currently using. To summarize, I have made the scroll view as wide and tall as it needs to be for either orientation, since I've found resizing it either manually or automatically adds complexity and is fragile.
View hierarchy:
view
scrollView
scrollableArea
content
ad
view is 320x411 and has all the autoresizing options on, so conforms to screen shape
scrollView is 480 x 361, starts at origin -80,0, and locks to top only and disables stretching
scrollableArea is 480 x 361 and locks to left and top. Since scrollView disables stretching, the autoresizing masks for its subviews don't matter, but I tell you anyway.
content is 320x361, starts at origin 80,0, and locks to top
I am setting scrollView.contentSize to 480x361.
shouldAutorotateToInterfaceOrientation supports all orientations except portrait upside down.
In didRotateFromInterfaceOrientation, I am setting a bottom content inset of 160 if the orientation is landscape, resetting to 0 if not. I am setting left and right indicator insets of 80 each if the orientation is portrait, resetting if not.
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 2.0
viewForZoomingInScrollView returns scrollableArea
// in IB it would be all options activated
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
scrollView.contentSize = content.frame.size; // or bounds, try both
what do you mean with scrollableArea?
your minZoomScale is set to 1.0 thats fine for portrait mode but not for landscape. Because in landscape your height is smaller than in portrait you need to have a value smaller than 1.0. For me I use this implementation and call it every time, the frame of the scrollView did change:
- (void)setMaxMinZoomScalesForCurrentBounds {
CGSize boundsSize = self.bounds.size; // self is a UIScrollView here
CGSize contentSize = content.bounds.size;
CGFloat xScale = boundsSize.width / contentSize.width;
CGFloat yScale = boundsSize.height / contentSize.height;
CGFloat minScale = MIN(xScale, yScale);
if (self.zoomScale < minScale) {
[self setZoomScale:minScale animated:NO];
}
if (minScale<self.maximumZoomScale) self.minimumZoomScale = minScale;
//[self setZoomScale:minScale animated:YES];
}
- (void)setFrame:(CGRect)rect { // again, this class is a UIScrollView
[super setFrame:rect];
[self setMaxMinZoomScalesForCurrentBounds];
}
I don't think I understood the entire problem from your post, but here's an answer for what I did understand.
As far as I know (and worked with UIScrollView), the content inside a UIScrollView is not automatically autoresized along with the UIScrollView.
Consider the UIScrollView as a window/portal to another universe where your content is. When autoresizing the UIScrollView, you are only changing the shape/size of the viewing window... not the size of the content in the other universe.
However, if needed you can intercept the rotation event and manually change your content too (with animation so that it looks good).
For a correct autoresize, you should change the contentSize for the scrollView (so that it knows the size of your universe) but also change the size of UIView. I think this is why you were able to scroll and get that black content. Maybe you just updated the contentSize, but now the actuall content views.
Personally, I haven't encountered any case that required to resize the content along with the UIScrollView, but I hope this will get you started in the right direction.
If I understand correctly is that you want a scrollview with an image on it. It needs to be fullscreen to start with and you need to be able to zoom in. On top of that you want it to be able to rotate according to orientation.
Well I've been prototyping with this in the past and if all of the above is correct the following code should work for you.
I left a bit of a white area for the bars/custombars.
- (void)viewDidLoad
{
[super viewDidLoad];
//first inits and allocs
scrollView2 = [[UIScrollView alloc] initWithFrame:self.view.frame];
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"someImageName"]];
[scrollView2 addSubview:imageView];
[self drawContent]; //refreshing the content
[self.view addSubview:scrollView2];
}
-(void)drawContent
{
//this refreshes the screen to the right sizes and zoomscales.
[scrollView2 setBackgroundColor:[UIColor blackColor]];
[scrollView2 setCanCancelContentTouches:NO];
scrollView2.clipsToBounds = YES;
[scrollView2 setDelegate:self];
scrollView2.indicatorStyle = UIScrollViewIndicatorStyleWhite;
[scrollView2 setContentSize:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)];
[scrollView2 setScrollEnabled:YES];
float minZoomScale;
float zoomHeight = imageView.frame.size.height / scrollView2.frame.size.height;
float zoomWidth = imageView.frame.size.width / scrollView2.frame.size.width;
if(zoomWidth > zoomHeight)
{
minZoomScale = 1.0 / zoomWidth;
}
else
{
minZoomScale = 1.0 / zoomHeight;
}
[scrollView2 setMinimumZoomScale:minZoomScale];
[scrollView2 setMaximumZoomScale:7.5];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
if (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
// Portrait
//the 88pxls is the white area that is left for the navbar etc.
self.scrollView2.frame = CGRectMake(0, 88, [UIScreen mainScreen].bounds.size.width, self.view.frame.size.height - 88);
[self drawContent];
}
else {
// Landscape
//the 88pxls is the white area that is left for the navbar etc.
self.scrollView2.frame = CGRectMake(0, 88, [UIScreen mainScreen].bounds.size.height, self.view.frame.size.width);
[self drawContent];
}
return YES;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
I hope this will fix your troubles. If not leave a comment.
When you want to put a content (a UIView instance, let's call it theViewInstance ) in a UIScrollView and then scroll / zoom on theViewInstance , the way to do it is :
theViewInstance should be added as the subview of the UIScrollView
set a delegate to the UIScrollView instance and implement the selector to return the view that should be used for zooming / scrolling:
-(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return theViewInstance;
}
Set the contentSize of the UIScrollView to the frame of the theViewInstance by default:
scrollView.contentSize=theViewInstance.frame.size;
(Additionally, the accepted zoom levels can be set in the UIScrollView :)
scrollView.minimumZoomScale=1.0;
scrollView.maximumZoomScale=3.0;
This is the way a pinch to zoom is achieved on a UIImage : a UIImageView is added to a UIScrollView and in the UIScrollViewDelegate implementation, the UIImageView is returned (as described here for instance).
For the rotation support, this is done in the UIViewController whose UIView contains the UIScrollView we just talked about.
Very strange behavior, there is a round dot in the center of the screen using this code, and a UIScrollview with nothing inside in a nib. I expect that UIScrollview should be empty. The dot blurs and disappears when I scroll the screen.
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *subviews = [closetScroll subviews];
UIImageView *strange=[subviews objectAtIndex:0];
strange.center = CGPointMake([[UIScreen mainScreen] bounds].size.width/2, [[UIScreen mainScreen] bounds].size.height/2);
strange.alpha=1;
NSLog(#"%#",subviews);
}
The console output is:
<UIImageView: 0x4b1f780; frame = (380.5 508.5; 7 7); opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x4b1f820>
Does anyone know why?
After magnifying the UIImageView and tweaking with configurations, I have come to conclude that the UIImageView is actually the scroll bar, and if horizontal and vertical scroll is enabled, an "empty" UIScrollview has two subviews inside.
I had the same issue. Calculating the number of subviews can be very deceptive, because of this "feature".
Start the application and count the number of subviews, this will be 1. Now, use you mouse in the simulator or finger on the device and swipe from the right to the left. Count the number of subviews. The number will be 2.
I can deduce nothing else than that this extra UIImageView is produced by Cocoa Touch to render the background in the right color when "bouncing" beyond the end of the UIScrollView's bounds.