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

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.

Related

UIWebView Scrolling

I load an HTML form in my UIWebView and it so happens that my UIWebView starts from the middle of the view and extends. I have to lock this webView from scrolling and put it on top of a scrollView to allow scrolling. The webView shouldn't scroll internally. I have tried this
[[webView.subviews objectAtIndex:0] setScrollEnabled:NO];
but I am not able to stop the webView from scrolling internally when I load a form in it that has textFields clicking on which brings up the keyboard. When I load plaintext, I am able to get the desired behavior. I want to stop the webView from autoscrolling and handle the scrolling myself using the scrollView that lay beneath the webView. It might not make sense to most of you that I am reinventing the wheel but the requirement is such.
Can anybody suggest what to do?
You can try,
for (id subview in webView.subviews)
if ([[subview class] isSubclassOfClass: [UIScrollView class]])
if([subview respondsToSelector:#selector(setScrollingEnabled:)]) [subview performSelector:#selector(setScrollingEnabled:) withObject:NO];
or you need to stop bouncing of the webview you can try this.
for (id subview in webView.subviews)
if ([[subview class] isSubclassOfClass: [UIScrollView class]])
((UIScrollView *)subview).bounces = NO;
Hope this helps.
Try running this after the webview has loaded (i.e. in the proper delegate method):
[webView stringByEvaluatingJavaScriptFromString:#"document.getElementsByTagName('body')[0].style.overflow='hidden'"]
If you have access to the HTML, you could also just set the correct style on the body element:
body { overflow: hidden; }
If you do either of these, you need to make sure that you make the UIWebView tall enough to display everything. You can get the height by running this (also after the webview has loaded):
CGFloat height = [webView stringByEvaluatingJavaScriptFromString:#"document.body.scrollHeight"]

Detect Zoomscale in UIWebView

My problem is that i want to detect the zoom scale of an UIWebView, i have tried searching it but did not come out with a proper answer.Any help is appreciated......
Well although the UIWebView doesn't have a zoomScale property, UIScrollView does!
So we just scan it's subView's for the scrollView everything sits in and get it that way.
Here's a little (1 method) category that will allow you to get the scale by calling [webView zoomScale].
UIWebView+zoom.h file
#interface UIWebView (zoom)
-(float)zoomScale;
#end
UIWebView+zoom.m file
#implementation UIWebView (zoom)
-(float)zoomScale{
UIScrollView *webViewContentView;
for (UIView *checkView in [self subviews] ) {
if ([checkView isKindOfClass:[UIScrollView class]]) {
webViewContentView = (UIScrollView*)checkView;
break;
}
}
return webViewContentView.zoomScale;
}
#end
UIScrollView Class Reference
UIWebView's View Hierarchy (Don't rely on it though, always scan the webView to avoid code breaking when apple makes changes to iOS)
NOTE: This code should work but has been written in the reply box so hasn't been tested.

How do I make UIWebView scroll to the top?

Is there a way to make a UIWebView scroll to the top when I touch say a UISearchView within the same viewController (without using Javascript).
Something like this:
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
[myWebView scrollToTop]; //pseudocode
}
In other words, what happens when I touch the top bar can also happen programmatically.
CGPoint top = CGPointMake(0, 0); // can also use CGPointZero here
[myWebView.scrollView setContentOffset:top animated:YES];
(Note that if you have set myWebView.scrollView.contentInset.top you will want to take that into account instead of just scrolling to CGPointZero.)
Here's a really ugly, terrible way to do this. I'm answering this to show you what never to do.
for (UIView *subview in webView.subviews)
{
if ([subview isKindOfClass:[UIScrollView class]])
[(UIScrollView*)subview setContentOffset:CGPointZero animated:YES];
}
If you don't want to use JavaScript there's no public API method to do this. UIWebViews don't inherit from scroll views, so you can't use any of the usual methods. As you've figured out, it's possible to do with JavaScript. You can try to find the actual scroll view in the UIWebView, but it's all undocumented and not really a good thing to do in a production app.
Update - as of iOS 5 you can now get direct access to a web view's UIScrollView - see Reconquistador's answer for more information.
Scroll the UIWebView by calling JavaScript through Objective-C:
NSString *script = #"scrollTo(0, 0)";
[webView stringByEvaluatingJavaScriptFromString:script];
For smooth scrolling, use a library like jQuery:
NSString *script = #"$('html, body').animate({scrollTop:0}, 'slow')";
[webView stringByEvaluatingJavaScriptFromString:script];
Access the UIWebView's scrollview and set YES to method scrollsToTop;
WebView.scrollView.scrollsToTop = YES;
Hope this helps!
Swift 3.x
webView.scrollView.setContentOffset(CGPoint.zero, animated: true)
I don't believe you will find any officially supported method to do what you are wanting.
This works for me:
CGPoint topOffset = CGPointMake(0, 0);
//[scrollView setContentOffset: topOffset animated: YES];
[[[webView subviews] lastObject] setContentOffset:topOffset animated:YES];
Where webView is the subclass of the UIWebView, of course.
why not just touch the status bar of your device. for all the scrollView based controls, tableView, webView, scrollView, there is a :
The scroll-to-top gesture is a tap on the status bar; when this property is YES, the scroll view jumps to the top of the content when this gesture occurs. The default value of this property is YES.
Displaying a PDF in a UIWebView, I found you can jump to the top simply by reloading the document.

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

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

Filling width in (iPhone) UIWebView landscape orientation?

I'm using a UIWebView with text in it. When the iPhone is rotated to landscape, text doesn't fill the now wider UIWebView width. I'm using P (paragraph) tags, which should not affect content filling landscape's width. The line breaks that are visible in portrait remain the same in landscape. In Interface Builder, I haven't changed anything. In the IB Inspector, Web View Size has all solid bars under AutoSizing, which means it should fill to the landscape width right?
Here is a tweak though not a good thing to do, and something should be handled by apple itself
As you've noticed that things workfine when WebView is initialized in portrait and then you turn it to landscape. So.. what you can do is always initialize your webview with portrait bounds, add a selector which calls back after 2~3 seconds and sets the frame of webView according to your requirement.
Now as the contents started loading when the frame size of your webview were according to portrait (say 320,460) so converting your webview to landscape will automatically adjust your web view if you have this line in your code
[webViewObjet_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
Below is the snippet of code
- (id) initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
webViewObjet_ = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
.....
}
}
- (void) webViewDidStartLoad:(UIWebView *)webView
{
.....
[self performSelector:#selector(chuss) withObject:nil afterDelay:3];
// call the function chuss after 3 second
}
- (void) chuss
{
webViewObjet_.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
[webViewObjet setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
}
Now tried around with the same problem, finally did it after looking detailed at "WhichWayIsUp"-Sample from Apple.
To keep it short:
1) Disable in the View Inspector the |--| and <-->
2) `switch the View Mode from the Webview to "Aspect Fill"
Et voila ;)
Keep the vibes,
Maniac
I have the same problem. Reloading does not work, the only thing that seems to help a bit is to add the following line to the code:
self.view.autoresizingMask =
UIViewAutoresizingFlexibleWidth;
(I place it in the willAnimateFirstHalfOfRotationToInterfaceOrientation function)
It still keeps a small white margin at the right, but its far better than the default. Note: you should apply this on the self.view and not on the UIWebView instance, that won't work.
Waiting for a solution from Apple..
Pieter
This will sound strange, but it works:
If the UIWebView is inside a UINavigationController, it will all work just fine. I had the same problem, so I just wrapped it up in a UINavigationController and the problem was gone.
For some reason, UINavigationController makes rotations work like a charm.