UIWebViewDelegate не контролирует XMLHttpRequest?

Верно ли, что UIWebViewDelegate не контролирует запросы, сделанные с помощью XMLHttpRequest? Если да, есть ли способ отслеживать подобные запросы?

например. UIWebViewDelegate не понимает этого в -(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSMutableURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.google.com", true);

xhr.onreadystatechange=function() 
{
    if (xhr.readyState==4) 
    {
        alert(xhr.responseText);
    }
}

xhr.send();

Ответ 1

Интересный вопрос.

Для выполнения этой работы есть две части: обработчик JavaScript и методы делегирования UIWebView. В JavaScript мы можем модифицировать методы прототипов для запуска событий при создании запроса AJAX. С нашим делегатом UIWebView мы можем зафиксировать эти события.

Обработчик JavaScript

Нам нужно получить уведомление, когда будет сделан запрос AJAX. Я нашел решение здесь.

В нашем случае, чтобы заставить код работать, я поместил следующий JavaScript в ресурс с именем ajax_handler.js, который связан с моим приложением.

var s_ajaxListener = new Object();
s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open;
s_ajaxListener.tempSend = XMLHttpRequest.prototype.send;
s_ajaxListener.callback = function () {
    window.location='mpAjaxHandler://' + this.url;
};

XMLHttpRequest.prototype.open = function(a,b) {
  if (!a) var a='';
  if (!b) var b='';
  s_ajaxListener.tempOpen.apply(this, arguments);
  s_ajaxListener.method = a;  
  s_ajaxListener.url = b;
  if (a.toLowerCase() == 'get') {
    s_ajaxListener.data = b.split('?');
    s_ajaxListener.data = s_ajaxListener.data[1];
  }
}

XMLHttpRequest.prototype.send = function(a,b) {
  if (!a) var a='';
  if (!b) var b='';
  s_ajaxListener.tempSend.apply(this, arguments);
  if(s_ajaxListener.method.toLowerCase() == 'post')s_ajaxListener.data = a;
  s_ajaxListener.callback();
}

Что это на самом деле делает, это изменить расположение браузера на некоторую составленную схему URL (в данном случае mpAjaxHandle) с информацией о сделанном запросе. Не беспокойтесь, наш делегат поймает это, и местоположение не изменится.

Делегат UIWebView

Во-первых, нам нужно прочитать наш файл JavaScript. Я предлагаю хранить его в статической переменной. У меня есть привычка использовать + initialize.

static NSString *JSHandler;

+ (void)initialize {
    JSHandler = [[NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ajax_handler" withExtension:@"js"] encoding:NSUTF8StringEncoding error:nil] retain];
}

Затем мы хотим добавить этот JavaScript до того, как страница будет загружена, чтобы мы могли получать все события.

- (void)webViewDidStartLoad:(UIWebView *)webView {
    [webView stringByEvaluatingJavaScriptFromString:JSHandler];
}

Наконец, мы хотим захватить событие.

Так как схема URL составлена, мы не хотим ее вообще следовать. Мы возвращаем NO, и все хорошо.

#define CocoaJSHandler          @"mpAjaxHandler"

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if ([[[request URL] scheme] isEqual:CocoaJSHandler]) {
        NSString *requestedURLString = [[[request URL] absoluteString] substringFromIndex:[CocoaJSHandler length] + 3];

        NSLog(@"ajax request: %@", requestedURLString);
        return NO;
    }

    return YES;
}

Я создал образец проекта с решением, но не могу его разместить. Вы можете сообщить мне, если вы можете разместить его, и я отредактирую это сообщение соответственно.

Ответ 2

Вы можете использовать NSURLProtocol. Например, если вы вызываете XMLHttpRequest с помощью http://localhost/path, вы можете обрабатывать его со следующим:

@interface YourProtocol: NSURLProtocol

Затем для реализации:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request 
{
    return [request.URL.host isEqualToString:@"localhost"];
}

+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}

- (void) startLoading
{
    // Here you handle self.request 
}

- (void)stopLoading
{
}

Вам необходимо зарегистрировать протокол следующим образом:

    [NSURLProtocol registerClass:[YourProtocol class]];

Ответ 3

Внедряя и регистрируя подкласс NSURLProtocol, вы можете захватить весь запрос из вашего UIWebView. В некоторых случаях это может быть чрезмерным, но если вы не можете изменить исполняемый javascript, это ваш лучший выбор.

В моем случае мне нужно захватить весь запрос и вставить конкретный HTTP-заголовок для каждого из них. Я сделал это, выполнив NSURLProtocol, зарегистрировав его с помощью registerClass и отвечая YES в моем подклассе на + (BOOL) запрос canInitWithRequest: (NSURLRequest *), если запрос соответствует интересующим вас URL-адресам. Затем вам нужно реализовать другие методы протокола, это можно сделать, используя NSURLConnection, установив класс протокола в качестве делегата и перенаправляя методы делегата NSURLConnection на NSURLProtocolClient

Ответ 4

Кажется, это правда. Невозможно отслеживать, что делает UIWebView за пределами того, что предоставляет UIWebViewDelegate, если, возможно, вы не сможете найти способ использовать stringByEvaluatingJavaScriptFromString: для ввода некоторого Javascript, чтобы делать то, что вам нужно.

Ответ 5

Как упоминалось в других языках, UIWebViewDelegate отмечает изменения только window.location и iframe.src.

Если вы просто хотите использовать настраиваемую схему URL-адресов только для iOS, вы можете использовать <iframe> таким образом.

Например, если вы хотите вызвать схему URL, подобную этой

objc://my_action?arg1=1&arg2=2

в файле js:

/**
* @param msg : path of your query
* @param data : arguments list of your query
*/
var call_url = function(msg, data){
    const scheme = "objc";
    var url = scheme + '://' + msg;
    if(data){
        var pstr = [];
        for(var k in data)
            if(typeof data[k] != 'function')
                pstr.push(encodeURIComponent(k)+"="+encodeURIComponent(data[k]));
        url += '?'+pstr.join('&');
    }
    var i = document.createElement("iframe");
    i.src = url;
    i.style.opacity=0;
    document.body.appendChild(i);
    setTimeout(function(){i.parentNode.removeChild(i)},200);
}

//when you call this custom url scheme
call_url ("my_action", {arg1:1,arg2:2});