Подтвердить что ты не робот

Удаленный 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, чтобы решить его сам в короткий промежуток времени, который у меня есть.

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

4b9b3361

Ответ 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]];