36

I have found that, when compiling for iOS 8 (and running in iOS 8), a UIWebView cannot show the camera/image picker if the UIWebView is in a view controller presented modally. It works without problem in view controllers directly “hanging” from the window rootViewController or view controllers pushed from it.

The test application can be found at https://dl.dropboxusercontent.com/u/6214425/TestModalWebCamera.zip but I will describe it below.

My test application (built with storyboards, but the real application doesn’t use them) has two view controllers (unoriginally named ViewController and ViewController2). ViewController is contained in a UINavigationController which is the root view controller. ViewController contains a UIWebView (works OK), a button that “shows” (“pushes”) ViewController2, and a UIBarButtonItem which modally presents ViewController2. ViewController2 has another UIWebView which works when “pushed” but not when “presented”.

Both ViewController and ViewController2 are loaded with:

- (void)viewDidLoad {
  [super viewDidLoad];

  [self.webView loadHTMLString:@"<input type=\"file\" accept=\"image/*;capture=camera\">" baseURL:nil];
}

When trying to use the modal UIWebView Xcode prints the following in the console and dismisses the app modal:

Warning: Attempt to present <UIImagePickerController: 0x150ab800> on <ViewController2: 0x14623580> whose view is not in the window hierarchy!

My current theory is that the changes in UIActionSheet to UIAlertController might have produced this situation, but it’s quite hard to prove. I will open a Radar with Apple, just in case.

Has someone found the same situation and some workaround?

computingfreak
  • 4,939
  • 1
  • 34
  • 51
yonosoytu
  • 3,319
  • 1
  • 17
  • 23
  • 1
    Currently in the same boat and have not yet found a workaround. The app I'm working on was working fine in iOS 7 but is behaving like you describe in iOS 8. I put a bounty on this question in the hope that it will get more attention. – bloudermilk Sep 22 '14 at 18:08
  • 1
    First of all, input file is broken on safari iOS 8, it doesn't upload anything, it's a bug and will be fixed on next updates. But I don't have your problem, my app just crash when I tap the input file button and select the type (camera or photo library, it doesn't matter) – jcesarmobile Sep 24 '14 at 06:48
  • 1
    Same here. The problem happens when the webview is inside a controller presented as a Modal. I'm trying to figure out a workaound. Still works on iOS 7, but not iOS 8. – Marc Sep 24 '14 at 21:12
  • I have requested a technical support with Apple – Marc Sep 25 '14 at 14:54
  • 1
    I've just tested and the bug is still present on 8.0.2, it works on the original view controller, on the pushed view controller, but not on the modal view controller. – jcesarmobile Sep 26 '14 at 08:41
  • Same problem here, still not solved in 8.0.2 – Accatyyc Sep 26 '14 at 09:02
  • Bug is actual for 8.1.3. – kelin Feb 23 '15 at 13:23
  • still present in iOS 9 – Rob85 Jan 15 '16 at 15:21
  • ive found this with iOS 10.2 today on iPAD – Welsh King Nov 01 '16 at 12:56
  • Continues to be an issue with iOS 10. Note this happens if you are using a UIWebView or WKWebView. – xdeleon Nov 20 '18 at 21:27

7 Answers7

47

I found that in iOS 8.0.2 iPad does not seem to have that bug but iPhone still does.

However, overriding following in the view controller containing the uiwebview

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

And checking that there is a presentedViewController seems to work.

But need to check side effects

