iOS 8 SDK:模态UIWebView和相机/图片选择器

36

我发现,在编译iOS 8(并在iOS 8上运行)时,如果UIWebView位于通过模态方式呈现的视图控制器中,则UIWebView无法显示相机/图片选择器。但是,如果UIWebView位于直接从窗口的rootViewController“悬挂”的视图控制器或从其推出的视图控制器中,则可以正常工作。

测试应用程序可以在https://dl.dropboxusercontent.com/u/6214425/TestModalWebCamera.zip 找到,但下面我将对其进行描述。

我的测试应用程序(使用storyboard构建,但真实应用程序不使用它们)有两个视图控制器(名称为ViewControllerViewController2)。 ViewController包含在一个UINavigationController中,这是根视图控制器。 ViewController包含一个UIWebView(正常工作),一个“显示”(“推送”)ViewController2 的按钮,以及以模态方式呈现ViewController2UIBarButtonItemViewController2具有另一个UIWebView ,当“推送”但不是“呈现”时,它可以正常工作。

无论是ViewController还是ViewController2都使用下面的加载方法:

- (void)viewDidLoad {
  [super viewDidLoad];

  [self.webView loadHTMLString:@"<input type=\"file\" accept=\"image/*;capture=camera\">" baseURL:nil];
}
尝试使用模态UIWebView时,Xcode会在控制台中打印以下内容并关闭应用程序模态窗口:
Warning: Attempt to present <UIImagePickerController: 0x150ab800> on <ViewController2: 0x14623580> whose view is not in the window hierarchy!

我的当前理论是,UIActionSheetUIAlertController 的变化可能导致了这种情况,但很难证明。我会向苹果公司提交一个雷达报告,以防万一。

是否有人发现了相同的情况并找到了解决方法?


1
目前我也处于同样的困境,还没有找到解决方法。我正在开发的应用在iOS 7上运行良好,但在iOS 8上却表现出你所描述的问题。我在这个问题上设置了赏金,希望能引起更多关注。 - bloudermilk
1
首先,Safari iOS 8 上的输入文件存在问题,无法上传任何内容,这是一个错误,将在下一次更新中修复。但我没有你的问题,我的应用程序只是在我点击输入文件按钮并选择类型(相机或照片库,无论哪种)时崩溃了。 - jcesarmobile
1
一样的问题。当WebView在以模态方式呈现的控制器中时,问题就会发生。我正在尝试找出解决方法。在iOS 7上仍然有效,但在iOS 8上无效。 - Marc
1
我刚刚测试了一下,这个错误在8.0.2上仍然存在。它能在原始视图控制器和推送的视图控制器上运行,但是在模态视图控制器上却不能运行。 - jcesarmobile
同样的问题在这里,8.0.2版本仍未解决。 - Accatyyc
显示剩余5条评论
7个回答

47
我发现在iOS 8.0.2中,iPad似乎没有这个bug,但iPhone仍然存在。

然而,在包含uiwebview的视图控制器中,可以重写以下方法:

-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion

检查是否有presentedViewController似乎有效。

但需要检查副作用。

#import "UiWebViewVC.h"

@interface UiWebViewVC ()

@property (weak, nonatomic) IBOutlet UIWebView *uiwebview;

@end

@implementation UiWebViewVC

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSURL *url = [NSURL URLWithString:@"http://html5demos.com/file-api-simple"];

    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    self.uiwebview.scalesPageToFit = YES;

    [self.uiwebview loadRequest:request];
}


-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
    if ( self.presentedViewController)
    {
        [super dismissViewControllerAnimated:flag completion:completion];
    }
}


@end

6
UIWebView导致UIAlertController出现在UIWebViewController上,以显示相机/现有/取消选项。当用户选择选项时,UIAlertController应该被解雇并且UIIMagePickerController呈现。但是,解雇UIAlertController似乎会发生两次。第一次没问题,因为presentedviewcontroller = uialertcontroller,但第二次presentedviewcontroller属性为空,这会导致dismissviewcontrolleranimated方法终止webviewviewcontroller,这是个问题,所以我现在测试这种情况。 - seeinvisible
12
这对我很有帮助。但请记住,如果您要在 UINavigationController 中呈现自定义的 UIWebViewController(或 WKWebViewController),请放在那里而不是直接放在控制器中。因此,只需创建一个 UINavigationController 的子类并将此代码放入其中即可。 - Baza207
同样的问题出现在我的iOS 8应用程序中。这段代码片段对我有效。 - Alex.BIG
1
可能需要在 https://github.com/apache/cordova-plugin-inappbrowser/blob/master/src/ios/CDVInAppBrowser.m 的第724行插入以下内容:(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion { if ( self.presentedViewController) { [super dismissViewControllerAnimated:flag completion:completion]; } } - seeinvisible
1
@Riesling 嘿,确保你将代码添加到CDVInAppBrowserNavigationController而不是CDVInAppBrowser中。您无需添加Multiple属性,它将正常工作。 :) - Vishwani
显示剩余16条评论

7

我在iOS 9上遇到了相同的问题。尝试添加ivar '_flag',然后在视图控制器中使用UIWebView重写这些方法。

#pragma mark - Avoiding iOS bug

- (UIViewController *)presentingViewController {

    // Avoiding iOS bug. UIWebView with file input doesn't work in modal view controller

    if (_flagged) {
        return nil;
    } else {
       return [super presentingViewController];
    }
}

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

    // Avoiding iOS bug. UIWebView with file input doesn't work in modal view controller

    if ([viewControllerToPresent isKindOfClass:[UIDocumentMenuViewController class]]
    ||[viewControllerToPresent isKindOfClass:[UIImagePickerController class]]) {
        _flagged = YES;
    }

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

