iPhone UIWebView width does not fit after zooming operation + UIInterfaceOrientation change - iphone

I created a bare bones iPhone app with a UIWebView (Scales Page to Fit = YES, shouldAutorotateToInterfaceOrientation = YES) and loaded a webpage, e.g. https://stackoverflow.com/
Rotating the device shows that UIWebView is auto-resized to fit the width. Good.
Incorrect: Zoom into the page and zoom out. Now rotating the device shows UIWebView in a weird width in one of the orientation (if u zoom in landscape, the portrait width is weird, vice versa). This behavior is fixed only when you navigate to another page.
Correct: Load the same URL in Mobile Safari. Rotating works & the width fits regardless of the zooming exercise.
Is this a UIWebView bug (probably not)? Or is there something that needs to be done to make things "just work" like in Mobile Safari?

I found something that worked for me. The problem is that when uiwebview changes its orientation web contents are zoommed to fit with viewport. But zoomscale parameter of scrollview subview is not updated correctly (nor are updated minimumZoomScale nor maximumZoomScale
Then we need to do it manually at willRotateToInterfaceOrientation:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
CGFloat ratioAspect = webview.bounds.size.width/webview.bounds.size.height;
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:
// Going to Portrait mode
for (UIScrollView *scroll in [webview subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale/ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale/ratioAspect;
[scroll setZoomScale:(scroll.zoomScale/ratioAspect) animated:YES];
}
}
break;
default:
// Going to Landscape mode
for (UIScrollView *scroll in [webview subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale *ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale *ratioAspect;
[scroll setZoomScale:(scroll.zoomScale*ratioAspect) animated:YES];
}
}
break;
}
}
Hope this helps!

I've tried the solution from M Penades and this seems to work for me as well.
The only issue that I'm experiencing is that when running this on a 3Gs the rotation is unfortunately not very smooth.
I'm therefore now using a different approach:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
CGFloat scale = browserWebView.contentScaleFactor;
NSString *javaStuff = [NSString stringWithFormat:#"document.body.style.zoom = %f;", scale];
[browserWebView stringByEvaluatingJavaScriptFromString:javaStuff];
}
Best Regards,
Ralph