#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
seeinvisible
  • 1,146
  • 2
  • 11
  • 16
  • Seems to be a good workaround. Thank you very much. Can I ask how you end up with this solution? – yonosoytu Oct 08 '14 at 06:19
  • 6
    The uiwebview causes the uialertcontroller to be presented on the uiwebviewviewcontroller to show the camera/existing/cancel. When user chooses option, then uialertcontroller is meant to be dismissed and the uuiimagepickercontroller presented. However the dismissing of the uialertcontroller seems to fire twice. The first time is ok because the presentedviewcontroller = the uialertcontroller , but the 2nd time the presentedviewcontroller property is nil, and this causes the dismissviewcontrolleranimated method to kill the webviewviewcontroller which is the problem, to I now test for this case – seeinvisible Oct 08 '14 at 07:06
  • 12
    This solved it for me. However remember, if you're presenting your custom UIWebViewController (or WKWebViewController for that matter) inside a UINavigationController, remember this needs to go in there instead. So just make a subclass of UINavigationController and dump this code in there instead. – Baza207 Oct 08 '14 at 10:18
  • Well, I'm having the same issue, but I'm on Xamarin Studio. I managed to do something like this: [http://pastebin.com/q6Zm8Gii](http://pastebin.com/q6Zm8Gii) but instead of my view closing on input button click, It closes when I click on one of the "take a picture" or "select an existing picture" button. This is anoying. – GabLeRoux Nov 14 '14 at 03:44
  • I tought this may be related to [ios-8-0-uiwebview-file-input-crashes-my-app](http://stackoverflow.com/questions/25898564/ios-8-0-uiwebview-file-input-crashes-my-app), but changing the deployment target from `7.0` to `8.1` or `8.0` did absolutly nothing. Works fine in simulator tho. Screw this and don't use webviews. – GabLeRoux Nov 14 '14 at 04:41
  • Be aware that adding above check will block dismissing this very view controller if it itself presented as modal. In my case it presented by modal segue and above check blocking dismissing it. I fix it by adding following dirty check: `if (self.presentedViewController||!completion)`. It works for me because I always pass `nil` to `completion`. Alternative solution is to use special flag to indicate that this very controller about to be dismissed. – Mike Keskinov Feb 06 '15 at 21:53
  • The same problem came to my app on iOS 8. This code snippet works for me. – Alex.BIG Mar 06 '15 at 07:01
  • could this workaround be applied to the cordova inappbrowser? https://github.com/apache/cordova-plugin-inappbrowser/tree/master/src/ios I can't upload images on iOS8 iphones but it works on iOS7 + iOS8 iPads ... unfortunately I don't know objective C – Riesling Mar 23 '15 at 16:52
  • 1
    possibly. you would need to insert the following at line 724 at https://github.com/apache/cordova-plugin-inappbrowser/blob/master/src/ios/CDVInAppBrowser.m (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion { if ( self.presentedViewController) { [super dismissViewControllerAnimated:flag completion:completion]; } } – seeinvisible Mar 23 '15 at 17:02
  • thanks, doing this + adding the attribute "multiple" to the html input element finally made it work for me – Riesling Mar 26 '15 at 09:35
  • @Riesling Thanks for your input. The solution you provided made it to work for me too, but it doesn't provide an option of "Take photo", instead it directly goes to your photos. Is it the same for you? – Vishwani Mar 30 '15 at 18:36
  • Yes, unfortunately. And I didn't find a workaround so far, so I guess I need to live with that until the bug is fixed. – Riesling Mar 30 '15 at 18:49
  • I actually don't want the user to select more than 1 image. :( If you find any other solution please post it here. Thanks!! – Vishwani Mar 30 '15 at 21:17
  • 1
    @Riesling Hey make sure you add the code in CDVInAppBrowserNavigationController and not in CDVInAppBrowser. You won't need to add the Multiple attribute and it would work fine. :) – Vishwani Mar 30 '15 at 21:50
  • Very great solution, i wish i can give you +100 – TMMDev Nov 21 '15 at 12:22
  • Nice one, seeinvisible. You saved me a lot of time. Please pay attention to what @Baza207 said, as it was exactly my case. – Luke47 Jan 20 '16 at 17:00
  • not working if web view controller is called using push view ! any luck for me? – 9to5ios Dec 28 '16 at 10:31
  • Thank you for the explanation on why this error occurred. Swift 3.1 update to the override method override func dismiss(animated flag: Bool, completion: ((_: Void) -> Void)?) { if presentedViewController != nil { super.dismiss(animated: flag, completion: completion) } else { print("\n Error Check") } } – Octavio Antonio Cedeño May 18 '17 at 13:39
  • Dude you may have saved me from a lot of hair loss today – 1800 INFORMATION Sep 19 '17 at 23:27
  • This is working fine for me and able to load photo/camera , but I am triggering action on second page of webview and after selection of photo it redirects me to home page of webview instead of page from where I load gallary. Any idea on that? – Raj Feb 20 '18 at 10:31
7

I have the same issue on iOS 9. Try to add ivar '_flag' and then override this methods in view controller with 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];
}

This works for me fine

