iOS 8 SDK: модальный UIWebView и камера / сборщик изображений

Я обнаружил, что при компиляции для iOS 8 (и работает в iOS 8), UIWebView не может показать камеру / сборщик изображений, если UIWebView в представлении контроллер представлен модально. Работает без проблем в виде контроллеров, прямо "висящих" из окна rootViewController или просмотр контроллеров выталкивается из него.

Тестовое приложение можно найти по адресу https://dl.dropboxusercontent.com/u/6214425/TestModalWebCamera.zip но я опишу его ниже.

Мое тестовое приложение (созданное с раскадровками, но реальное приложение не использует их) имеет два контроллера представления (с неоригинальным названием ViewController а также ViewController2). ViewController содержится в UINavigationController который является корневым контроллером представления. ViewController содержит UIWebView (работает ОК), кнопка, которая "показывает" ("нажимает") ViewController2и UIBarButtonItem который модально представляет ViewController2, ViewController2 имеет другой 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!

Моя текущая теория заключается в том, что изменения в UIActionSheet в UIAlertController возможно, произвела эту ситуацию, но это довольно сложно доказать. Я открою Радар с Apple, на всякий случай.

Кто-то нашел такую ​​же ситуацию и какой-то обходной путь?

7 ответов

Решение

Я обнаружил, что в iOS 8.0.2 iPad, похоже, не имеет этой ошибки, но iPhone все еще имеет.

Однако переопределение следующего в контроллере представления, содержащем uiwebview

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

И проверка наличия представленного ViewController, кажется, работает.

Но нужно проверить побочные эффекты

#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

У меня такая же проблема на 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];
}

Это работает для меня нормально

Для меня я склонен иметь собственный UINavigationController, чтобы несколько представлений могли использовать одну и ту же логику. Так что для моего обхода (в Swift) вот что я вставил в свой пользовательский 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
    }
}

Это означает, что вы можете иметь множество контроллеров представления с веб-представлениями, и все они будут работать с элементом загрузки файла.

У меня была похожая проблема, и я обнаружил, что элементы UIWebView в IOS не поддерживают элемент html:

Я не уверен, почему Apple решила не поддерживать этот важный HTML-элемент, но я уверен, что у них есть свои причины. (Хотя этот элемент отлично работает в Safari на IOS.)

Во многих случаях, когда пользователь нажимает кнопку такого типа в UIWebView, он позволяет им делать / выбирать фотографию. ОДНАКО, UIWebView в IOS не имеет возможности прикреплять подобные файлы в данные POST при отправке формы.

Решение: для выполнения той же задачи вы можете создать похожую форму в InterfaceBuilder с помощью кнопки, которая запускает UIImagePickerController. Затем вы создаете HTTP-запрос POST со всеми данными формы и изображением. Это не так сложно, как кажется, проверьте ссылку ниже для примера кода, который выполняет свою работу: ios Загрузить изображение и текст с помощью HTTP POST

Хорошо, вот обходной путь, который я в конечном итоге использовал. Это 2-минутное изменение, довольно хакерское, но работает как положено и позволяет избежать ошибки, с которой мы все сталкиваемся. По сути, вместо того, чтобы представлять дочерний контроллер вида модально, я устанавливаю его в rootViewController окна и сохраняю ссылку на родительский контроллер. Итак, где я имел это (в родительском контроллере представления):

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

Теперь у меня есть это

view.window?.rootViewController = newController

В обоих случаях родительский контроллер newControllerделегат, вот как я держу ссылку.

newController.delegate = self

Наконец, где в моем делегате обратный вызов для закрытия модального режима, где я имел обыкновение иметь это:

dismissViewControllerAnimated(true, completion: nil)

Теперь у меня есть это:

viewController.view.window?.rootViewController = self

Таким образом, мы получаем тот же эффект, что контроллер представления полностью захватывает экран, а затем уступает его контролю, когда все готово. В этом случае, хотя, это не анимировано. У меня есть чувство, что анимация перехода контроллера не будет очень сложной с некоторыми встроенными анимациями вида.

Надеюсь, вы уже используете шаблон делегата для управления связью с модальным контроллером, согласно рекомендации Apple, но если вы не уверены, вы можете использовать любое количество методов, которые сохраняют ссылку и получают обратный вызов.

Мое решение состоит в том, чтобы создать пользовательский View Controller с пользовательским модальным представлением, основанным на иерархии дочерних элементов контроллеров. Анимация будет одинаковой, поэтому пользователь не заметит разницы.

Мои предложения по анимации:

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

Это будет выглядеть точно так же, как стандартная анимация презентации в iOS7/8.

То, что я сделал, что каждый раз, когда вид контроллера меняется, преобразовать вид назначения в корень.

с первого взгляда контроллер:

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

view.window?.rootViewController = web

так я передаю рут другому, а когда вернулся к первому, сделал это.

из контроллера веб-просмотра:

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

и все отлично, могу показать модал для выбора фото из библиотеки, сделать фото и все остальное.

Я не знаю, хорошая ли это практика, но отлично работает в эмуляторе и устройстве.

Надеюсь, поможет.

Другие вопросы по тегам