- (UIScrollView *)findScrollViewInsideView:(UIView *)view
{
for(UIView *subview in view.subviews){
if([subview isKindOfClass:[UIScrollView class]]){
return (UIScrollView *)subview;
}
UIScrollView *foundScrollView = [self findScrollViewInsideView:subview];
if (foundScrollView){
return foundScrollView;
}
}
return nil;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
switch (self.interfaceOrientation){
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
{
UIScrollView *webViewScrollView = ([self.webView respondsToSelector:#selector(scrollView)])
? self.webView.scrollView
: [self findScrollViewInsideView:self.webView];
[webViewScrollView setZoomScale:1.01f animated:YES];
}
break;
default:
break;
}
}
try this code, it insignificantly changes zoom level (1.01) to allow UIWebView increase content size in landscape mode
findScrollViewInsideView: method added to support ios4

I have a solution to this problem, but I gotta say I'm not a huge fan of it. It works great, but the solution actually causes another problem. I have a fix for the secondary issue, but it takes a bit of effort.
Just keep in mind that since OS3.2 or iOS4 (not sure which) UIWebView's direct subview is now UIScrollView instead of UIScroller, so we can do a lot more with it. Also, since accessing subviews of a View is not a private action, neither is using a subview that is casted as a documented view we can do a lot with the UIWebView without breaking the rules.
First we need to get the UIScrollView from the UIWebview:
UIScrollView *sview = [[webView subviews] objectAtIndex:0];
Now we need to change the delegate of this scrollview so we can override scrollview delegate calls (which may actually be the cause of a secondary bug as a result of this solution, which I'll share in a moment):
sview.delegate = self;
Now, if you try it at this point, zooming is broken. We need to implement a UIScrollViewDelegate method to fix it. add:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
UIView *webBrowserView = [[scrollView subviews] objectAtIndex:10];
return webBrowserView;
}
webBrowserView is actually a UIWebBrowserView, but that isn't a documented class, so we are just going to treat it as a UIView.
Now run your app, zoom in and then zoom out the webpage. Rotate, and it should appear correctly.
This does cause a rather large bug, that is perhaps worse than the original.
If you zoom in and then rotate, you will loose scrolling ability, but your view will be zoomed in still. Here is the fix To complete the whole thing.
First, we need to keep track of a few numbers, and have a flag defined:
I have these defined in my h file:
BOOL updateZoomData;
float zoomData; //this holds the scale at which we are zoomed in, scrollView.zoomScale
CGPoint zoomOffset; //this holds the scrollView.contentOffset
CGSize zoomContentSize; //this holds the scrollView.contentSize
You may think you can just grab these numbers from UIScrollView, but when you need them, they will have changed, so we need them stored elsewhere.
We need to use another delegate method:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView{
if(updateZoomData){
zoomData = scrollView.zoomScale;
zoomOffset = scrollView.contentOffset;
zoomContentSize = scrollView.contentSize;
}
}
Now it gets into a mess I feel.
We need to track rotation, so you'll need to add this to your viewDidLoad, loadView, or whatever method you use to register notifications:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(webViewOrientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
and create this method:
- (void)webViewOrientationChanged:(NSNotification *)notification{
updateZoomData = NO;
[self performSelector:#selector(adjustWithZoomData) withObject:nil afterDelay:0.0];
}
So now anytime you rotate webViewOrientationChange will be called. The reason performSelector is delayed for 0.0 seconds is because we want to call adjustWithZoomData on the next runloop. If you call it directly, the adjustWithZoomData will adjust for the previous orientation.
Here is the adjustWithZoomData method:
- (void)adjustWithZoomData{
UIScrollView *sview = [[webView subviews] objectAtIndex:0];
[sview setZoomScale:zoomData animated:YES];
[sview setContentOffset:zoomOffset animated:YES];
[sview setContentSize:zoomContentSize];
updateZoomData = YES;
}
Thats it! Now when you rotate it will maintain zoom, and roughly maintain the correct offset. If anyone wants to do the math on how to get the exact correct offset then go for it!

I was looking into this myself and found out some more information:
Issues when changing zoom:
Safari often doesn't repaint properly (if at all) even though zoom level changed.
Changing the width forces a repaint.
you would think width=device-width in landscape would use 1024 but it seems to use 768 (screen.width happens too).
e.g. if current width is 1024 and you want to zoom from 1 to 1.5 in landscape you could:
change combination of width and zoom e.g. width to 2048 and zoom to 0.75
change width to 1023 (ugly aliasing?)
change width to say 1023, then next line back to 1024 (double repaint, but at least window is repainted).

So apparently I didn't use the solution by M Penades in the end (and forgot to update this post! sorry).
What I did was to resize the entire document (and change my font-size to keep things proportionate). That apparently fixed the issue.
However, my UIWebView is only for loading my own HTML & CSS from the iOS filesystem - if you're building a general purpose web browser, this trick may not work as well.
ViewController.m
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:
if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)) {
[webview stringByEvaluatingJavaScriptFromString:#"document.body.className = 'ppad'"];
} else {
[webview stringByEvaluatingJavaScriptFromString:#"document.body.className = 'pphone'"];
}
break;
default:
if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)) {
[webview stringByEvaluatingJavaScriptFromString:#"document.body.className = 'lpad'"];
} else {
[webview stringByEvaluatingJavaScriptFromString:#"document.body.className = 'lphone'"];
}
break;
}
}
And app.css
html>body.pphone { font-size:12px; width: 980px; }
html>body.lphone { font-size:18px; width: 1470px; }
html>body.ppad { font-size:12px; width: 768px; }
html>body.lpad { font-size:15.99999996px; width: 1024px; }
Gist at https://gist.github.com/d6589584944685909ae5

I am posting this because i have also faced the same problem and here i am following the M Penades Approach.M Penades 's Answer woks good only for case if user does not Skew(pinch Out) the Webview then rotate the device and repeat this process .then Content Size of UiwebView gets reduce gradually. so that was the issue came in M Penades Answer. so I have fixed that issue too and my code is as below.
1) For This I set the Pinch Gesture so that when User Skew The UIwebView could check the Scaled size of UIwebView.
//One This Please import The UIGestureRecognizerDelegate Protocol in '.h file'
//call below method in ViewDidLoad Method for setting the Pinch gesture
- (void)setPinchgesture
{
UIPinchGestureRecognizer * pinchgesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(didPinchWebView:)];
[pinchgesture setDelegate:self];
[htmlWebView addGestureRecognizer:pinchgesture];
[pinchgesture release];
// here htmlWebView is WebView user zoomingIn/Out
}
//Allow The allow simultaneous recognition
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
-(void)didPinchWebView:(UIPinchGestureRecognizer*)gestsure
{
//check if the Scaled Fator is same is normal scaling factor the allow set Flag True.
if(gestsure.scale<=1.0)
{
isPinchOut = TRUE;
}
else// otherwise Set false
{
isPinchOut = FALSE;
}
NSLog(#"Hello Pinch %f",gestsure.scale);
}
If User Hase Pinch In/Out The Web View in that Case Just Set THat Zooming Factor .
SO that WebView Can Adjust Its ContentSize as Oreintaion Changed.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//Allow the Execution of below code when user has Skewed the UIWebView and Adjust the Content Size of UiwebView.
if(isPinchOut){
CGFloat ratioAspect = htmlWebView.bounds.size.width/htmlWebView.bounds.size.height;
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:
// Going to Portrait mode
for (UIScrollView *scroll in [htmlWebView subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale/ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale/ratioAspect;
[scroll setZoomScale:(scroll.zoomScale/ratioAspect) animated:YES];
}
}
break;
default:
// Going to Landscape mode
for (UIScrollView *scroll in [htmlWebView subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale *ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale *ratioAspect;
[scroll setZoomScale:(scroll.zoomScale*ratioAspect) animated:YES];
}
}
break;
}
}
}
This Works perfectly for even user skew the UIWebView.

