Parse - How to save a html file in iOS device - ios

I'm using Parse for my iOS app and I'm trying to locally save my HTML file from Parse core data to my device's local storage. I'm doing this so the user can still access the data even when they're offline. I've read about local datastore and cache policy and have tried both. With local datastore, it doesn't let me load the HTML to my UIWebView but it loads everything else (probably due to the HTML needing internet to access it). Cache policy actually works but only accesses the more recent items (just as a cache should work).
I am asking on here to see what I can do to locally save these HTML files from Parse so a user can access them even when the internet is gone.
Thanks!

You can use PFFile's method getData to get NSData and save it to NSUserDefaults:
let data = file.getData()
NSUserDefaults.standardUserDefaults().setObject(data, forKey: file.name)
...and when you need to read it:
let data = NSUserDefaults.standardUserDefaults().dataForKey(file.name)
This can help you display the page in WebView: How do I convert HTML NSData to an NSString?
Update: UIWebView has method to show NSData: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWebView_Class/#//apple_ref/occ/instm/UIWebView/loadData:MIMEType:textEncodingName:baseURL:

Related

Intermittent data loss with background fetch - could NSKeyedUnarchiver return nil from the documents directory?

I have a simple app that stores an array of my custom type (instances of a class called Drug) using NSCoding in the app’s documents folder.
The loading and saving code is an extension to my main view controller, which always exists once it is loaded.
Initialisation of array:
var drugs = [Drug]()
This array is then appended with the result of the loadDrugs() method below.
func saveDrugs() {
// Save to app container
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(drugs, toFile: Drug.ArchiveURL.path)
// Save to shared container (for iMessage, Spotlight, widget)
let isSuccessfulSaveToSharedContainer = NSKeyedArchiver.archiveRootObject(drugs, toFile: Drug.SharedArchiveURL.path)
}
Here is the code for loading data.
func loadDrugs() -> [Drug]? {
var appContainerDrugs = NSKeyedUnarchiver.unarchiveObject(withFile: Drug.ArchiveURL.path) as? [Drug]
return appContainerDrugs
}
Data is also stored in iCloud using CloudKit and the app can respond to CK notifications to fetch changes from another device. Background fetch also triggers this same method.
// App Delegate
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// Code to get reference to my main view controller
// This will have called loadDrugs() to populate local array drugs of type [Drug]
mainVC.getZoneChanges()
}
Finally, there is the getZoneChanges() method, which uses a stored CKServerChangeToken to get the changes from the private user database with CKFetchRecordZoneChangesOperation. The completion block calls saveDrugs().
The problem
All of this seems to work fine. However, sometimes all local data disappears between uses of the app, especially if it has not been used for some time. Deleting and reinstalling the app does pull the backed-up data from iCloud thankfully.
It seems to happen if the app has not been used for a while (presumably terminated by the system). Something has to have changed, so I presume it is the calling of a background fetch when the app is terminated that may be the problem. Everything works fine while debugging and when the app has been in foreground recently.
Possible causes
I’m guessing the problem is that I depend on background fetch (or receiving a CK notification) loading my main view controller in the background and then loading saved local data.
I have heard that UserDefaults does not work correctly in the background and there can be file security protections against accessing the documents directory in this context. If this is the case here, I could be loading an empty array (or rather initialising the array and not appending the data to it) and then saving it, overwriting existing data, all without the user knowing.
How can I circumvent this problem? Is there a way to check if the data is being loaded correctly? I tried making a conditional load with a fatal error if there is a problem, but this causes problems on the first run of the app as there is no data anyway!
Edit
The archive URLs are obtained dynamically as shown below. I just use a static method in my main data model class (Drug) to access them:
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("drugs")
The most common cause of this kind of issue is being awakened in the background when the device is locked and protected data are encrypted.
As a starting point, you can check UIApplication.isProtectedDataAvailable to verify that protected data is available. You can also lower the protection levels of data you require to .completeUntilFirstUserAuthentication (the specifics of how to do that depends on the how you create your files).
As a rule, you should minimize reducing data protection levels on sensitive information, so it's often best to write to some other location while the device is locked, and then merge that once the device is unlocked.
The problem is that you are storing the full path of the file and not just the fileName (or relative path). The document directory URL can change, and then if you stored that URL persistently you will not be pointing to the correct location of the file. Instead just store the filename and use NSFileManager URLsForDirectory to get the documents directly every time you need it.

