Can I create an NSURL that refers to in-memory NSData? - ios

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.

Related

Loading local file with dataTaskWithURL throws unsupported url - Swift [duplicate]

I've seen many questions on SO concerning converting between NSURL and NSString. They all involve using either NSString *path = [myURL absoluteString]; or NSString *path = [myURL path];. What is the actual difference between these methods? Is there a time when one should be used over the other? I tried consulting the Apple Docs, but I found it less than helpful.
I'm used to URL's only being mentioned in discussions concerning websites and other topics regarding sending information between different machines, and never being mentioned when dealing with just the file structure on a single machine. Perhaps this is where some of my confusion is coming from, since NSURL seems to be the preferred way of accessing files, regardless of whether that file exists on a network or on the local device. Or maybe that's a totally unrelated topic. I'm not even sure.
Question 1:
What is the actual difference between these methods?
Let's analyze this writing 6 lines of code - 3 for a local and 3 for http URL - and playing around with them a little bit.
Let's create an NSURL using the file:// scheme. If you ask yourself why there are 3 / after file: you should remember that a complete URL exists of a scheme (file:// and absolute or relative path (you can find more information on creating URLs in RFC 1808 on page 3). We use an absolute path which starts with a / so that we end up with ///.
NSURL *aLocalURL = [NSURL URLWithString:#"file:///Users/dennis/Desktop/"];
NSLog(#"absolute string: %#", aLocalURL.absoluteString);
NSLog(#"path: %#", aLocalURL.path);
Output:
absolute string: file:///Users/dennis/Desktop/
path: /Users/dennis/Desktop
So we see that absoluteString still knows its scheme whereas path doesn't have this information anymore.
Note: path is a file (directory) URL and as the docs state, the trailing slash it is stripped.
Now let's take a look at remote URLs. With these type of URLs most people are more familiar. We create it using the same procedure as for local URLs. Our scheme is now http:// and our path is www.apple.com/.
NSURL *anHTTPURL = [NSURL URLWithString:#"http://www.apple.com/"];
NSLog(#"absolute string: %#", anHTTPURL.absoluteString);
NSLog(#"path: %#", anHTTPURL.path);
Output:
absolute string: http://www.apple.com/
path: /
Again, we see that the absolute string still knows its scheme but path is now /. So path seems to be not an appropriate way when working with remote URLs.
However, when we have an URL like http://www.apple.com/index.html we get
absolute string: http://www.apple.com/index.html
path: /index.html
Reading the docs helps here, too:
Per RFC 3986, the leading slash after the authority (host name and port) portion is treated as part of the path.
So the path is everything beginning (and including) at the slash after the authority which is www.apple.com in our case.
Question 2
Is there a time when one should be used over the other?
From the docs: (method: path)
If this URL object contains a file URL (as determined with isFileURL), the return value of this method is suitable for input into methods of NSFileManager or NSPathUtilities.
In my opinion that sentence states clearly that you should use path when you work with NSFileManager or NSPathUtilities.
Conclusion:
When you work with remote URLs you (generally) use absoluteString, otherwise the result is not what you (generally) want.
When you work with local URLs use path.
Sources:
http://www.ietf.org/rfc/rfc1808.txt
http://www.ietf.org/rfc/rfc3986.txt
NSURL Class Reference
Adding to HAS' response -- the Apple docs mention that Path-based URLs are simpler in some ways, however file reference URLs have the advantage that the reference remains valid if the file is moved or renamed while your app is running.
From the documentation for "Accessing Files and Directories":
"Path-based URLs are easier to manipulate, easier to debug, and are generally preferred by classes such as NSFileManager. An advantage of file reference URLs is that they are less fragile than path-based URLs while your app is running. If the user moves a file in the Finder, any path-based URLs that refer to the file immediately become invalid and must be updated to the new path. However, as long as the file moved to another location on the same disk, its unique ID does not change and any file reference URLs remain valid."
https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html
One further note, and I've only tried this for Swift and URL not NSURL. The relativeTo form of URL:
URL(fileURLWithPath: aPath, relativeTo: URL)
generates a URL that behaves not fully like a remote URL (as in #HAS above) and not like a file URL.
So, for example:
let url0 = URL(fileURLWithPath: "/Foo")
let url1 = URL(fileURLWithPath: "Bar", relativeTo: url0)
print("\(url1.path)")
// Output: "/Bar\n"
(similar to results for a remote URL, but not a file URL).
If we use absoluteString, we get:
print("\(url1.absoluteString)")
// Output: "file:///Bar\n"
(not similar to either a file URL or a remote URL).

How to get PFFile from disk?

I want to get a PFFile from disk. I learned here that Parse caches all PFFiles as they are downloaded. I am wondering now how to retrieve that cache. What is the correct path. The path I use is suggested here
NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0] stringByAppendingFormat:#"/Caches/Parse/PFFileCache/%#", pffile.name];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:path];
data is always nil.
Am I using the correct path? /Caches/Parse/PFFileCache/
From your description of the problem you should use a slightly different process. Use the PFFile interface initially, but then store the urls from the files for the case where you need to access the file data later. In this way you have a direct link to the file data and can use a normal connection or session to download.
If you don't actually need the PFFile for anything about the download then using the REST interface may be more suitable for you.
Another alternative is to make the same request which originally returned the file information to you, but instructing the API to use cached information only. This is done by setting cachePolicy to CACHE_ONLY on your PFQuery.
I struggled with this in Swift for some time, and found this thread which aided in the answer here:
let fileURL: String = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0] + ("/Caches/Parse/PFFileCache/\(checkedRemoteAsset.name)")
Thanks to you KKendall!

What is difference between URLWithString and fileURLWithPath of NSURL?

In my code I have to use URLWithString to play streaming(HLS) video and fileURLWithPath to play local video.
What is the difference between these two methods?
How should I use single method to play both videos.
Also I need to show last frame as still image when HSL video ends. Its now showing blank screen when it ends. How should i achieve this?
+URLWithString: produces an NSURL that represents the string as given. So the string might be #"http://www.google.com" and the URL represents http://www.google.com.
+fileURLWithPath: takes a path, not a URL, and produces an NSURL that represents the path using a file:// URL. So if you give it /foo/bar/baz the URL would represent file:///foo/bar/baz.
You can of course construct a file URL string manually and pass it to +URLWithString:, but +fileURLWithPath: is simpler to use when you already have a path, as you don't have to deal with escaping the string and coercing it to a URL format.
Similar thing happened in my app which use AVAudioPlayer. I tried with [NSURL URLWithString:path] and found out it fails to open certain mp3 files. I looked into error by a line like [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:path] fileTypeHint:AVFileTypeMPEGLayer3 error:&error], but the error was simply nil
However it was resolved by replacing the url with [NSURL fileURLWithPath:path].
In both case, the path path NSString * #"/var/mobile/Containers/Data/Application/4D96D4AE-2ED4-40B0-85D2-230E1AFA90E7/Documents/01-AudioTrack 01.mp3" 0x1457a8f0 Still I don't know the reason but now I should be careful using [NSURL URLWithString:].
PS. In NSURL Reference document, Apple said as below:
IMPORTANT
To create NSURL objects for file system paths, use fileURLWithPath:isDirectory: instead.
which clearly indicates [NSURL fileURLWithPath:] should be used for open file, though [NSURL URLWithString] also works for some cases.

Getting Image URLs in a Web Directory

I want to get URLs of all images or lets say "JPEG" files in a web directory (www.abcde.com/images). I just want their URLs in an array.. I couldnt manage that. Could u pls help me with this?
Thanks in advance..
Assuming you have access to an index file you could simply load via NSURL the whole html file and cut out the link lines. This however will not work (or hardly work) when you want to search ("spider or crawl") for links in more complex documents. On iOS i would suggest you use the simple, yet quite powerfull "hpple" framework (https://github.com/topfunky/hpple). It is used to parse html. You can search with it for certain html elements, such as <a href...> constructs.
a sample with hpple could looks like this:
NSURL *url = [NSURL URLWithString:#"whatver.com/images"];
NSData *data = [NSData url];
TFHpple *hppleParser = [TFHpple data];
NSString *images = #"//img"; // grabbs all image tags
NSArray *node = [hppleParser searchWithXPathQuery:images]
find a bigger example at http://www.raywenderlich.com/14172/how-to-parse-html-on-ios
Create a server side script(eg php) which gives you a list of all images in that directory as xml or json. From iOS send a request to that script get the xml or JSON parse it and use the image urls.

UIWebView loadHtmlString not working on device

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.

Resources