- (void)trueDismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {

    // Avoiding iOS bug. UIWebView with file input doesn't work in modal view controller

    _flagged = NO;
    [self dismissViewControllerAnimated:flag completion:completion];
}

这对我来说很好用。

trueDismissViewController 是什么?我应该将其添加到第一个视图控制器还是模态视图控制器中? - esh
在这个类中,当您需要取消时,请使用trueDismissViewControllerAnimated:completion:而不是调用dismissViewControllerAnimated:completion:,因为后者不起作用。 - M-P

2

对于我来说,我倾向于有一个自定义的UINavigationController,这样多个视图可以共享相同的逻辑。所以对于我的解决方法(在Swift中),这就是我放在自定义导航控制器中的内容。

import UIKit

class NavigationController: UINavigationController {

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) {
        if let vc = self.presentedViewController {
            // don't bother dismissing if the view controller being presented is a doc/image picker
            if !vc.isKindOfClass(UIDocumentMenuViewController) || !vc.isKindOfClass(UIImagePickerController) {
                super.dismissViewControllerAnimated(flag, completion:completion)
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // make sure that the navigation controller can't be dismissed
        self.view.window?.rootViewController = self
    }
}

这意味着您可以拥有许多带有Web视图的视图控制器,并且它们都可以使用文件上传元素。

1
我遇到了类似的问题,并发现 IOS 中的 UIWebView 元素不支持 html 元素:
我不确定为什么苹果选择不支持这个重要的 html 元素,但我相信他们有他们的理由。(即使这个元素在 IOS 的 Safari 上可以完美工作。) 在许多情况下,当用户在 UIWebView 中点击这种按钮时,它会让他们拍照或选择照片。然而,在 IOS 中的 UIWebView 没有能力将这样的文件附加到 POST 数据中,当表单被提交时。
解决方案: 为了完成同样的任务,您可以在 InterfaceBuilder 中创建一个类似的表单,其中包含一个触发 UIImagePickerController 的按钮。然后,您可以创建一个 HTTP POST 请求,其中包括所有表单数据和图像。这并不难,查看下面的链接以获取一些完成该任务所需的示例代码: ios Upload Image and Text using HTTP POST

0
每次视图控制器改变时,我所做的是将目标视图转换为根视图。
从第一个视图控制器开始:
let web = self.storyboard?.instantiateViewController(withIdentifier:"web") as! UINavigationController

view.window?.rootViewController = web

这是我如何将根传递给其他人,当回到第一个时我做了这个。

来自 Web 视图控制器:

let first = self.storyboard?.instantiateViewController(withIdentifier: "reveal") as! SWRevealViewController
    present(first, animated: true, completion: nil)

一切都很好,我可以展示从库中选择照片、拍照等模态框。

我不知道这是否是一个好的实践方法,但在模拟器和设备上都运行良好。

希望能对您有所帮助。


0
我的解决方案是创建自定义视图控制器,并基于控制器的子父级层次结构创建自定义模态呈现。动画将保持不变,因此用户不会注意到任何差异。
我的动画建议:
animateWithDuration:(animated ? 0.6 : 0.0)
                      delay:0.0
     usingSpringWithDamping:1.f
      initialSpringVelocity:0.6f
                    options:UIViewAnimationOptionCurveEaseOut

它将与iOS7/8中的默认模态呈现动画完全相同。


0

好的,这是我最终使用的解决方法。虽然有点取巧,但只需要两分钟就能完成,而且可以正常工作,避免了我们所有人都遇到的错误。基本上,与其以模态方式呈现子视图控制器,我将其设置为窗口的rootViewController,并保留对父控制器的引用。因此,在父视图控制器中,我原来写的代码:

presentViewController(newController, animated: true, completion: nil)

我现在有这个

view.window?.rootViewController = newController

在这两种情况下,父控制器都是newController的代理,这就是我保留引用的方式。
newController.delegate = self

最后,在我的委托回调中关闭模态时,我曾经使用以下代码:
dismissViewControllerAnimated(true, completion: nil)

我现在有这个:

viewController.view.window?.rootViewController = self

所以我们可以获得视图控制器完全接管屏幕的相同效果,然后在完成时放弃其控制。但在这种情况下,它没有动画效果。我有一种感觉,使用一些内置的视图动画来实现控制器转换的动画不会很难。

希望您已经按照苹果的建议使用委托模式来管理与模态控制器的通信,但如果您没有,我相信您可以使用任何数量的方法来保持引用并被回调。


就像你所说的,这是一个解决方法,当视图是模态的时候,你将其设置为根视图控制器而不是以模态方式呈现时,你知道存在一个错误。但正如你所说,没有任何动画,在iPad上,如果你不想全屏呈现它,使用你的解决方法是不可能的。你可以只推出视图控制器,就能达到相同的效果,但带有滑动动画。 - jcesarmobile
@jcesarmobile,是的,我对此并不满意,但至少它让我们能够分发我们的应用程序。关于iPad的观点很好。我们只有iPhone版本,所以我没有考虑过它。实际上,我想知道这个错误是否会在iPad上发生? - bloudermilk
这个问题在安卓上似乎没有发生,但我在iOS 8.1上使用xamarin时遇到了这个问题,而且我没能在xamarin上编写你的解决方法 :(。 - GabLeRoux

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