Pytanie Zdalny html iOS WebView z lokalnymi plikami obrazów


Podobne pytania zadawano wcześniej, ale nigdy nie mogłem znaleźć rozwiązania.

Oto moja sytuacja - mój UIWebView wczytuje zdalną stronę html. Obrazy używane na stronach internetowych są znane w czasie budowy. Aby przyspieszyć ładowanie strony, chcę spakować pliki obrazów w aplikacji na iOS i zastąpić je w środowisku wykonawczym.

[Należy pamiętać, że html jest zdalny. Zawsze otrzymuję odpowiedzi na załadowanie zarówno html jak i plików graficznych z lokalnego - już to zrobiłem]

Najbliższym zaleceniem było użycie niestandardowego schematu adresu URL, takiego jak myapp: //images/img.png na stronie html iw aplikacji na iOS, przechwycenie adresu URL myapp: // z podklasą NSURLProtocol i zastąpienie obrazu lokalną obraz. Brzmiało dobrze w teorii, ale nie znalazłem pełnego przykładu kodu demonstrującego to.

Mam tło Java. Mogłabym to zrobić z łatwością dla Androida za pomocą niestandardowego dostawcy treści. Jestem pewien, że podobne rozwiązanie musi istnieć dla iOS / Objective-C. Nie mam wystarczającego doświadczenia w Objective-C, aby rozwiązać to sam w krótkim czasie, jaki mam.

Każda pomoc zostanie doceniona.


44
2018-04-06 19:59


pochodzenie




Odpowiedzi:


OK tutaj jest przykład jak podklasy NSURLProtocol i dostarczyć obraz (image1.png), który jest już w pakiecie. Poniżej znajduje się nagłówek podklas, implementacja oraz przykład użycia go w viewController (niekompletny kod) i lokalny plik html (który można łatwo wymienić na zdalny). Zadzwoniłem do niestandardowego protokołu: myapp:// jak widać w pliku html na dole.

I dzięki za pytanie! Sam o to pytałem dość długo, czas, który zajęło, aby to zrozumieć, był warty co sekundę.

EDYTOWAĆ: Jeśli ktoś ma problemy z uruchomieniem mojego kodu pod bieżącą wersją systemu iOS, zapoznaj się z odpowiedzią od programu sjs. Kiedy jednak odpowiedziałem na pytanie, to działało. Wskazuje pomocne dodatki i rozwiązuje pewne problemy, więc daj mu również rekwizyty.

Oto jak wygląda mój symulator:

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>

84
2018-04-06 21:26



Fantastyczny! Wygląda dokładnie to, czego szukałem. Wypróbuję to i dam ci znać. - CM Subram
Dlaczego żądanie jest zadeklarowane jako własność podklasy? NSURLProtocol ma już request własność, więc powinieneś po prostu używać self.request. W powyższym kodzie request jest zawsze nil. - Sami Samhuri
@SJS Dobra uwaga, nie mogę powiedzieć, co myślałem, kiedy wprowadziłem tę właściwość, jednak nie zrobiono nic złego, ponieważ ten przykład działał wtedy dobrze. I żadna prośba nie jest zerowa, jest to zarejestrowana podklasa NSURLProtocol. Spójrz na dokumentację statycznej metody registerClass. - Nick Weaver
@NickWeaver Przeczytałem dokumentację. request nigdy nie jest przypisany i jest nil. Używam twojego kodu i żeby to działało, musiałem korzystać z nieruchomości self.request. Wypróbuj ten kod już dziś, to nie działa. Nawet jeśli zadziałało, niewykorzystany ivar jest cruftem, który powinien zostać usunięty. Jeśli przeczytasz dokumentację, zobaczysz także, że - [NSURLProtocol stopLoading] nie jest stanem błędu. Nie powinieneś się logować "Coś poszło nie tak!" kiedy jest to regularna część pomyślnego cyklu żądania. - Sami Samhuri
@ Krutarth Patel Nie bezpośrednio. Będziesz musiał dodać przycisk / link w swoim html, który wyzwala ładowanie określonego adresu URL. Wtedy będziesz musiał przechwycić to w startLoading lub w jednej z metod UIWebViewDelegate, takich jak - webViewDidStartLoad :. - Nick Weaver


Nick Weaver ma dobry pomysł, ale kod w jego odpowiedzi nie działa. Łamie też pewne konwencje nazewnictwa, nigdy nie wymienia swoich klas na NS prefiks, i postępuj zgodnie z konwencją kapitalizacji akronimów, takich jak URL w nazwach identyfikatorów. Będę trzymać się jego nazwy w celu uczynienia tego łatwym do naśladowania.