On rotation, try setting the scrollView zoomScale to 0.
See my full answer here: UIWebView content not adjusted to new frame after rotation

Related

UIWebView loading some black screen in the right side of Landscape View

I am facing this weird problem not in a normal behavior. I am loading the images from a url in the web-view. If I change the Orientation from portrait to landscape and landscape to portrait no problem. But In Portrait If I do some zoom-in , zoom-out and double tap the image to fit in the screen, then if I change to Landscape now, I can see there is white/black screen is coming in the right side.
I have googled about this issue, I tried many ways like Opaque property, clear color etc. Even I tried my own way to resolve this , but I failed. How to fix the issue ?
I am posting this because i have also faced the same problem ,I have fixed that issue too and my code is as below.Here Below is the Whole Code Which can Solve the Issue.Need To properly Put ALl code in ViewControler
For This I set the Pinch Gesture so that when User Skew The UIwebView could check the Scaled size of UIwebView.
//One This Please import The `UIGestureRecognizerDelegate` Protocol in '.h file'
//call below method in ViewDidLoad Method for setting the Pinch gesture
- (void)setPinchgesture
{
UIPinchGestureRecognizer * pinchgesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(didPinchWebView:)];
[pinchgesture setDelegate:self];
[htmlWebView addGestureRecognizer:pinchgesture];
[pinchgesture release];
// here htmlWebView is WebView user zoomingIn/Out
}
//Allow The allow simultaneous recognition
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
-(void)didPinchWebView:(UIPinchGestureRecognizer*)gestsure
{
//check if the Scaled Fator is same is normal scaling factor the allow set Flag True.
if(gestsure.scale<=1.0)
{
isPinchOut = TRUE;
}
else// otherwise Set false
{
isPinchOut = FALSE;
}
NSLog(#"Hello Pinch %f",gestsure.scale);
}
If User Hase Pinch In/Out The Web View in that Case Just Set THat Zooming Factor .
SO that WebView Can Adjust Its ContentSize as Oreintaion Changed.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//Allow the Execution of below code when user has Skewed the UIWebView and Adjust the Content Size of UiwebView.
if(isPinchOut){
CGFloat ratioAspect = htmlWebView.bounds.size.width/htmlWebView.bounds.size.height;
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:
// Going to Portrait mode
for (UIScrollView *scroll in [htmlWebView subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale/ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale/ratioAspect;
[scroll setZoomScale:(scroll.zoomScale/ratioAspect) animated:YES];
}
}
break;
default:
// Going to Landscape mode
for (UIScrollView *scroll in [htmlWebView subviews]) { //we get the scrollview
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)]){
scroll.minimumZoomScale = scroll.minimumZoomScale *ratioAspect;
scroll.maximumZoomScale = scroll.maximumZoomScale *ratioAspect;
[scroll setZoomScale:(scroll.zoomScale*ratioAspect) animated:YES];
}
}
break;
}
}
}
This Works perfectly for even user skew the UIWebView.