Loading local images into WKWebView

I'm trying to get WKWebView to display locally downloaded images in a WKWebView. The webview normally displays HTML, which is retrieved remotely. The contents of the HTML can sometimes contain remote links to images. My app parses the HTML and looks for these HTML tags, downloads the file it is referencing and subsequently replaces the remote link with a local one.
Normally speaking, this wouldn't be very difficult but the images aren't being displayed, presumably due to the images and the local HTML files for the webview being in two separate directories (the documents directory and the app bundle directory respectively).
I've seen people suggest moving the download destination of the images to the same directory as where the HTML files are but this isn't an option for me as I don't want to start mixing up files downloaded by the user with local assets.
What would be my best course of action here?
Well, I've found a workaround. Instead of locally storing the images and referencing them in the HTML files, I'm now instead converting the images to Base64 and then adding them to the HTML. It's not ideal but it gets the job done. I'm going to leave this question open in case someone ever manages to find an actual solution.
To display cached HTML referencing cached resources in a WKWebView:
For each of the resources within your HTML content string, cache it into the directory as provided by NSTemporaryDirectory(). So an image tag like:
...<img src='https://www.myimage.com/example_image.png'/>...
should be cached and replaced into something like this:
...<img src='/private/var/mobile/Containers/Data/Application/527CF4FC-9319-4DFF-AB55-9E276890F5DC/tmp/example_image.png'/>...
Now cache the HTML content string with the replaced resource URLs. It must also be cached in the directory provided by NSTemporaryDirectory(). One difference here is that it must be cached (and later referenced) using the file:// protocol as a restriction of caching the string using NSData (see sample code).
For example file:///private/var/mobile/Containers/Data/Application/527CF4FC-9319-4DFF-AB55-9E276890F5DC/tmp/my_html_content_string.html
A few things to point out:
You cannot load the HTML as a raw string (loadHTMLString:baseURL:).
You cannot reference the cached resource within your HTML string using the file:// protocol. That may work in a UIWebView, but will not work in the WKWebView.
Objective-C
// To cache the HTML string:
NSString *HTML = <HTML CONTENT WITH CACHED RESOURCES>;
NSData *data = [HTML dataUsingEncoding: NSUTF8StringEncoding];
[data writeToURL: cachedHTMLURL atomically: YES];
// To load the store HTML file:
[myWKWebView loadRequest: [NSURLRequest requestWithURL: cachedHTMLURL]]; // (file://.../tmp/my_html_content_string.html)
Swift
// To cache the HTML string:
let HTML = <HTML CONTENT WITH CACHED RESOURCES>
let data = HTML.data(using: String.Encoding.utf8)
do {
try data.write(to: cachedHTMLURL, options: .atomic)
} catch {
print(error)
}
// To load the store HTML file:
myWKWebView.load(URLRequest(url: cachedHTMLURL)) // (file://.../tmp/my_html_content_string.html)
I had the same problem with WKWebView as it can not load both html strings and images at the same time for security purposes. I switched to UIWebView, which is deprecated, but I was able to load both html strings and referenced images at the same time.
I developed a definitive solution for the company I work for. But it relies on the html / javascript side. Anywhere inside your html code where you will reference to a local image <img src="..."/> you should set this "src" dynamically, and it will work seamlessly.
function getLocalURL(path) {
let origin = window.location.origin
if (origin == "file://") {
return origin + window.location.pathname.replace("/index.html","") + path
}
return path
}
You should, clearly, rename index.html to whatever is your main .htm(l) filename :)
Usage:
getLocalURL("/local_images/location_icon.png")
Will return a WKWebView working path for the referenced local image path:
"file:///Users/arthurdapaz/Library/Developer/CoreSimulator/Devices/5073AF19-26A0-460E-BC82-E89100B8E1AB/data/Containers/Data/Application/2B099343-0BF5-4849-B1C2-2512377A9772/Documents/distDriver/local_images/location_icon.png"

How to get an image from server? or How to get NSData from NSUrl?

