iPhone中UIWebView在缩放操作和屏幕方向变化后宽度不匹配

14

我使用一个 UIWebView 创建了一个简单的 iPhone 应用程序 (Scales Page to Fit = YES, shouldAutorotateToInterfaceOrientation = YES),并加载了一个网页,例如 https://stackoverflow.com/

旋转设备后,UIWebView 会自动调整大小以适应宽度。很好。

错误的行为:缩放页面再放大。现在旋转设备,UIWebView 在其中一个方向上呈现奇怪的宽度(如果在横向模式下缩放,则纵向宽度是奇怪的,反之亦然)。只有当您导航到另一个页面时,这种行为才得到修复。

正确的做法:在 Mobile Safari 中加载相同的 URL。旋转工作,无论缩放练习如何,宽度都适合。

这是一个 UIWebView 的 bug 吗?还是需要做些事情才能像在 Mobile Safari 中一样 "正常工作"?

8个回答

15

我找到了一些对我有用的东西。问题在于,当uiwebview改变方向时,Web内容会被缩放以适应视口。但是scrollview子视图的zoomscale参数没有正确更新(minimumZoomScale和maximumZoomScale也没有更新)。

然后我们需要在 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;
    }
}

希望这可以帮到你!


谢谢你提供的解决方案!我已经修复了代码,使得任何人都可以更容易地将其实现到他们的ViewController.m中(代码也被复制到https://gist.github.com/834907)。 - choonkeat
1
它仍然在动画方面做一些奇怪的事情,但谢谢;)比以前好多了!我想知道苹果为此做了什么...我认为他们有一些未记录的类来提供修复。 - PostCodeism
谢谢 - 有点跳跃,但比以前好了。 - daihovey
@M Penades,它完美地运行了,谢谢你,它为我节省了很多麻烦。谢谢伙计。 - Kamar Shad

4

我尝试了M Penades的解决方案,这对我也有效。

唯一的问题是,在3Gs上运行时旋转不太流畅。

因此,我现在采用了不同的方法:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {

[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];

CGFloat scale = browserWebView.contentScaleFactor;
NSString *javaStuff = [NSString stringWithFormat:@"document.body.style.zoom = %f;", scale];
[browserWebView stringByEvaluatingJavaScriptFromString:javaStuff];

}

最好的问候,
Ralph


M Penades 的回答完美地解决了问题。另一个回答中的一个坏处是每次设备旋转时,JavaScript 都会调用,这会导致内存警告。因此,上面的回答是最好的。 - Kamar Shad
我正在iOS8中使用这个代码,放在-viewWillTransitionToSize:withTransitionCoordinator:方法里,在旋转后强制调整内部UIWebBrowserView组件的大小。但是被接受的答案并没有起作用。 - Jano
@Jano,你能详细解释一下你到底做了什么吗?我遇到了同样的问题——在iOS8上旋转时,UIWebBrowserView位置不正确。 - Martin
@马丁 -(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator { CGFloat scale = self.webView.contentScaleFactor;NSString *js = [NSString stringWithFormat:@"document.body.style.zoom = %f;", scale];[self.webView stringByEvaluatingJavaScriptFromString:js]; } - Jano

4
- (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;
    }
}

尝试使用以下代码,微调缩放级别(1.01)以允许UIWebView在横向模式下增加内容大小。

添加findScrollViewInsideView:方法以支持iOS4。


0

我有一个解决这个问题的方案,但是我必须说我不太喜欢它。它很好用,但是这个解决方案实际上会导致另一个问题。我有一个修复次要问题的方法,但需要花费一些努力。

请记住,自从OS3.2或iOS4(我不确定哪个版本)之后,UIWebView的直接子视图现在是UIScrollView而不是UIScroller,因此我们可以做更多的事情。此外,由于访问View的子视图不是私有操作,使用作为文档化视图转换的子视图也不违反规则,所以我们可以在不违反规则的情况下对UIWebView进行很多操作。

首先,我们需要从UIWebview中获取UIScrollView:

UIScrollView *sview = [[webView subviews] objectAtIndex:0];

现在我们需要更改此滚动视图的委托,以便我们可以覆盖滚动视图委托调用(这实际上可能是由于此解决方案而导致的次要错误的原因,稍后我将分享):
sview.delegate = self;

现在,如果你在这个点上尝试,缩放功能是会出问题的。我们需要实现一个UIScrollViewDelegate方法来修复它。添加:

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    UIView *webBrowserView = [[scrollView subviews] objectAtIndex:10];
    return webBrowserView;
}

webBrowserView实际上是一个UIWebBrowserView,但这不是一个文档化的类,所以我们将把它视为UIView。

现在运行你的应用程序,缩放网页然后再次缩小。旋转后,它应该正确显示。

这确实会导致一个相当大的bug,可能比原来的更糟糕。

