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.
My UIScrollView on the iPhone is 480x230 and the content is 972x230 when first displayed. I need to provide functionality where when the user double taps the UIScrollView the contents zoom to fit with the 480x230 UIScrollView proportionally. When the double tap it again it should zoom back out to it's original size.
What is the best way to do this? I have been fumbling for several hours with this and thought that this would work...
[bodyClockScrollView zoomToRect:bodyClockScrollView.frame animated:YES];
But nothing seems to happen.
Thanks, any help pointing me in the right direction would be appreciated.
You need to make sure the class you've set up as the scrollview's delegate implements this method:
-(UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView {
return someView;
}
Using CGAffineTransform I figured out a way to do what I want...
//bodyCleckScrollView contains a UIView name bodyClock
//get the scale factor required to fit bodyClock inside the iPhone window and resize it...
CGFloat scale = 480/bodyClockScrollView.contentSize.width;
bodyClock.transform = CGAffineTransformScale(bodyClock.transform, scale, scale);
//move bodyClock to the bottom of bodyScrollView.
bodyClock.transform = CGAffineTransformTranslate(bodyClock.transform, 0.0,bodyClockScrollView.frame.size.height-bodyClock.frame.size.height);
//scoll bodyScrollView so that bodyClock is centered in the window
CGPoint offsetPoint = CGPointMake(bodyClock.frame.origin.x, 0.0);
[bodyClockScrollView setContentOffset:offsetPoint animated:YES];
This works great and when I want to zoom it back out to the default size and position you simply call...
bodyClock.transform = CGAffineTransformIdentity;
I have to rotate a view circularly on finger touch...I mean like dialing the old phones number...and the touch should be only on corners.....can any one help me...i have tried it a lot...but was not successes
You need to define the UIRotationGestureRecognize on the view that you want to rotate and than add a selector method and implement it like this.
Add this to you viewDidLoad method
UIRotationGestureRecognizer *rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotation:)];
[myUIViewObject addGestureRecognizer:rotate];
[rotate release];
And then implement the method.
- (void) rotation:(UIRotationGestureRecognize *) sender
{
CGAffineTransform myTransform = CGAffineTransformMakeRotation(sender.rotation);
sender.view.transform = myTransform;
}
PS. myUIViewObject can be any UIView Object that you want to rotate.
Edit:
you will find a lot of information on this here:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/49847-how-find-angle-two-points.html
I've managed to manually detect rotation and set my own rotation effects between two UIViewControllers. When in the first, rotating to landscape prompts the second to be pushed on with a manual animation. The status bar moves to the correct orientation. However, the view loaded by the second UIViewController is not in the position I expected it. Instead there is margin on the left where the status bar previously was and margin/space at the bottom that I was expecting to be filled by the view.
(Click to enlarge. The orange box is simply to reference where 0,0 is in the rotated UIView, CustomView)
The code I'm using in my view controller to do the rotation is:
-(void)loadView {
CustomView *customView = [[CustomView alloc] initWithFrame:CGRectMake(0, 20, 480, 300)];
self.view = customView;
CGAffineTransform rotate = CGAffineTransformMakeRotation(degreesToRadian(90));
[self.view setTransform:rotate];
[customView release];
}
Any help would be much appreciated!
EDIT Managed to solve after a variety of trial and error approaches - answer provided below. Perhaps there's a more elegant/obvious solution though - if so feel free to provide!
try these on the viewcontrollers. Solved the 20 pixel(status bar height) clip issue when I was getting fullscreen subviews to show up.
[viewcontroller1.view setCenter:CGPointMake(viewcontroller1.view.center.x, viewcontroller1.view.center.y-20)];
and/or
[viewcontroller2.view setCenter:CGPointMake(viewcontroller2.view.center.x-20, viewcontroller2.view.center.y)];
I managed to solve this by setting the View's bounds after the transformation:
self.view.bounds = CGRectMake(20, -20, 480, 300);
I am using uislider in my application.Now i want to get position of center button when i am moving slider.How it possible?
UISlider *slider = mySlider;
CGRect bounds = slider.bounds;
CGRect trackRect = [slider trackRectForBounds:bounds];
CGRect thumbPosition = [slider thumbRectForBounds:bounds trackRect:trackRect
value:slider.value];
See the documentation here.
It's one of the subviews. However, accessing it like this is prone to breakage in future OS versions. You can probably make a reasonable guess of the coordinates with a little math though.