How to cache entire webpage in iphone? - iphone

I want to load a webpage when user connected to network and store it offline(including with images/resources). If the user not connected to any network then i should load the previously stored webpage. I have tried NSData dataWithContentsOfURL:(NSURL *)url and NSString stringWithContentsOfURL but these stores only html content not the resources.
Thanks in advance.

You can do that with ASIHttpRequest. If you do not want to to use that project (it is no longer active) you can look into the code and what it does. Look at "how to cache a whole web page with images in iOS" for more info as well.

I think the simple solution is this - "Safari Client-Side Storage and Offline Applications Programming Guide", https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/Introduction/Introduction.html
Only if you are making an app with HTML5 and webview, didn't test this method yet so far, so it might work.

Write this data into file using:
-(void)writeDataToFile:(NSString*)filename
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
if(filename==nil)
{
DLog(#"FILE NAME IS NIL");
return;
}
// the path to write file
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat: #"%#",filename]];
/*NSData *writeData;
writeData=[NSKeyedArchiver archivedDataWithRootObject:pArray]; */
NSFileManager *fm=[NSFileManager defaultManager];
if(!filePath)
{
//DLog(#"File %# doesn't exist, so we create it", filePath);
[fm createFileAtPath:filePath contents:self.mRespData attributes:nil];
}
else
{
//DLog(#"file exists");
[self.mRespData writeToFile:filePath atomically:YES];
}
NSMutableData *resData = [[NSMutableData alloc] init];
self.mRespData=resData;
[resData release];
}
and load it next time.

I don't know if there is one-line-solution like myWebView.cache4Offline = YES; , but I fear as long as you don't have access to the website's code (i.e. if you want to make any website available offline inside your app), you have to program this on your own. Thinking about it, it doesn't seem so difficult:
Scan the html string for image urls (and everything else you need)
Download those resources from the internet using NSData dataWithContentsOfURL (maybe a little annoying, because of relative/absolute URLs)
Save data to file with NSData writeToFile:options:error:
Replace URL in HTML with filePath from 3. (OR, better use a convention for converting their URLs in your file-URLs)
Hope it helps

Related

is saving in NSDocumentDirectory okay?

