I have a webview which i want to load using the loadHtmlString method. The problem is that I want to be able to change the img src's with images that i have previously downloaded. I also use google analitics in the html so I need to set the baseUrl to the actual url so it will work. Here comes the problem. If I put the baseUrl in, the images will not load. If I don't set the baseUrl, it works. How can I get around this, so I will be able to both use google analitycs and have the images store locally in my application? I would prefer not having to implement the google analitics sdk in my project.
A strange thing is that if I run it in simulator, and not put the "http://" prefix in front of my baseUrl, it works fine. However, when I run it on a device, I receive the following error and it doesn't work:
Domain=WebKitErrorDomain Code=101 "The URL can’t be shown"
Thanks
EDIT
If I do this, it works:
[appsWebView loadHTMLString:htmlString baseURL:nil];
However, I must provide a baseURL in order to have Google Analitics working, I have two further cases:
This one gives the above mentioned error: (it works ok in simulator but gives error when running on device)
[appsWebView loadHTMLString:htmlString baseURL:[NSURL urlWithString:#"test.com"]];
This one simply doesn't show anything: (neither loads the html string or the url)
[appsWebView loadHTMLString:htmlString baseURL:[NSURL urlWithString:#"http://test.com"]];
I incorrectly assumed that the problem was that the local image was not fully specifying the full path, but that does not appear to be the problem here. But, you are quite right that it appears (somewhat surprisingly) that you cannot specify some web-based baseURL and also reference a local image in your HTML string. No simple solutions are leaping out at me, but at the very least, it appears that you might have a couple of (not very good) options:
First, you could base64 encode the local image using some base64 library like Mike Gallagher's NSData+Base64 category, e.g.:
NSData *imageData = [NSData dataWithContentsOfFile:imagePath];
NSString *imageDataBase64 = [imageData base64EncodedString];
NSString *imageHtml = [NSString stringWithFormat:#"<img src='data:image/png;base64,%#'>", imageDataBase64];
This slows the initial rendering, but maybe it's better than nothing.
Second, you could always try leaving the baseURL as nil, removing the JavaScript that does the Google Analytics from the HTML string, and then try injecting that JavaScript via stringByEvaluatingJavaScriptFromString. This approach may or may not work depending upon the complexity of the Google Analytics JavaScript (e.g. what further web-based references it might have), but there's a outside chance you might be able to do something that way.
My apologies for assuming the problem was a trivial img URL. Clearly you had identified a more fundamental issue.
Original answer:
Create your image URLs in your HTML string to be fully qualified file URLs within your local file system:
The image is either in Documents:
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *imagePath = [documentsPath stringByAppendingPathComponent:imageName];
Or in the bundle:
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName
ofType:nil];
But, once you have fully qualified path, you should be able to use that:
NSURL *imageUrl = [NSURL fileURLWithPath:imagePath];
NSString *imageHtml = [NSString stringWithFormat:#"<img src='%#'>", imageUrl];
I would bet it's a casing issue. Take into account that the Device is case sensitive whereas the Simulator is not. Check the URL and make sure it contains the right characters.
Related
This is strange, but basically I download and save a video locally, and the store the url path to provide to an AVPlayer to play.
This works fine the first time I do it. I download a file, and then I can play it to my hearts content as many times UNTIL I exit the app. When I launch the app a second time, I now get a black screen when I try to play the same exact video using the same exact path.
Because I am using the Simulator I can verify that the videos and pictures indeed very much still exist in the same folder I saved them to, and I can still play them if I click on them from the Finder.
Maybe it's a caching issue? If it matters, I've saved them straight to the Library directory as I test this.
Relevant Code:
NSString *outputFile = [NSString stringWithFormat:#"video_%#.mp4", guid];
NSString *outputDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *tempPath = [outputDirectory stringByAppendingPathComponent:outputFile];
NSURL *fileURL = [NSURL fileURLWithPath:tempPath];
// save the video to the URL
Then I "persist" it using an NSString [fileURL path] (The way I've built this out, assume the solution requires an NSString to NSURL conversion).
Later I create an AVPlayerItem:
NSURL *url = [NSURL fileURLWithPath:persistedObject.contentURL];
NSLog(#"url: %#", url); // prints a valid location**
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
** for example this is a sample url location
url: file:///Users/gabriel/Library/Developer/CoreSimulator/Devices/CE1FC933-808C-4003-9BE4-DEC59B787FF7/data/Containers/Data/Application/FAD072B4-B5B0-4487-8A76-57B047324A00/Library/picture_D8DEAFA5-0843-4AA3-BB32-C61E32D13579.mp4
It's been suggested I use URLForDirectory:inDomain:appropriateForURL:create:error: and URLByAppendingPathComponent: instead, which I will look into. But still confused as to why it would play when I first download it, but not after app exits when it's the same exact file.
You've made a classic mistake. You are persisting the full path. But the full path changes. Never persist a full path. Only persist the part of the path relative to the value obtained from NSSearchPathForDirectoriesInDomains.
Given what you are doing, you should only persist the base filename (outputFile). Then when the app starts, you rebuild the full path again like you did originally but use the persisted filename to append it to the dynamically obtained path to the application support folder.
I am trying to update my app for WatchKit and I save a NSKeyedArchiver file to the NSDocumentsDirectory normally.
With updating to app groups I need to store it in the app groups folder. The issue I am having is I cant figure out how to just get the path, and not have it referenced as a file I am looking for.
The way it is set up now is to find the file it gives the path as a NSString
/Users/ME/Library/Developer/CoreSimulator/Devices/43F/data/Containers/Data/Application/5E/Documents/fav
but when I store to app groups, no matter which way I access the folder, it is returned
file:///Users/ME/Library/Developer/CoreSimulator/Devices/43F/data/Containers/Data/Application/5E/Documents/fav
What is the best way to just obtain the path to the shared group, rather than have the app looking for the direct file?
So coffee deprived me had forgotten about the .path for filemanager.
NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com"];
NSString *tmpPath = [NSString stringWithFormat:#"%#", fileManagerURL.path];
NSString *finalPath = [NSString stringWithFormat:#"%#",[string stringByAppendingString:#"/Favourites2"]];
I was running into the same problem. I was going through the whole process of building a string to my save location and now I'm switching over to app groups and using the
NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupID];
Well, the problem is, now instead of a string to the location that starts with "/Users/yourname/Library..." you get "file:///Users/yourname/Library..."
Here's what I did. I created the NSURL. Then I called absoluteString on it.
NSURL groupPath = [[fileManager containerURLForSecurityApplicationGroupIdentifier:groupID] absoluteString];
I now have a string that I need to strip off the first 7 characters of, then my old code works just fine, except now instead of being in the Documents directory, it's in a shared app group that can be accessed by both my old code and my new watchkit extension.
Here's the code to strip off the first 7 characters (index 6 since you start with 0), you should be able to use either method...
NSString *newGroupPath = [groupPath substringFromIndex:6];
or
NSString *newGroupPath = [groupPath substringWithRange:NSMakeRange(6, [str length]-6)];
This just removes the "file://" from the absoluteString that was made from the NSURL and gives you back your older string path the starts "/Users/YourName/Library/Developer/yada yada yada"
Hope that helps you, I have spent 4 hours figuring it out.
It seems to work for me on the simulator, I haven't tried it on the Watch yet. But at least my app is now working the way it was before, just saving the data in a shared app group. (I have a singleton that manages all of my data throughout my app, and I want that same singleton to provide data to my watch app).
I have an NSString containing some HTML. I’m trying to pre-load that HTML, including any external image links inside it. So, to clarify: I have just HTML, and I need to basically load it, grab its images, and cache that data.
I can do this with a live website using the following code:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [NSString stringWithFormat:#"%#/%#", [paths objectAtIndex:0], #"index.html"];
NSURL *url = [NSURL URLWithString:#"http://www.google.co.uk"];
NSData *urlData = [NSData dataWithContentsOfURL:url];
[urlData writeToFile:filePath atomically:YES];
But this won’t work without an NSURL. I’m wondering if I can make an NSURL somehow from this string of HTML, and substitute it in the URLWithString: method here.
So, question: can I take some local HTML inside an NSString and turn that into an NSURL, so that I can feed it into the code above, and save the both the HTML and any images it links to?
The question you are asking makes absolutely no sense.
A URL is a pointer to the location of a resource online. In this context a html file. You can not make one into the other.
I suggest you create a UIWebView, load the string into that, have it render and cache the result.
[webView loadHTMLString:#"data" baseURL:nil];
I believe it will need to be actually place on the screen for it to render, so make it an invisible 1 X 1 pixel square and it should be fine. Then when the didFinishLoading fires. Cache the result
Yes, you can. You would have to parse the links out of HTML yourself. Real world HTML is not XML compliant, so a quick and dirty way to achieve your goals would be to treat HTML as text and parse links out using a regular expression library.
The docs for NSURL state that:
An NSURL object represents a URL that can potentially contain the
location of a resource on a remote server, the path of a local file on
disk, or even an arbitrary piece of encoded data.
I have a blob of in-memory data that I'd like to hand to a library that wants to load a resource via an NSURL. Sure, I can first write this NSData to a temp file and then create a file:// NSURL from that, but I'd prefer to have the URL point directly to the buffer that I already have present in memory.
The docs quoted above seem to suggest this is possible, but I can't find any hint of how to accomplish it. Am I missing something?
NSURL supports the data:// URL-Scheme (RFC 2397).
This scheme allows you to build URLs in the form of
data://data:MIME-Type;base64,<data>
A working Cocoa example would be:
NSImage* img = [NSImage imageNamed:#"img"];
NSData* imgData = [img TIFFRepresentation];
NSString* dataFormatString = #"data:image/png;base64,%#";
NSString* dataString = [NSString stringWithFormat:dataFormatString, [imgData base64EncodedStringWithOptions:0]];
NSURL* dataURL = [NSURL URLWithString:dataString];
Passing around large binary blobs with data URLs might be a bit inefficient due to the nature of base64 encoding.
You could also implement a custom NSURLProtocol that specifically deals with your data.
Apple has some sample code that uses a custom protocol to pass around image objects: https://developer.apple.com/library/mac/samplecode/SpecialPictureProtocol/Introduction/Intro.html#//apple_ref/doc/uid/DTS10003816
What you are missing is the NSURLProtocol class. Takes about three dozen lines of code, and any code that handles URLs properly can access your in-memory data. Read the documentation, it's not difficult and there is sample code available.
Unfortunately there are some APIs that take an NSURL as a parameter, but can only handle file URLs.
I have found that so far the path sent to application:openURL:sourceApplication:annotation: is:
file://localhost/private/var/mobile/Applications/<GUID>/Documents/Inbox/file
To check that the filesystem operations that I am about to perform are indeed likely to succeed (and that the url given to me is not a location outside the sandbox), it looks like I have to do this:
NSString* hdurl = [[#"file://localhost/private" stringByAppendingString:NSHomeDirectory()] stringByAppendingString: #"/"];
NSString* path = url.absoluteString;
if ([path hasPrefix:hdurl]) {
// now ready to e.g. call fopen on: [path substringFromIndex:#"file://localhost".length]
Now, I seem to vaguely recall (and this is probably wrong) that in the past I have seen the file:/// style URL being used. That would clearly cause this code to fail.
How am I to know that it will always give me a file://localhost URL prefix?
Apple's documentation on URLs is strangely missing a section on file URLs.
An NSURL that points to a file on the local file system is called a "file URL". To convert the NSURL to an NSString representing the file's path you use:
NSString *filePath = [url path];
To check to see if an NSURL represents a file URL, use:
BOOL isFileURL = [url isFileURL];
Keep in mind that if your app is passed a file URL, you will always have access to the file. There is no need to check if it starts with any prefix. Why would iOS pass you a file that you don't have access to?