iOS WebView удаленный HTML с локальными файлами изображений - PullRequest
45 голосов
/ 06 апреля 2011

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

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

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

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

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

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

Ответы [ 5 ]

84 голосов
/ 07 апреля 2011

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

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

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

enter image description here

MyCustomURLProtocol.h

@interface MyCustomURLProtocol : NSURLProtocol
{
    NSURLRequest *request;
}

@property (nonatomic, retain) NSURLRequest *request;

@end

MyCustomURLProtocol.m

#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.m

...

@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];
}

file.html

<html>
<body>
    <h1>we are loading a custom protocol</h1>
    <b>image?</b><br/>
    <img src="myapp://image1.png" />
<body>
</html>
39 голосов
/ 27 ноября 2011

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

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

NSURLProtocolCustom.h

@interface NSURLProtocolCustom : NSURLProtocol
@end

NSURLProtocolCustom.m

#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 ivar в его исходном коде никогда не присваивается, он всегда равен nil (и, если он был назначен, его нужно было где-то выпустить).Этот код не может и не работает.

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

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

2 голосов
/ 05 июня 2014

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

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

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

правый


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


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

<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 (index.html / jquery.js / etc ... используйте корневые уровни для тестирования) в проект и подключитесь к цели


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

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


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

<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

1 голос
/ 07 апреля 2011

Хитрость заключается в предоставлении явного базового URL-адреса для существующего HTML.

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

0 голосов
/ 22 марта 2016

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]];
...