iOS WebView удаленный html с локальными файлами изображений

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

вот моя ситуация-мой UIWebView загружает удаленную html-страницу. Изображения, используемые на веб-страницах, известны во время сборки. Чтобы ускорить загрузку страницы, я хочу упаковать файлы изображений в приложение iOS и заменить их во время выполнения.

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

ближайшая рекомендация, которую я получил, - использовать пользовательскую схему url, такую как myapp://images/img.png на html-странице и в приложении iOS перехватите URL-адрес myapp:// с подклассом NSURLProtocol и замените изображение локальным изображением. Звучит хорошо в теории, но я не встречал полного примера кода, демонстрирующего это.

У меня есть Java-фон. Я мог бы сделать это легко для Android, используя пользовательский контент-провайдера. Я уверен, что подобное решение должен существовать для iOS / Objective-C. У меня недостаточно опыта в Objective-C, чтобы решить его самостоятельно за короткий промежуток времени.

любая помощь будет оценили.

5 ответов


Ok вот пример того, как подкласс NSURLProtocol и поставить картинку (рис1.формат PNG), который уже находится в комплекте. Ниже приведен заголовок подклассов, реализация, а также пример использования его в viewController(неполный код) и локальном html-файле(который можно легко обменять на удаленный). Я вызвал пользовательский протокол:myapp:// как вы можете видеть в html-файле внизу.

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

EDIT: Если у кого-то есть трудности с запуском моего кода под текущей версией iOS, пожалуйста, посмотрите на ответ от sjs. Когда я ответил на вопрос, это сработало. Он указывает на некоторые полезные дополнения и исправил некоторые проблемы, поэтому дайте ему реквизит.

вот как это выглядит в моем симулятор:

enter image description here

MyCustomURLProtocol.h

@interface MyCustomURLProtocol : NSURLProtocol
{
    NSURLRequest *request;
}

@property (nonatomic, retain) NSURLRequest *request;

@end

MyCustomURLProtocol.м

#import "MyCustomURLProtocol.h"

@implementation MyCustomURLProtocol

@synthesize request;

+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest
{
    if ([theRequest.URL.scheme caseInsensitiveCompare:@"myapp"] == NSOrderedSame) {
        return YES;
    }
    return NO;
}

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

- (void)startLoading
{
    NSLog(@"%@", request.URL);
    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[request URL] 
                                                        MIMEType:@"image/png" 
                                           expectedContentLength:-1 
                                                textEncodingName:nil];

    NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"image1" ofType:@"png"];  
    NSData *data = [NSData dataWithContentsOfFile:imagePath];

    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    [[self client] URLProtocol:self didLoadData:data];
    [[self client] URLProtocolDidFinishLoading:self];
    [response release];
}

- (void)stopLoading
{
    NSLog(@"something went wrong!");
}

@end

MyCustomProtocolViewController.h

@interface MyCustomProtocolViewController : UIViewController {
    UIWebView *webView;
}

@property (nonatomic, retain) UIWebView *webView;

@end

MyCustomProtocolViewController.м

...

@implementation MyCustomProtocolViewController

@synthesize webView;

- (void)awakeFromNib
{
    self.webView = [[[UIWebView alloc] initWithFrame:CGRectMake(20, 20, 280, 420)] autorelease];
    [self.view addSubview:webView];
}

- (void)viewDidLoad
{   
    // ----> IMPORTANT!!! :) <----
    [NSURLProtocol registerClass:[MyCustomURLProtocol class]];

    NSString * localHtmlFilePath = [[NSBundle mainBundle] pathForResource:@"file" ofType:@"html"];

    NSString * localHtmlFileURL = [NSString stringWithFormat:@"file://%@", localHtmlFilePath];

    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:localHtmlFileURL]]];

    NSString *html = [NSString stringWithContentsOfFile:localHtmlFilePath encoding:NSUTF8StringEncoding error:nil]; 

    [webView loadHTMLString:html baseURL:nil];
}

.HTML-код

<html>
<body>
    <h1>we are loading a custom protocol</h1>
    <b>image?</b><br/>
    <img src="myapp://image1.png" />
<body>
</html>

у Ника Уивера есть правильная идея, но код в его ответе не работает. Он также нарушает некоторые соглашения об именах, никогда не называйте свои собственные классы с помощью NS префикс и следуйте соглашению о капитализации аббревиатур, таких как URL в именах идентификаторов. Я буду придерживаться его имени в интересах того, чтобы это было легко следовать.

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

NSURLProtocolCustom.h

@interface NSURLProtocolCustom : NSURLProtocol
@end

NSURLProtocolCustom.м

#import "NSURLProtocolCustom.h"

@implementation NSURLProtocolCustom

+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest
{
    if ([theRequest.URL.scheme caseInsensitiveCompare:@"myapp"] == NSOrderedSame) {
        return YES;
    }
    return NO;
}

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

- (void)startLoading
{
    NSLog(@"%@", self.request.URL);
    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL 
                                                        MIMEType:@"image/png" 
                                           expectedContentLength:-1 
                                                textEncodingName:nil];

    NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"image1" ofType:@"png"];  
    NSData *data = [NSData dataWithContentsOfFile:imagePath];

    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    [[self client] URLProtocol:self didLoadData:data];
    [[self client] URLProtocolDidFinishLoading:self];
    [response release];
}