Scrolling, zooming UIScrollView and interface orientation rotation. How to use autoresize and more

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.

Can we zoom out a image by code, which is been zoomed in by pinch zoom?

The implementation of pinch zoom is been done by using UIScrollView and putting 'UIImageView` inside it, through following code:
-(void)viewDidLoad{
slideShowImageViewScrollView.maximumZoomScale = 4.0;
slideShowImageViewScrollView.minimumZoomScale = 1.0;
slideShowImageViewScrollView.clipsToBounds = YES;
slideShowImageViewScrollView.delegate = self;
slideShowImageViewScrollView.scrollEnabled = NO;
}
Then after checking zoomScale scrolling is been enabled as:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if (scrollView.zoomScale!=1.0)
{
slideShowImageViewScrollView.scrollEnabled = YES;
}
else
{
slideShowImageViewScrollView.scrollEnabled = NO;
}
}
And here is the view that is for zooming in, in scroll view
- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return slideShowImageView;
}
Now using timer the image of UIImageView is been changed after a time period, by following code:
- (void) changeImageSlide
{
if (imageCounter>=totalNoOfImages-1)
{
imageCounter=0;
}
NSString *str = [NSString stringWithFormat:#"%#.jpg",[copyOf_myGlobleArrayOfImageIds objectAtIndex:imageCounter]];
mainSlideShowImageView setImage:[UIImage imageNamed:str]];
[mainSlideShowImageView setTag:[[copyOf_myGlobleArrayOfImageIds objectAtIndex:imageCounter] intValue]];
imageCounter++;
}
Only thing problamatic here is when the next image is loaded that is also, 'zoomed in', we want image in normal mode.
Can we achieve it(by coding it for zoom out in our changeImageSlide method or any where)?
If yes, then how?
If no, then is there any alternative?
Thank You.
As you load the new image, set the zoomScale of the scrollView to 1.0.
[slideShowImageViewScrollView setZoomScale:1.0 animated:NO];
You can set the zoom scale on the scrollview to reset it to 1.0. You could even store zoom levels against images so that it preserves the individual zoom setting per image.

How to reset UIWebView's zoom? I'm already using scalesPagesToFit = YES;

