iOS8 iPad 在显示下拉列表 HTML select 标签时,使用弹出窗口会崩溃。

42

在iOS8和iPad上,如果uiwebview显示包含下拉列表的HTML页面

例如此页面http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_select

然后:

  • 重复点击包含汽车列表的HTML下拉列表。第一项是沃尔沃。
  • 每隔半秒钟左右轻点一次,直到弹出窗口打开并关闭。
  • 应用程序将崩溃:

Terminating app due to uncaught exception 'NSGenericException',[...] 'UIPopoverPresentationController () should have a non-nil sourceView or barButtonItem set before the presentation occurs.'

是否有任何方法可以在uiwebview中解决此问题?

使用wkwebview不会发生这种情况,但我想在uiwebview中修复它。

更新:以下似乎有所帮助,但不确定副作用。我已经在包含uiwebview的视图控制器中覆盖了以下内容。

-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
    if (completion)
    {
        completion();
    }

    [super dismissViewControllerAnimated:NO completion:nil];
}

我在更新到iOS8后使用UIWebViews时遇到了相同的问题。你能找到解决方法吗?很感兴趣知道。 - abstract_a
这个问题出现在我项目中所有的Web视图上,但在Safari上没有出现。该问题存在于iOS 8.1上。您应该在Apple门户网站上提交一个错误报告。 - pablobart
这个bug已经向苹果报告(#18513999),并且被标记为#18487570的重复问题,该问题目前仍然处于打开状态。 - pablobart
感谢 @pablobart,我报告的错误是 #18505076。 - abstract_a
这个问题在iOS 8.3中似乎已经得到修复,但由于旧版本仍然很普遍,因此这些问题和答案仍然非常有用。 - Joseph Lord
显示剩余5条评论
5个回答

42

问题中提到的解决方案对我没有帮助,但它确实指引了我正确的方向。经过一些调查,我认为这是在呈现和移除Popover之间某种竞争条件导致的。 作为一种解决方法,您可以将UIWebView的代理中的呈现推迟:

-(void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_USEC), dispatch_get_main_queue(),
               ^{
                   [super presentViewController:viewControllerToPresent animated:flag completion:completion];
               });
}

几个测试,但这个解决方案对我有效。谢谢! - RFG
已经提交了一个关于此问题的错误报告,rdar://19151009。 - Seth Spitzer
5
应该在控制器上显示UIWebView,而不是UIWebView的代理上。通常情况下它们会是同一个对象,但不一定要是同一个对象。 - Nicholas Daley-Okoye
6
这个修复方法并不适用于所有情况。如果用户使用键盘顶部的“前进”或“后退”按钮快速遍历表单字段,应用程序仍然会崩溃。 - CodingWithoutComments
UIWebView的哪个委托方法?如何实现?我怎样从UIWebView中获取ViewControllerToPresent? - Min Soe
@Min:请查看https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWebView_Class/和https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIWebViewDelegate_Protocol/index.html的文档,还可以参考[ask]。 - dogsgod

9

之前的解决方案对我没有帮助。

已经有一个错误日志被提交到了苹果(参见openradar)。

问题似乎是Web视图尝试在没有设置弹出窗口的源视图的情况下呈现视图控制器。虽然这绝对是苹果的问题,但我已经使用以下解决方法来避免我的应用程序崩溃:

- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
{
    // Override this method in the view controller that owns the web view - the web view will try to present on this view controller ;)

    if (viewControllerToPresent.popoverPresentationController && !viewControllerToPresent.popoverPresentationController.sourceView) {
        return;
    }

    [super presentViewController:viewControllerToPresent animated:flag completion:completion];
}

我发现在init函数中添加[UIView setAnimationsEnabled:NO];也有帮助。 - DdD
应用程序在按下虚拟键盘上的 ∧ ∨ 按钮时崩溃。 - Mikalai Daronin

1
我在同一场景中遇到了不同的异常,这里的任何解决方法都没有帮助到我。
这是我的异常:
Terminating app due to uncaught exception 'NSRangeException', reason: '-[UITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:]: row (4) beyond bounds (0) for section (0).'

这是我用来解决它的代码:

-(void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
    if ([viewControllerToPresent respondsToSelector:NSSelectorFromString(@"_cachedItems")]) {
        if([viewControllerToPresent valueForKey:@"_cachedItems"] == nil) {
            if (completion != nil) {
                completion();
            }
            return;
        }
    }

    [super presentViewController:viewControllerToPresent animated:flag completion:completion];
}

这是一种非常糟糕的解决方法,它可以防止下拉菜单在即将崩溃的情况下显示,并且由于使用了内部属性,此解决方案随时可能停止工作。然而,这是我找到的唯一有效的解决方法,所以也许对其他人有帮助。

1

在注意到在崩溃的情况下设置了sourceView之后,我通过以下方式解决了它:

-(void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {

  UIPopoverPresentationController* pres = viewControllerToPresent.popoverPresentationController;

  if(pres.sourceView) {
    //log the fact you are ignoring the call
  }
  else {
    [super presentViewController:viewControllerToPresent animated:flag completion:completion];
  }
}

是的,你说得对,但你还应该检查 viewControllerToPresent.popoverPresentationController.sourceView == nil,否则你会禁止任何弹出窗口展示,即使是有效的 ;) - tanz
这就是我正在检查的。当然,您需要知道,如果您自己的视图控制器已经设置了它们的sourceView,那么它们也将被忽略,但这就是记录您正在忽略它的事实的重要性所在。如果我发现这是个问题,我可能会在if语句中放置一个手动异常,以允许呈现已知类类型的视图控制器。我注意到您修改了您的答案,而且它包含了与我的相反的逻辑。在我的调试中,我注意到当sourceView不为空时应用程序崩溃了,尽管错误消息说的是相反的。 - Daz Eddy

0

我通过以下方式降低了崩溃的发生概率... 使用了JavaScript代码和本地iOS

网页端代码更改

  1. 为您的HTML组件(下拉框)注册“click”事件侦听器。
  2. 在回调方法中向本地代码发送通知。例如:"window.location='fromJavaScript://PopoverIssue';"
  3. 它将调用uiwebviews的shouldStartLoadWithRequest

本地端代码更改

1. 在具有UIWebView的视图控制器上实现UIPopoverPresentationControllerDelegate协议并重写popoverPresentationControllerShouldDismissPopover和popoverPresentationControllerDidDismissPopover方法 2. 将下面的代码放置在上述点击通知的UIWebView的shouldStartLoadWithRequest方法中 ``` [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; self.popoverPresentationController = self.presentedViewController.popoverPresentationController; self.existedPopoverDelegate = [self.popoverPresentationController delegate]; self.popoverPresentationController.delegate = self; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_async(queue, ^{ int64_t delay = 2.0; dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{ if([[UIApplication sharedApplication] isIgnoringInteractionEvents]) { [[UIApplication sharedApplication] endIgnoringInteractionEvents]; } }); }); ``` 3. 实现重写的协议方法如下: ``` (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController { [self.existedPopoverDelegate popoverPresentationControllerShouldDismissPopover:popoverPresentationController]; return YES; }
(void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)popoverPresentationController { [self.existedPopoverDelegate popoverPresentationControllerDidDismissPopover:popoverPresentationController]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_async(queue, ^{ int64_t delay = 2.0; dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{ if([[UIApplication sharedApplication] isIgnoringInteractionEvents]) { [[UIApplication sharedApplication] endIgnoringInteractionEvents]; } }); }); } ```
希望这能帮助减少崩溃的发生。

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