Artem Deviatov
  • 970
  • 12
  • 20
  • what is `trueDismissViewController` ? And should I add this in the first View Controller or the Modal View controller? – esh Jun 08 '16 at 07:00
  • `trueDismissViewControllerAnimated:completion:` is what you should use in this class when you need to dismiss, rather than calling `dismissViewControllerAnimated:completion:` which will not work. – M-P Apr 13 '17 at 14:29
2

For me I tend to have a custom UINavigationController so that multiple views can share the same logic. So for my workaround (in Swift) here is what I put in my custom NavigationController.

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

This means that you can have loads of view controllers with webviews and they'll all work with the file upload element.

Jeremy Paul
  • 43
  • 1
  • 5
1

I was having a similar issue, and I have discovered that the UIWebView elements in IOS do not support the html element:

I am not sure why Apple chose to not support this IMPORTANT html element, but I am sure they have their reasons. (Even though this element works perfectly on Safari on IOS.)

In many cases, when the user clicks this kind of button in a UIWebView, it will let them take/ choose a photo. HOWEVER, the UIWebView in IOS does not have the capability to attach files like this into the POST data when the form is submitted.

The Solution: To accomplish the same task you can create a similar form in InterfaceBuilder with a button that triggers the UIImagePickerController. Then, you create you an HTTP POST request with all of the form data and the image. It isn't as hard as it sounds, check out the link below for some sample code that gets the job done: ios Upload Image and Text using HTTP POST

Community
  • 1
  • 1
Takide
  • 335
  • 3
  • 10
0

Alright, here's the workaround I ended up using. It's a 2 minute change, pretty hacky, but works as expected and avoids the bug we're all having. Basically, rather than presenting the child view controller modally, I set it to the window's rootViewController and keep a reference to the parent controller. So where I used to have this (in parent view controller):

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

I now have this

view.window?.rootViewController = newController

In both cases, the parent controller is newController's delegate, which is how I keep the reference.

newController.delegate = self

Finally, where in my delegate callback for closing the modal, where I used to have this:

dismissViewControllerAnimated(true, completion: nil)

I now have this:

viewController.view.window?.rootViewController = self

So we get the same effect of a view controller taking over the screen entirely, then yielding its control when it's done. In this case though, it's not animated. I have a feeling animating the controller transition wouldn't be very hard with some of the built in view animations.

Hopefully you're already using the delegate pattern to manage communication with the modal controller, per Apple's recommendation, but if you're not I'm sure you can use any number of methods that keep a reference and get called back.

bloudermilk
  • 17,820
  • 15
  • 68
  • 98
  • as you say, it's a workaround, you know there is a bug when the view is modal and then you make it the root view controller instead of presenting it modally. But as you say, there is no anymation, and in case you are in the iPad and you don't want to present it full screen, with your workaround isn't possible. You could just push the view controller and you'll have the same effect but with a slide animation – jcesarmobile Oct 02 '14 at 10:28
  • @jcesarmobile yeah, not exactly happy with it, but at least it let's us distribute our app. Great point regarding iPad. We're iPhone only so I hadn't considered it. Actually, I wonder if the bug even happens on iPad? – bloudermilk Oct 02 '14 at 16:54
  • This doesn't seem to happen on android, but I'm having this issue on iOS 8.1 with xamarin and I didn't manage to code your workaround on xamarin :(. – GabLeRoux Nov 14 '14 at 03:45
0

My solution is to create custom View Controller with custom modal presentation based on controllers child-parent hierarchy. Animation will be the same so the user will not notice the difference.

My suggestions for animation:

animateWithDuration:(animated ? 0.6 : 0.0)
                      delay:0.0
     usingSpringWithDamping:1.f
      initialSpringVelocity:0.6f
                    options:UIViewAnimationOptionCurveEaseOut

It will look exactly like default modal presentation animation in iOS7/8.

kelin
  • 11,323
  • 6
  • 67
  • 104
0

What I did what every time the view controller is change, convert the destination view in root.

from first view controller:

let web = self.storyboard?.instantiateViewController(withIdentifier:"web") as! UINavigationController

view.window?.rootViewController = web

this is how I pass the root to the other, and when comeback to the first one I made this.

from web view controller:

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

and everything is fine, I can show the modal for select photo from library, take photo and everything else.

I don't know if that is a good practice but works fine in emulator and device.

Hope it helps.

MSU_Bulldog
  • 3,501
  • 5
  • 37
  • 73