42

On ios8 and iPad if a uiwebview is displaying a HTML page containing a drop down list

eg this page http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_select

then

  • repeatedly tap on the HTML drop down list that contain lists of cars . first item is Volvo.
  • tap every 1/2 second or so that uipopover opens and closes
  • app will crash:

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

Is there anyway to work around this in uiwebview in ios8?

It doesn't happen using wkwebview, but I would like to fix it in uiwebview.

Update: This seems to help but unsure of side effects. I have overridden the following in the view controller that contains the uiwebview.

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

    [super dismissViewControllerAnimated:NO completion:nil];
}
seeinvisible
  • 1,146
  • 2
  • 11
  • 16
  • I'm seeing the same behaviour after updating to iOS8 with UIWebViews. Were you able to find a solution to this? Would be interested to find out. – abstract_a Sep 29 '14 at 14:09
  • It's happening on all the webviews that I have on my project, but it's not happening on Safari. The issue is present on iOS 8.1. You should file a bug on the Apple portal. – pablobart Sep 30 '14 at 19:36
  • This bug has been reported to Apple (#18513999), it has been marked as a duplicated of #18487570 that is currently Open – pablobart Oct 02 '14 at 16:03
  • thanks @pablobart, the bug i reported is #18505076 – abstract_a Oct 03 '14 at 14:17
  • @abstract_a @pablobart Update: This seems to help but unsure of side effects. I have overridden the following in the view controller that contains the uiwebview. (see body of the question) `-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion` – seeinvisible Oct 08 '14 at 09:05
  • I've issues in iOS 8.0, 8.0.2 and 8.1. But the app have begun to show this issues after compilation with Xcode 6.1. – RFG Nov 04 '14 at 13:19
  • I've the same problem and I've changed all the selects to [Select2](http://ivaynberg.github.io/select2/) – Sergio de la Losa Nov 04 '14 at 11:44
  • @pablobart Where are the bug reports you guys listed above? I tried looking for 18513999, 18487570, 18505076, and 19151009 (commented in the answer below) on http://openradar.appspot.com/ but couldn't find any. – Eternal Rubyist Jan 08 '15 at 23:30
  • @nomizzz bugs reported on apple's site (bugreport.apple.com) are not public, you have to manually submit the bug also to openradar. One of the duplicated ones is on openradar http://openradar.appspot.com/18837004 – pablobart Jan 12 '15 at 19:38
  • This seems to be fixed in iOS 8.3 but still very useful question and answers as older versions still common. – Joseph Lord May 01 '15 at 10:22

5 Answers5

42

The solution mentioned in the question did not help me, however it did point me in the right direction. After some investigation I would say it's some sort of race condition between presenting and removing the popover. As a workaround you can postpone the presentation in the delegate of the 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];
               });
}
dogsgod
  • 6,267
  • 6
  • 25
  • 53
  • A few tests, but this solution works for me. Thanks! – RFG Nov 04 '14 at 12:50
  • Submitted a bug report on this, rdar://19151009 – Seth Spitzer Dec 05 '14 at 00:41
  • 5
    That should be on the controller displaying the UIWebView, not the UIWebView's delegate. Usually these will be the same object, but they don't have to be the same object. – Nicholas Daley-Okoye Jan 09 '15 at 15:15
  • 6
    This fix does not work in all cases. If the user uses the 'forward' or 'back' buttons on the top of the keyboard to traverse the form fields quickly, the app will still crash. – CodingWithoutComments Feb 10 '15 at 21:46
  • which delegate method of UIWebView? and how? How can i get this ViewControllerToPresent from UIWebView ? – Min Soe Mar 16 '15 at 08:44
  • @Min: Please see documentation of https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWebView_Class/ and https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIWebViewDelegate_Protocol/index.html, also see [ask]. – dogsgod Mar 16 '15 at 15:51
9

The previous solutions did not help me.

There is a bug already logged to Apple (see openradar) for this.

The issue seems to be that the web view tries to present a view controller in a popover without setting the sourceView of the popover. Although it's definitely an Apple issue I have used the following workaround to avoid my app to crash:

- (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];
}
tanz
  • 2,557
  • 20
  • 31
1

I worked around it in the following way after noticing that sourceView is set in the cases where it crashes:

-(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];
  }
}
Daz Eddy
  • 332
  • 2
  • 15
  • Yes you're correct but you should also check whether `viewControllerToPresent.popoverPresentationController.sourceView == nil` otherwise you would forbid any popover presentation, even the valid ones ;) – tanz Mar 03 '15 at 16:48
  • Thats what I am checking. Of course you need to be aware that any of your own viewControllers will also be ignored if they have had their sourceView set but that is why it is important to log the fact you are ignoring it. If I ever find this a problem I will probably put a manual exception in the if statement to allow viewControllers of known class types to be presented. I notice that you have modified your answer since I posted mine and it contains the opposite logic to mine. In my debugging I noticed that the app crashed when the sourceView was NOT null, despite what the error message said – Daz Eddy Mar 05 '15 at 11:58
1

I had different exception in the same scenario, and none of workarounds from here helped me.

This was my exception:

Terminating app due to uncaught exception 'NSRangeException', reason: '-[UITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:]: row (4) beyond bounds (0) for section (0).'

This is code I used to workaround it:

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

It's very nasty workaround that prevents from showing dropdown in cases when it's about to crash, and this solution can stop working in any time as it uses internal properties. However it was the only solution that worked for me so maybe it will be helpful for someone.

marcin93w
  • 402
  • 6
  • 13
0

I have decreased the probability of occurrence of crash in this way.. Used javascript code and native ios

Web Side code changes

  1. Register a 'click' event listener to your html component(drop down).
  2. In the call back method send notification to native code. ex : "window.location='fromJavaScript://PopoverIssue'; "
  3. It will call uiwebviews shouldStartLoadWithRequest

Native Side code changes

  1. Implement UIPopoverPresentationControllerDelegate protocol on viewcontroller which has uiwebview and over ride popoverPresentationControllerShouldDismissPopover popoverPresentationControllerDidDismissPopover
  2. put below code in shouldStartLoadWithRequest method of uiwebview for the above click notification
    [[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. implement the overridden protocol methods as follows

    
    
    
    • (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]; } }); }); }

Hope it will help to decrease the crash occurrence .

Uday Sravan K
  • 1,305
  • 12
  • 18