I am using xamarin.ios I have already uploaded a selected image to server. And I want to get it again. I am getting NSUrl as http://172.16.10.49/thunder_ex/backend/web/uploads/AppUserProfilePic/Profile_140.jpg . I want to use this NSUrl and show the image in UIImage. I tried
NSData data;
data = NSData.FromUrl(url);
profileImage.Image = UIImage.LoadFromData(data);
But I am getting data as null.
Your code should work, in fact it's very similar to the one I posted there.
If you get null it generally comes from either:
the URL, i.e. an invalid URL (that iOS does not like) will return `null; or
the downloaded data is not in a format that iOS supports.
The URL you provided does not seems to connect or load (from Chrome) for me. That could also be the reason (as the API does not have any other way to return an error condition).

Image within PFFile will be uploaded to parse instead of being saved into the local datastorage

I am using swift with the Parse Framework (1.6.2). I am storing data into the local datastorage. The data consists of a few strings and an image. This image will be stored within a PfFile and then will be added to the PfObject.
Now I wondered why the image doesn't get saved until I noticed I needed to call the save() method of the PFFile. Problem is that the Image then seems to be uploaded to Parse which I don't want. I want it to be stored on the local device..
Is there a solution for this?
Thanks
Edit:
The code works like this:
var spot = PFObject(className: "Spot")
let imageData = UIImageJPEGRepresentation(spotModel.getPhoto(), 0.05)
let imageFile = PFFile(name:"image.jpg", data:imageData)
spotObj.setObject(spotModel.getCategory(), forKey: "category")
spotObj.setObject(imageFile, forKey: "photo")
//if I simply would do this, the PFObject will be saved into the local datastorage, but the image won't be saved
spotObj.pin()
//if I do this, the image will be saved also BUT it won't be saved into the local data storage but will be uploaded to parse
imageFile.save()
spotObj.pin()
Okay, take a look here Save eventually on PFObject with PFFile (Parse Local Datastore)?.
One this is for sure, if you call save on PFFile it will get save to online datastore. So you should use PFFile.save(). I think best option for you is to save the file in some folder. locally and save that path in PFObject. Parse documentation just say this
"Pinning a PFObject is recursive, just like saving, so any objects that are pointed to by the one you are pinning will also be pinned. When an object is pinned, every time you update it by fetching or saving new data, the copy in the local datastore will be updated automatically. You don't need to worry about it at all."
It recursively calls .pin() on the other objects in your main PFObject. But if you take a look at PFFile API doc it doest have a .pin() which means it doesn't support saving PFFile to local datastore. So I would say you should save them in some directory and save path in your PFObject.
Update
save:
Saves the file synchronously and sets an error if it occurs.
- (BOOL)save:(NSError **)error
Parameters
error
Pointer to an NSError that will be set if necessary.
Return Value
Returns whether the save succeeded.

Get copied data from UIPasteboard

i have copied image from UIwebView using clipboard and i want to mail it.For this,I use general pasteboard to get data,but there is a problem in retrieving data.When i check the pasteboard current data,it says the it has Apple Web Archive pasteboard type data,how to read this.here is my code of retriving text.
UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
NSArray* array = [pasteboard pasteboardTypes];
for (NSString* type in array) {
NSLog(#"%#",type);
}
NSString* item = #"Apple Web Archive pasteboard type";
NSData* val = [pasteboard dataForPasteboardType:item];
I tried to create a UIImage using this data but that didn't work.
I don't understand what you mean by mail it? You can paste the webpage image copy right into the mail app and it will appear as an image.
You can rebuild the data from the Apple Web Archive pasteboard type if you need to manual. It is essentially a XML document with html and the actual image data all within. The html and accompanying images are base64 encoded. If you want to look at an archive example save this, or perhaps a simple webpage in safari as an archive. Open the archive file in something like Text wrangler. Text edit will probably try to render it.
I've written a post on how to make an Apple Web Archive pasteboard type that might help you understand the process.
http://mcmurrym.wordpress.com/2010/08/13/pasting-simplehtml-into-the-mail-app-ios/
I take it you are trying to mail it from within your app and not using the mail app?
If this is the case you will probably have to get the xml from the pasteboard, find the tag that holds the encoded image data, decode it and create an image from the decoded data.

Resources