如果你放大然后旋转,你将失去滚动能力,但你的视图仍然会被放大。这里是修复整个问题的方法。

首先,我们需要跟踪一些数字,并定义一个标志:

我已经在我的h文件中定义了这些:

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

你可能认为可以直接从UIScrollView中获取这些数字,但是当你需要它们时,它们可能已经发生了变化,因此我们需要将它们存储在其他地方。
我们需要使用另一个委托方法:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView{
    if(updateZoomData){
        zoomData = scrollView.zoomScale;
        zoomOffset = scrollView.contentOffset;
        zoomContentSize = scrollView.contentSize;
    }
}

现在感觉有点乱了。

我们需要跟踪旋转,因此您需要将其添加到viewDidLoad、loadView或您用于注册通知的任何方法中:

[[NSNotificationCenter defaultCenter] addObserver:self 
            selector:@selector(webViewOrientationChanged:) 
            name:UIDeviceOrientationDidChangeNotification 
            object:nil];

并创建此方法:

- (void)webViewOrientationChanged:(NSNotification *)notification{
    updateZoomData = NO;
    [self performSelector:@selector(adjustWithZoomData) withObject:nil afterDelay:0.0];
}

现在每次旋转webViewOrientationChange都会被调用。performSelector延迟0.0秒的原因是因为我们想在下一个runloop中调用adjustWithZoomData。如果直接调用它,adjustWithZoomData将会调整先前的方向。

这里是adjustWithZoomData方法:

- (void)adjustWithZoomData{
    UIScrollView *sview = [[webView subviews] objectAtIndex:0];
    [sview setZoomScale:zoomData animated:YES];
    [sview setContentOffset:zoomOffset animated:YES]; 
    [sview setContentSize:zoomContentSize];
    updateZoomData = YES;
}

就是这样!现在当您旋转时,它将保持缩放,并大致保持正确的偏移量。如果有人想要计算如何获得精确的正确偏移量,那就去做吧!


我想我没有提到,我认为这首先是一个错误。 - maxpower
我也注意到这会在某种程度上破坏双击缩小的功能。 - maxpower
嗨,maxpower,UIWebView的views数组有文档吗?如果WebBrowserView是objectAtIdx:10,则pdf / jpg / png文档直接加载到UIWebView中的哪个条目? - user306766
@iFloh - 这是完全没有记录的,而且将来可能会发生变化,所以在发布应用程序时使用这样的东西要非常小心。如果苹果更改了此视图的工作方式(就像他们经常为此类未记录的UI元素所做的那样),则您的应用程序可能会出现故障。 - Brad Larson

0

我发布这篇文章是因为我也面临同样的问题,并且在遵循 M Penades 的方法。但是,M Penades 的答案仅适用于用户不扭曲(缩小)Webview然后旋转设备并重复此过程的情况下良好工作。然后,UiwebView的内容大小会逐渐减少。这就是出现在 M Penades 答案中的问题。因此,我也解决了这个问题,我的代码如下:

1)对于这个问题,我设置了捏合手势,以便当用户扭曲UIwebView时,可以检查UIwebView的缩放大小。 //请在“.h文件”中导入UIGestureRecognizerDelegate协议。

     //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;
  }

返回 YES 可以保证允许同时识别手势。返回 NO 不能保证防止同时识别,因为另一个手势的委托可能会返回 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);
}

如果用户在 Web 视图中进行了缩放操作,那么只需设置该缩放因子。这样 WebView 就能够根据方向变化自动调整其内容大小。
- (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;
     }
  }

}

这对于即使用户扭曲了UIWebView也能完美运行。


0

所以显然我最终没有使用M Penades的解决方案(并且忘记更新这篇文章!抱歉)。

我所做的是调整整个文档的大小(并更改我的字体大小以保持比例)。那显然解决了这个问题。

然而,我的UIWebView仅用于从iOS文件系统加载自己的HTML和CSS - 如果您正在构建通用Web浏览器,则此技巧可能效果不佳。

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;
    }
}

还有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; }

请查看https://gist.github.com/d6589584944685909ae5上的要点。


0

0

我自己也在研究这个问题,发现了更多的信息:

更改缩放时出现的问题:

  1. Safari经常无法正确地(如果有的话)重新绘制,即使缩放级别已更改。
  2. 更改宽度会强制重新绘制。
  3. 你可能认为在横向方向上width=device-width会使用1024,但似乎使用768(screen.width也是如此)。

例如,如果当前宽度为1024,您想在横向方向上从1缩放到1.5,则可以:

  • 更改宽度和缩放的组合,例如将宽度更改为2048并将缩放更改为0.75
  • 将宽度更改为1023(丑陋的锯齿状?)
  • 将宽度更改为1023,然后下一行再次更改为1024(双重重新绘制,但至少窗口已重新绘制)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接