My app is using the NSDocumentDirectory to save images in it, I just wanna ask if its the safe way to save images(100 maximum). I have read several thread & questions with answers about it, though I dont know which to follow.Some say that its okay to save there. Some say I shouldnt use NSDocumentDirectory for saving, because it will be back-up by the iCloud. So where can I save it that when the user exit the app then run the app again, then images should still be there?. I dont know much about the tmp directory or cache directory. But if its either one of the 2 that I should use, How can I use them in my code here:
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask ,YES );
NSString *documentsDir = [paths objectAtIndex:0];
NSString *savedImagePath = [documentsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"Images%d.png", i]];
ALAssetRepresentation *rep = [[info objectAtIndex: i] defaultRepresentation];
UIImage *image = [UIImage imageWithCGImage:[rep fullResolutionImage]];
//----resize the images
image = [self imageByScalingAndCroppingForSize:image toSize:CGSizeMake(256,256*image.size.height/image.size.width)];
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:savedImagePath atomically:YES];
Thank you so much for the help.
The tmp and cache directories are periodically cleaned up by iOS. If the images are for general use, use the camera roll as the other two answers suggest. However if these images are intended just for the scope of your app, you can still safely store them in the Documents directory, you just have to include an "exclude from iCloud backup" function call to each file after saving, in order to prevent Apple rejecting your app for using too much iCloud space. Of course there's a trade-off, disabling this means the user will lose their photos anyway should they delete the app or get another device(etc), but this caveat is preferable to not getting the App on the store at all.
To disable iCloud backup on a file, there's two methods for iOS versions > 5.0:
UPDATE! MERGED BOTH METHODS INTO A SINGLE FUNCTION THAT AUTOMATICALLY HANDLES iOS VERSION:
#include <sys/xattr.h> // Needed import for setting file attributes
+(BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)fileURL {
// First ensure the file actually exists
if (![[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
NSLog(#"File %# doesn't exist!",[fileURL path]);
return NO;
}
// Determine the iOS version to choose correct skipBackup method
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
if ([currSysVer isEqualToString:#"5.0.1"]) {
const char* filePath = [[fileURL path] fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
NSLog(#"Excluded '%#' from backup",fileURL);
return result == 0;
}
else if (&NSURLIsExcludedFromBackupKey) {
NSError *error = nil;
BOOL result = [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
if (result == NO) {
NSLog(#"Error excluding '%#' from backup. Error: %#",fileURL, error);
return NO;
}
else { // Succeeded
NSLog(#"Excluded '%#' from backup",fileURL);
return YES;
}
} else {
// iOS version is below 5.0, no need to do anything
return YES;
}
}
If your app must support 5.0, then unfortunately your only option is to save those photos in the Caches directory, which means they won't be backed up (this not causing an App Store rejection for that reason), but whenever the storage watchdog decides it's time to clean the Caches folder, you'll lose those photos. Not an ideal implementation at all, but such is the nature of the beast in 5.0, where Apple added in Backup exclusion as an afterthought.
EDIT: Forgot to answer the 'how to save to the tmp/cache directory' part of the question. If you do decide to go down that path:
Saving to tmp:
NSString *tempDir = NSTemporaryDirectory();
NSString *savedImagePath = [tempDir stringByAppendingPathComponent:[NSString stringWithFormat:#"Images%d.png", i]];
(note that this won't appear to have any effect in the simulator, but it works as expected on device)
Saving to Cache:
NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES)lastObject];
NSString *savedImagePath = [cacheDir stringByAppendingPathComponent:[NSString stringWithFormat:#"Images%d.png", i]];
If you want the user to be able to use the images in other apps or view them along with their photos, use the photo album as Mike D suggest. If the files are something you generate locally for use with your app only, then you should probably use the documents directory. You can expose the documents directory to iTunes with the info.plist option "Application supports iTunes file sharing" which will allow the user to add or delete files through iTunes, but the files will not be exposed to other apps
You are saving scaled images so they are really only useful for your game. They are not going to be very large and will not take up much space. You could save them in the Library directory for the app and avoid the whole iCloud thing, as it doesn't sound like there is any reason to back them up. Also, saving the the Library avoid the possibility of the user deleting them, if for some other reason you have iTunes sharing turned on.
Update: code for saving to the app Library directory
- (void)saveSequences:(NSMutableDictionary*)sequences
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libDirectory = [path objectAtIndex:0];
NSString *settingsPath = [libDirectory stringByAppendingPathComponent:#"userSequences.plist"];
NSLog(#"settingsPath %#", settingsPath);
[sequences writeToFile:settingsPath atomically:YES];
}
// The code below gets the path to a named directory in the 'Documents' folder - and if it doesn't exist, creates it. Adjust it to use the Library path, if you decide to go that route.
- (NSString *)getDirectoryBySequenceName:(NSString *)sequenceName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentDirectory = [paths objectAtIndex:0];
NSString * sequenceDirectory = [documentDirectory stringByAppendingPathComponent:sequenceName];
NSError *error;
BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:sequenceDirectory
withIntermediateDirectories:YES
attributes:nil error:&error];
if (!success) {
NSLog(#"Error creating data path: %#", [error localizedDescription]);
}
return sequenceDirectory;
}
Depending on the purpose of your app, you could save it to the photos app (UIImageWriteToSavedPhotosAlbum(image, self, nil, nil) I think, Apple reference). Saving here, or in the documents directory (or any sub folder), will allow the user backup those images to iCloud or iTunes, if the user chooses too and/or if they have set up iCloud.
Since you state the images need to persist between launches, the temp or cache directory get emptied when the application is removed from memory, maybe sooner (the O/S decides).
More about the iOS file system.

iTunes file sharing. But sharing over http? iOS

I have a file which I want to file share. 'hello.mp3'
At the moment the code enables file sharing if the file is in the app.
I was hoping I would be able to do it over http. So file sharing with the link instead of the sound being in the app. I want to do this because it will save memory for the user.
Here is the current code. The code allows file sharing if the file is in the app. I want it so the user will 'download' the sound from a http server such as http://test.com/hello.mp3
Thanks
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSArray *names = [NSArray arrayWithObjects: #"hello.mp3",
#"hi.mp3", nil];
for (NSString *fileName in names)
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *documentDBFolderPath = [documentsDirectory stringByAppendingPathComponent:fileName];
if (![fileManager fileExistsAtPath:documentDBFolderPath])
{
NSString *resourceDBFolderPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName];
[fileManager copyItemAtPath:resourceDBFolderPath toPath:documentDBFolderPath error:&error];
}
}
}
I may be wrong, but that may not be allowed. In the guidelines:
2.7 Apps that download code in any way or form will be rejected.
While an audio file may not be "code", if it is used within the app, perhaps as a language translation item, etc...I would think it would be rejected...just my 2 cents worth though.
Correct, you can "stream" content over HTTP, but if your using HTTP AND saving to the "Documents" directory to hold content you risk rejection by the app store.
If you need a workaround maybe use the "tmp" directory and save a file off a file there. The complaint Apple has is mainly using phone data more than should be in a 3rd party application.

Saving a file to a user's device where the webview can see it

I have a hybrid (Objective C + HTML) application and I would like to be able to periodically save a remote file (images and css) to a user's device but in such a way that it can be loaded by a browser within a webview instance in the same application.
I don't think it's possible to save files to the resource bundle itself (though in the simulator you can), so I assume I would have to save the file somewhere else. But I'm not sure what path to use where the HTML document could still access it.
Basically, I'd like to do something like this:
NSURL* url = [NSURL URLWithString:#"http://myserver.com/August.png"];
NSString* filePath = #"[?????]/MonthlyImage.png";
NSData* data = [NSData dataWithContentsOfURL:url];
if (data)
{
NSError* error;
if ([data writeToFile:filePath options:NSDataWritingAtomic error:&error])
{
NSLog(#"Wrote");
}
if (error != nil)
{
NSLog(#"ERROR: %#", [error description]);
}
}
Then in my webview, I'd like to be able to load the image like this:
<img src="[??????]/MonthlyImage.png" />
What values can I use in place of the ?????? that will work? Is it even possible?
Saving to the bundle is not allowed because it is not allowed to modify the app binary.
You could try and store the file in the documents folder, then retrieve it from there. The important point, to make it work, is specifying also a baseURL when creating the UIWebVIew.
You can retrieve the documents directory in this way:
NSString *documentsDirectory = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
if ([paths count] > 0) {
documentsDirectory = [paths objectAtIndex:0];
}
Then you store there your file, and when you want to load it in your UIWebView, execute:
[_label loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:documentsDirectory]];
Now, <img src="MonthlyImage.png" /> will look for the png in your documents directory.

Can I parse a XML file that is on a web server without downloading the file?

I need to parse a XML file which is located on my server and return the node values without ever downloading the file to the device. Right now, the file is downloading to the device for testing purposes. Here is my current code:
+ (NSString *)dataFilePath:(BOOL)forSave {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *documentsPath = [documentsDirectory stringByAppendingPathComponent:#"file.xml"];
return documentsPath;
}
+ (NSString *)parse:(NSString *)nodesForPath:(NSString *)elementsForName {
NSString *filePath = [self dataFilePath:FALSE];
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
if (doc == nil) { return nil; }
ViewController *view = [[[ViewController alloc] init] autorelease];
NSArray *getVersionInfo = [doc nodesForXPath:nodesForPath error:nil];
for (GDataXMLElement *versionInfo in getVersionInfo) {
NSArray *elm1 = [versionInfo elementsForName:elementsForName];
GDataXMLElement *elm2 = (GDataXMLElement *) [elm1 objectAtIndex:0];
return elm2.stringValue;
}
[doc release];
[xmlData release];
}
It works fine but it parses the file in the document's directory. How would I set it up to parse directly from the web server? Thanks
The only way I can see to do this would be to run a program on the server to parse the file and return the result to the phone.
For the phone to run the code that parses the file it has to read the file which, by definition, will download it.
You could try using a pull parser for the XML and just work with the data received from the network as a stream, discarding unneeded contents.
You read a few bytes from the response, parse them, process, discard, read some more. You might have to make your own implementation of a pull parser because your dealing with incomplete, hence invalid, XML or to look up a parser that's tolerant to data ending (definitely not a validating one).
It might be worth-while to make your own implementation for the parser if you are dealing with really huge XML files. This way you can only store pieces of the XML that you need (you might need part of the XML 'tree' or none at all or you might need to retain other nodes referenced by another node in your XML).
I'm sorry for the generic answer, but I haven't used XML parsers on iOS yet and I'm not anywhere near a Mac atm to be able to test if NSXMLParser works for you.
Edit: Related post here.
Even if you don't want to save the file to your hard disk, you must have the contents of that file loaded on your RAM.
Convert NSData* to NSString, and then NSString to char* and give that to RapidXML.

iPhone Persistent Storage

I need to store certain information while my application is executing and again fetch it at the time the application starts. I tried storing it in XML using GData but didn't succeed.
I used the NSFileHandle it doesn't give me an error but it fails to create a .txt file for read / write purpose. Is there any other way of storing and retrieving the data on the iPhone. Below is my code for NSFileHandle.
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"myFile.txt"];
//NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:#"file://localhost/Users/shraddha/Desktop/info.txt"];
NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:#"myFile.txt"];
[fh seekToEndOfFile];
NSData *data = [camName dataUsingEncoding:NSASCIIStringEncoding];
[fh writeData:data];
[fh closeFile];
For Reading
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"myFile.txt"];
//NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:#"file://localhost/Users/shraddha/Desktop/info.txt"];
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:#"myFile.txt"];
if(fh == nil)
return nil;
else
{
NSData *data = [fh readDataOfLength:8];
NSString *retStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
return retStr;
}
You're trying to write to the resource directory which you probably don't have permission to do. Try changing the first line of your code to point to the document directory:
NSArray *savePaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSMutableString *savePath = [NSMutableString stringWithString:[savePaths objectAtIndex:0]];
[savePath appendString:#"/myFile.txt"];
Also, you don't need to worry about file handles. NSData is capable of writing itself to disk:
BOOL result = [data writeToFile:savePath atomically:YES];
You can use a sqlite database to store persistent data on the iPhone. Here is a blog post that should get you pointed in the right direction:
http://dblog.com.au/iphone-development-tutorials/iphone-sdk-tutorial-reading-data-from-a-sqlite-database/
Every iPhone application has at its disposal the ability to read/write name/value pairs which can be very, very useful for storing small information like user preferences. These preferences can also be edited by the user of your application (if you choose).
Another option you have which is more robust than trying to do text file storage and retrieval (I never liked this option, especially with the crappy XML parsing support on the iPhone) is sqlite. sqlite is a really light-weight relational database engine that is included with every iPhone and iPod touch.