- (void)stopLoading
{
    NSLog(@"request cancelled. stop loading the response, if possible");
}

@end

проблема с кодом Ника заключается в том, что подклассы NSURLProtocol не нужно хранить запросу. NSURLProtocol уже есть запрос и вы можете получить доступ с помощью метода -[NSURLProtocol request] или свойство с тем же именем. С request Ивар в своем исходном коде никогда не присваивается, он всегда nil (и если он был назначен, он должен были выпущены где-то). Этот код не может и не работает.

во-вторых, я рекомендую прочитать данные файла перед созданием ответа и передачей [data length] как ожидаемая длина содержимого вместо -1.

и наконец, -[NSURLProtocol stopLoading] не обязательно ошибка, это просто означает, что вы должны прекратить работу над ответом, если это возможно. Возможно, пользователь отменил его.


надеюсь, я правильно понимаю вашу проблему:

1) Загрузите удаленную веб-страницу ... и

2) замените некоторые удаленные активы файлами в приложении / build

верно?


Ну, то, что я делаю, выглядит следующим образом (я использую его для видео из-за ограничения кэширования 5 Мб на мобильном Safari, но я думаю, что любой другой контент DOM должен работать одинаково):


• создать локальная (для компиляции с помощью Xcode) HTML-страница с тегами стилей, для замены содержимого в приложении/сборке, установите значение hidden, например:

<div style="display: none;">
<div id="video">
    <video width="614" controls webkit-playsinline>
            <source src="myvideo.mp4">
    </video>
</div>
</div> 


• в том же файле поставьте контент div, например

<div id="content"></div>


• (используя jQuery здесь) загрузите фактическое содержимое с удаленного сервера и добавьте локальный (импортированный ресурс Xcode) в целевой div, например,

<script src="jquery.js"></script>
<script>
    $(document).ready(function(){
        $("#content").load("http://www.yourserver.com/index-test.html", function(){
               $("#video").appendTo($(this).find("#destination"));           
        });

    });
</script>


• отбросьте файлы www (индекс.html / jquery.js / etc ... используйте корневые уровни для тестирования) в проект и подключитесь к target


• удаленный HTML-файл (здесь находится по адресу yourserver.com/index-test.html) имея

<base href="http://www.yourserver.com/">


• а также пункт назначения div, например,

<div id="destination"></div>


• и, наконец, в вашем проекте Xcode загрузите локальный HTML в веб-представление

self.myWebView = [[UIWebView alloc]init];

NSURL *baseURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
NSString *path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[self.myWebView loadHTMLString:content baseURL:baseURL];

работает удовольствие для меня, лучше всего в сочетании с https://github.com/rnapier/RNCachingURLProtocol, для автономного кэширования. Надеюсь, это поможет. F


трюк состоит в том, чтобы предоставить явный базовый URL существующему HTML.

загрузить HTML в NSString, используйте с URL-адресом в вашем комплекте в качестве базы. Для загрузки HTML в строку вы можете использовать [NSString stringWithContentsOfURL], но это синхронный метод, и при медленном подключении он заморозит устройство. Использование асинхронного запроса для загрузки HTML также возможно,но более активно. Читайте на NSURLConnection.


NSURLProtocol хороший выбор для UIWebView, но до настоящего времени WKWebView все еще не поддерживают его. Для WKWebView мы можем построить локальный HTTP-сервер для обработки запроса локального файла GCDWebServer хорошо для этого:

self.webServer = [[GCDWebServer alloc] init];

[self.webServer addDefaultHandlerForMethod:@"GET"
                              requestClass:[GCDWebServerRequest class]
                              processBlock:
 ^GCDWebServerResponse *(GCDWebServerRequest *request)
{
    NSString *fp = request.URL.path;

    if([[NSFileManager defaultManager] fileExistsAtPath:fp]){
        NSData *dt = [NSData dataWithContentsOfFile:fp];

        NSString *ct = nil;
        NSString *ext = request.URL.pathExtension;

        BOOL (^IsExtInSide)(NSArray<NSString *> *) = ^(NSArray<NSString *> *pool){
            NSUInteger index = [pool indexOfObjectWithOptions:NSEnumerationConcurrent
                                                  passingTest:^BOOL(NSString *obj, NSUInteger idx, BOOL *stop) {
                                                      return [ext caseInsensitiveCompare:obj] == NSOrderedSame;
                                                  }];
            BOOL b = (index != NSNotFound);
            return b;
        };

        if(IsExtInSide(@[@"jpg", @"jpeg"])){
            ct = @"image/jpeg";
        }else if(IsExtInSide(@[@"png"])){
            ct = @"image/png";
        }
        //else if(...) // other exts

        return [GCDWebServerDataResponse responseWithData:dt contentType:ct];

    }else{
        return [GCDWebServerResponse responseWithStatusCode:404];
    }

}];

[self.webServer startWithPort:LocalFileServerPort bonjourName:nil];

при указании пути к локальному файлу добавьте префикс локального сервера:

NSString *fp = [[NSBundle mainBundle] pathForResource:@"picture" ofType:@"jpg" inDirectory:@"www"];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d%@", LocalFileServerPort, fp]];
NSString *str = url.absoluteString;
[self.webViewController executeJavascript:[NSString stringWithFormat:@"updateLocalImage('%@')", str]];