Удаленный html iOS WebView с локальными файлами изображений
Аналогичные вопросы задавали раньше, но я никогда не мог найти решение.
Вот моя ситуация: мой UIWebView загружает удаленную страницу html. Изображения, используемые на веб-страницах, известны во время сборки. Чтобы ускорить загрузку страницы, я хочу упаковать файлы изображений в приложение iOS и подставить их во время выполнения.
[Обратите внимание, что html удален. Я всегда получаю ответы для загрузки как html, так и файлов изображений из локального - я уже это сделал]
Ближайшей рекомендацией, которую я получил, было использование специальной схемы URL-адресов, такой как myapp://images/img.png на странице html и в приложении iOS, перехватить URL-адрес myapp://с подклассом NSURLProtocol и заменить изображение с локальным изображением. Звучит неплохо в теории, но я не сталкивался с полным примером кода, демонстрирующим это.
У меня есть Java-фона. Я мог бы сделать это легко для Android с помощью пользовательского поставщика контента. Я уверен, что подобное решение должно существовать для iOS/ Objective-C. У меня недостаточно опыта в Objective-C, чтобы решить его сам в короткий промежуток времени, который у меня есть.
Любая помощь будет оценена.
Ответы
Ответ 1
Хорошо, вот пример подкласса NSURLProtocol и доставить изображение (image1.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.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>
Ответ 2
Ник Уивер имеет правильную идею, но код в его ответе не работает. Он также нарушает некоторые соглашения об именах, никогда не называйте ваши собственные классы префиксом 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
Проблема с кодом Nick заключается в том, что подклассам NSURLProtocol
не нужно сохранять запрос. NSURLProtocol
уже имеет запрос, и вы можете получить доступ с помощью метода -[NSURLProtocol request]
или свойства с тем же именем. Поскольку request
ivar в его исходном коде никогда не назначается, он всегда nil
(и если он был назначен, он должен был быть выпущен где-то). Этот код не может и не работает.
Во-вторых, я рекомендую прочитать данные файла перед созданием ответа и передать [data length]
как ожидаемую длину содержимого вместо -1.
И, наконец, -[NSURLProtocol stopLoading]
не обязательно является ошибкой, это просто означает, что вы должны прекратить работу над ответом, если это возможно. Пользователь может отменить его.
Ответ 3
Надеюсь, я правильно понимаю вашу проблему:
1) загрузить удаленную веб-страницу... и
2) замените некоторые удаленные ресурсы на файлы в приложении /build
Right?
Хорошо, что я делаю следующим образом (я использую его для видео из-за предела кэширования 5 МБ на Mobile Safari, но я думаю, что любое другое содержимое DOM должно работать одинаково):
• создать локальную (чтобы скомпилировать с помощью Xcode) HTML-страницу с тегами стиля, для того, чтобы подзаголовок in-app/build был заменен на скрытый, например:
<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
Ответ 4
Фокус в том, чтобы предоставить явный базовый URL существующего HTML.
Загрузите HTML в NSString, используйте UIWebView loadHTMLString: baseURL:
с URL-адресом в ваш комплект в качестве базы. Для загрузки HTML в строку вы можете использовать [NSString stringWithContentsOfURL], но это синхронный метод, а при медленном подключении он заморозит устройство. Использование async-запроса для загрузки HTML также возможно, но более активно. Читайте на NSURLConnection
.
Ответ 5
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]];