I've been looking for the past week for the answer to this question.
I have a UIWebView, inside of a UIScrollView. Everything works great, but I want the content of the UIWebView to reset its zoom, when the orientation changes.
In the HTML inside the UIWebView, I set the width of the viewport (w/ a meta tag) to "device-width" and then on the Obj-C side, I set the scalesPagesToFit = YES;
I've tried resetting the zoom with javascript; by replacing the meta tags in runtime; reloading; accessing the UIScrollView inside of the UIWebView; etc...
but with no success.
Any of you gods know a workaround?
The only one I can think off is to recreate the UIWebViews every time we change the orientation, but that makes them flash to white whilst rendering content, which looks terrible :(
Any thoughts?
Many thanks,
Andre
I'm just guessing here and haven't tried, but AFAIK a UIWebView has a UIScrollView child. So one should be able to do:
for (UIScrollView *scroll in [myWebView subviews]) {
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)])
[scroll setZoomScale:1.0];
}
On iOS 5+ you have access to scrollView.
Just do:
[webView.scrollView setZoomScale:1.0];
If you want to do it programmatically this is the only way I could find to accomplish it: (specify your own sizes if you wish, i was attempting to zoom out after typing into a form field)
UIScrollView *sv = [[webViewView subviews] objectAtIndex:0];
[sv zoomToRect:CGRectMake(0, 0, sv.contentSize.width, sv.contentSize.height) animated:YES];
Update:
Downscaling wasn't working properly when using
[[[webView subviews] lastObject] setZoomScale:0.25];
The quality of the images being downscaled on the page was awful. Doing:
[[[webView subviews] lastObject] setZoomScale:0.25 animated:YES];
Fixed it. So that last line is the one you could use.
webView was subclassed of a UIWebView which lies on some IB file. I didn't use the Viewport at all. I find that one should pick by either doing this from the Cocoa Touch side or use JS.
I used:
webView.scalesPageToFit = YES;
I wonder if there's a way of resetting the scalesPageToFit.
Adapting from Captnwalker1's answer, I came up with this:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if(toInterfaceOrientation ==UIInterfaceOrientationPortrait||toInterfaceOrientation==UIInterfaceOrientationMaskPortraitUpsideDown)
{
currentScrollView = [[webView subviews] objectAtIndex:0];
[currentScrollView zoomToRect:CGRectMake(0, 0, currentScrollView.contentSize.width, currentScrollView.contentSize.height) animated:NO];
}
else
{
currentScrollView = [[webView subviews] objectAtIndex:0];
[currentScrollView zoomToRect:CGRectMake(0, 0, currentScrollView.contentSize.width, currentScrollView.contentSize.height) animated:NO];
}
}
So load your webview image, and the image will reset it's size when rotated.

iphone/ipad orientation handling