Zmiany są subtelne, ale ważne: stracić nieprzypisane request ivar i zamiast tego odnoszą się do rzeczywistego żądania dostarczonego przez NSURLProtocol i działa dobrze.

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

Problem z kodem Nicka polega na tym, że podklasy NSURLProtocol nie trzeba przechowywać żądania. NSURLProtocol już ma żądanie i możesz uzyskać dostęp za pomocą tej metody -[NSURLProtocol request]lub własność o tej samej nazwie. Od request ivar w swoim oryginalnym kodzie nigdy nie jest przypisany, zawsze jest nil (a jeśli został przydzielony, powinien był gdzieś zostać wydany). Ten kod nie może i nie działa.

Po drugie, zalecam odczytanie danych pliku przed utworzeniem odpowiedzi i przekazaniem [data length] jako oczekiwaną długość treści zamiast -1.

I w końcu, -[NSURLProtocol stopLoading] niekoniecznie jest błędem, oznacza to tylko, że powinieneś przerwać pracę nad odpowiedzią, jeśli to możliwe. Użytkownik mógł je anulować.


39
2017-11-27 19:29



Dzięki za tę ważną poprawę! Zrobiłem tak, jak powiedziałeś i zadziałało. - Vladimir Obrizan
Czy jest jakiś sposób, aby aplikacja wyświetlała obraz żądany przez stronę internetową załadowaną w przeglądarce Mobile Safari lub w interfejsie UIWebView innej aplikacji? czy ta metoda zadziała? - Danny
@Danny Nie sądzę. Rozumiem, że nie można przechwytywać żądań HTTP ani https. - Sami Samhuri
Wygląda na to, że to rozwiązanie przestało działać z ios8. Czy cacheStoragePolicies zmieniło się z iOS8? - Tim Christmann


Mam nadzieję, że dobrze rozumiem Twój problem:

1) załaduj zdalną stronę ... i

2) zamień określone zasoby zdalne na pliki w aplikacji / kompilacji

Dobrze?


No cóż, robię to w następujący sposób (używam go do filmów z powodu limitu buforowania wynoszącego 5 MB w Mobile Safari, ale myślę, że każda inna treść DOM powinna działać tak samo):


• utworzyć lokalną (do kompilacji za pomocą Xcode) stronę HTML ze znacznikami stylu, dla treści w aplikacji / kompilacji do podstawienia, ustawić na ukryty, np .:

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


• w tym samym pliku podać element treści, np.

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


• (używając tutaj jQuery) załaduj aktualną zawartość ze zdalnego serwera i dołącz lokalny (importowany zasób Xcode) do docelowego elementu div, np.

<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>


• upuść pliki www (index.html / jquery.js / etc ... użyj poziomów root do testowania) do projektu i połącz się z celem


• zdalny plik HTML (tutaj znajduje się na yourserver.com/index-test.html) mający

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


• jak również docelowy element div, np.

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


• i wreszcie w projekcie Xcode załaduj lokalny kod HTML do widoku WWW

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

Działa dla mnie, najlepiej w połączeniu z https://github.com/rnapier/RNCachingURLProtocol, do buforowania offline. Mam nadzieję że to pomoże. fa


2
2018-06-05 08:36





Sztuką jest zapewnienie jawnego podstawowego adresu URL do istniejącego HTML.

Załaduj HTML do NSString, korzystaj z UIWebView loadHTMLString: baseURL: z adresem URL do twojego pakietu jako podstawą. Aby załadować kod HTML do ciągu znaków, można użyć [Ciąg NSString z ContentConture], ale jest to metoda synchroniczna, a przy wolnym połączeniu spowoduje zawieszenie urządzenia. Użycie żądania asynchronicznego do załadowania kodu HTML jest również możliwe, ale bardziej zaangażowane. Czytaj dalej NSURLConnection.


1
2018-04-06 20:36





NSURLProtocol jest dobrym wyborem UIWebView, ale do tej pory WKWebView wciąż nie obsługuje tego. Dla WKWebView możemy zbudować lokalny serwer HTTP do obsługi lokalnego żądania plików, GCDWebServer jest na to dobre:

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

Po określeniu ścieżki pliku lokalnego, dodaj prefiks lokalnego serwera:

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

0
2018-03-22 04:22