web-dev-qa-db-fra.com

iOS 8 SDK: UIWebView modal et sélecteur de caméra/image

J'ai constaté que, lors de la compilation pour iOS 8 (et sous iOS 8), une UIWebView ne peut pas afficher le sélecteur de caméra/d'image si la UIWebView est dans un contrôleur de vue présenté sous forme modale. Cela fonctionne sans problème en vue des contrôleurs de vue directement "suspendus" à la fenêtre rootViewController ou des contrôleurs de vue poussés à partir de celle-ci.

L'application test peut être trouvée à https://dl.dropboxusercontent.com/u/6214425/TestModalWebCamera.Zip mais je vais le décrire ci-dessous.

Mon application de test (construite avec des storyboards, mais l’application réelle ne les utilise pas) a deux contrôleurs de vue (nommés de manière non originale ViewController et ViewController2). ViewController est contenu dans une UINavigationController qui est le contrôleur de vue racine. ViewController contient une UIWebView (fonctionne correctement), un bouton qui «montre» («pousse») ViewController2 et une UIBarButtonItem qui présente modalement ViewController2. ViewController2 a une autre UIWebView qui fonctionne quand “poussé” mais pas quand “présenté”.

ViewController et ViewController2 sont chargés avec:

- (void)viewDidLoad {
  [super viewDidLoad];

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

Lorsque vous essayez d'utiliser le modal UIWebView Xcode imprime ce qui suit dans la console et rejette le modal d'application:

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

Ma théorie actuelle est que les changements de UIActionSheet à UIAlertController pourraient avoir produit cette situation, mais il est assez difficile à prouver. Je vais ouvrir un radar avec Apple, juste au cas où.

Quelqu'un at-il trouvé la même situation et une solution de contournement?

35
yonosoytu

J'ai trouvé que dans iOS 8.0.2, iPad ne semble pas avoir ce bogue, mais iPhone le reste.

Cependant, le suivi suivant dans le contrôleur de vue contenant le fichier uiwebview

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

Et vérifier qu’il existe un presentsViewController semble fonctionner. 

Mais il faut vérifier les effets secondaires

#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
46
seeinvisible

J'ai le même problème sur iOS 9. Essayez d'ajouter ivar '_flag' puis de remplacer cette méthode dans le contrôleur de vue avec 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];
}

Cela fonctionne bien pour moi

7
Artyom Devyatov

Pour moi, j'ai généralement un UINavigationController personnalisé afin que plusieurs vues puissent partager la même logique. Donc, pour ma solution de contournement (dans Swift), voici ce que j'ai mis dans mon NavigationController personnalisé.

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

Cela signifie que vous pouvez avoir des charges de contrôleurs de vue avec des vues Web et qu'ils fonctionneront tous avec l'élément de téléchargement de fichier.

2
Jeremy JC Paul

J'avais un problème similaire et j'ai découvert que les éléments UIWebView dans IOS ne prennent pas en charge l'élément html:

Je ne sais pas pourquoi Apple a choisi de ne pas prendre en charge cet élément HTML IMPORTANT, mais je suis sûr qu’ils ont leurs raisons. (Même si cet élément fonctionne parfaitement sur Safari sur IOS.)

Dans de nombreux cas, lorsque l'utilisateur clique sur ce type de bouton dans un UIWebView, il le laissera prendre/choisir une photo. CEPENDANT, UIWebView dans IOS ne permet pas de joindre de tels fichiers aux données POST lors de l'envoi du formulaire.

La solution: pour accomplir la même tâche, vous pouvez créer un formulaire similaire dans InterfaceBuilder avec un bouton déclenchant UIImagePickerController. Ensuite, vous créez une demande HTTP POST avec toutes les données du formulaire et l'image. Ce n'est pas aussi difficile que ça en a l'air, consultez le lien ci-dessous pour obtenir un exemple de code permettant d'accomplir le travail: ios Téléchargez une image et du texte via HTTP POST

1
Takide

Ce que j'ai fait et chaque fois que le contrôleur de vue change, convertissez la vue de destination à la racine.

depuis le premier contrôleur de vue:

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

view.window?.rootViewController = web

c'est comme ça que je passe la racine à l'autre, et quand je reviens à la première, j'ai fait ceci.

depuis le contrôleur de vue Web:

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

et tout va bien, je peux montrer le modal pour sélectionner une photo de la bibliothèque, prendre une photo et tout le reste.

Je ne sais pas si c'est une bonne pratique, mais cela fonctionne très bien dans les émulateurs et les périphériques.

J'espère que ça aide.

0
Jason Sa

Bon, voici la solution que j'ai finalement utilisée. C’est un changement de 2 minutes, joli hacky, mais fonctionne comme prévu et évite le bogue que nous avons tous. Fondamentalement, plutôt que de présenter le contrôleur de vue enfant de manière modale, je l'ai défini sur rootViewController de la fenêtre et j'ai gardé une référence sur le contrôleur parent. Donc, là où j'avais l'habitude d'avoir ceci (dans le contrôleur de vue parent):

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

J'ai maintenant ceci

view.window?.rootViewController = newController

Dans les deux cas, le contrôleur parent est le délégué de newController, ce qui permet de conserver la référence.

newController.delegate = self

Enfin, où dans mon rappel de délégué pour la fermeture du modal, où j'avais auparavant:

dismissViewControllerAnimated(true, completion: nil)

J'ai maintenant ceci:

viewController.view.window?.rootViewController = self

Nous obtenons donc le même effet de voir un contrôleur de vue s'emparer entièrement de l'écran, puis céder son contrôle quand c'est fait. Dans ce cas cependant, ce n'est pas animé. J'ai l'impression que l'animation de la transition du contrôleur ne serait pas très difficile avec certaines des animations de vue intégrées.

J'espère que vous utilisez déjà le modèle de délégué pour gérer la communication avec le contrôleur modal, conformément à la recommandation d'Apple, mais si vous n'êtes pas sûr, vous pouvez utiliser plusieurs méthodes qui conservent une référence et sont rappelées.

0
bloudermilk

Ma solution consiste à créer un contrôleur de vue personnalisé avec une présentation modale personnalisée basée sur la hiérarchie parent-parent des contrôleurs. L'animation sera la même pour que l'utilisateur ne remarque pas la différence.

Mes suggestions pour l'animation:

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

Elle ressemblera exactement à l’animation de présentation modale par défaut dans iOS7/8.

0
kelin