This is more of a general question for people to provide me guidance on, basically Im learning iPad/iPhone development and have finally come across the multi-orientation support question.
I have looked up a fair amount of doco, and my book "Beginning iPhone 3 Development" has a nice chapter on it.
But my question is this, if I was to programatically change my controls (or even use different views for each orientation) how on earth to people maintain their code base? I can just imagine so many issues with spaghetti code/thousands of "if" checks all over the place, that it would drive me nuts to make one small change to the UI arrangement.
Does anyone have experience handling this issue? What is a nice way to control it?
Thanks a lot
Mark
I do this with two simple methods in my view controller:
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self adjustViewsForOrientation:toInterfaceOrientation];
}
- (void) adjustViewsForOrientation:(UIInterfaceOrientation)orientation {
if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
titleImageView.center = CGPointMake(235.0f, 42.0f);
subtitleImageView.center = CGPointMake(355.0f, 70.0f);
...
}
else if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
titleImageView.center = CGPointMake(160.0f, 52.0f);
subtitleImageView.center = CGPointMake(275.0f, 80.0f);
...
}
}
To keep this clean you could easily compartmentalize the view adjustments/reloading/etc. with methods called from inside the single if-else conditional.
It really depends on what it is you are laying out.
If you look at the Apple Settings application, you can see that they use table views for the layout, with custom cells for most rows. With that, you can allow a simple interface to rotate pretty cheaply by just filling the width of the cells. This even applies to things like Mail, where there are edit text cells in each row. And tables can easily be all transparent, with only buttons or labels visible, so they do not look like tables.
You can get a lot of milage out of the autoresizingMask of every UIView. If you have one or more items that can have a flexible height, then you can usually get an interface layout that looks good in either orientation. Depending on how it looks, sometimes you can just pin everything to the top.
In rare cases, if all the interface elements fit in a square, you can just rotate them in place.
There are two times when you must explicitly handle orientation changes. One is when a view moves from beside to below another on rotation. The other is when you have different images for each orientation, for example if you always want to be full width.
There are sometimes ways to work around both of these. You might use stretchable images or limit yourself to one view per line. Or you might lock out orientation for certain views.
If you must change the layout of views, there is an explicit layoutSubviews method. You should try to handle all you conditional layout in this one method. It is only called when the view bounds change, for example on rotation or if you have made room for the keyboard. Make a custom view for each view hierarchy that needs to respond to rotation, and layout the subviews from there.
The iPhone SDK is built around having an MVC architecture, so in theory if you keep all your logic (model) separated from your UI (view) then you will only have to worry about the UI in one spot: your view controllers. For those, you could have a separate view controller for each orientation, each of which would then just be loaded with one if/else to choose which view controller to load.
The same idea holds for iPhone / iPad support, where you can load another view controller which can handle larger displays.
Refer the following link:
http://mustafashaik.wordpress.com/2010/11/17/handling-orientations-in-ipad/
I can't vouch for this code, and in all honesty the above willRotateToInterfaceOrientation works great. Here's another take on it with FBDialog.m from Facebook for iphone / ipad. (albeit, I think this was for a webview)
here's the gist
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(deviceOrientationDidChange:)
name:#"UIDeviceOrientationDidChangeNotification" object:nil];
- (void)deviceOrientationDidChange:(void*)object {
UIDeviceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if ([self shouldRotateToOrientation:orientation]) {
CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
[self sizeToFitOrientation:YES];
[UIView commitAnimations];
}
}
-(CGAffineTransform)transformForOrientation {
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (orientation == UIInterfaceOrientationLandscapeLeft) {
return CGAffineTransformMakeRotation(M_PI*1.5);
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
return CGAffineTransformMakeRotation(M_PI/2);
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
return CGAffineTransformMakeRotation(-M_PI);
} else {
return CGAffineTransformIdentity;
}
}
- (void)sizeToFitOrientation:(BOOL)transform {
if (transform) {
self.transform = CGAffineTransformIdentity;
}
CGRect frame = [UIScreen mainScreen].applicationFrame;
CGPoint center = CGPointMake(
frame.origin.x + ceil(frame.size.width/2),
frame.origin.y + ceil(frame.size.height/2));
CGFloat scale_factor = 1.0f;
if (FBIsDeviceIPad()) {
// On the iPad the dialog's dimensions should only be 60% of the screen's
scale_factor = 0.6f;
}
CGFloat width = floor(scale_factor * frame.size.width) - kPadding * 2;
CGFloat height = floor(scale_factor * frame.size.height) - kPadding * 2;
_orientation = [UIApplication sharedApplication].statusBarOrientation;
if (UIInterfaceOrientationIsLandscape(_orientation)) {
self.frame = CGRectMake(kPadding, kPadding, height, width);
} else {
self.frame = CGRectMake(kPadding, kPadding, width, height);
}
self.center = center;
if (transform) {
self.transform = [self transformForOrientation];
}
}
In your question, you wrote:
I can just imagine so many issues with spaghetti code/thousands of "if" checks all over the place, that it would drive me nuts to make one small change to the UI arrangement.
One way to dodge this is to make a view hierarchy that splits the handling of iPhone/iPad specific changes from the very beginning. You'd only have to set which view is initially loaded for each device. Then you create a viewcontroller like you normally do, but you also subclass the viewcontroller you've created. One subclass for each device. That's where you can put the device specific code, like orientation handling. Like this:
MyViewController.h // Code that is used on both devices
MyViewController_iPhone.h // iPhone specific code, like orientation handling
MyViewController_iPad.h // iPad specific code, like orientation handling
If you are interested in this approach, I'd suggest that you read this article. It explains it in a very nice way.
One of the things the article mentions, is this:
--start of quote--
The beauty of this pattern is we don’t have to litter our code with crap that looks like this:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// The device is an iPad running iPhone 3.2 or later.
// set up the iPad-specific view
} else {
// The device is an iPhone or iPod touch.
// set up the iPhone/iPod Touch view
}
---end of quote--
I hope that helps. Good luck!