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)
и все отлично, могу показать модал для выбора фото из библиотеки, сделать фото и все остальное.
Я не знаю, хорошая ли это практика, но отлично работает в эмуляторе и устройстве.
Надеюсь, поможет.