How do I prevent downloading the same JSON file at runtime? - ios

I have two different view controllers that both download the same JSON file from the Internet at runtime in viewDidLoad.
I want to prevent them from downloading the same file twice but instead, use the first JSON file downloaded by the initial view controller to pass it to the second view controller.
This is so that I can shorten my app loading time.

To achieve this goal, you need to make business logic such as downloading JSON independent from MVC's C(Controller).
Usually add a class file named XXXModel. Use this Model as a singleton. In this singleton you will need to implement functions of downloading and saving JSON data. You'd better save JSON data to local with a key named after its URL.
And in your Controllers, always call the singleton to download JSON.

You can create a singleton dictionary with url as key and bool as the value then save true for downloaded url. that way, you can keep track of which url's contents u have
var isDownloaded = [NSURL : Bool] // singleton in appdelegate
if let url = NSURL(string: urlString) {
if isDownloaded[url] != nil && !isDownloaded[url]! {
if let data = try? NSData(contentsOfURL: url, options: []) {
let json = JSON(data: data)
isDownloaded.updateValue(true, forKey: url) //mark it back to false if you delete the data for some reason.
}
} else {
//get it from memory
}
}

Related

How to use my view controllers and other class in the Share Extension ? iOS | Swift 4

I am creating a chatting application. User can share the images from other application to my application. I have added Share Extension to show my app in the native share app list. I'm also getting the selected data in didSelectPost Method. From here I want to show the list of the users to whom the image can be forwarded. For this, I'm using an already created view controller in the main app target.
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
if let content = self.extensionContext!.inputItems[0] as? NSExtensionItem {
let contentType = kUTTypeImage as String
// Verify the provider is valid
if let contents = content.attachments as? [NSItemProvider] {
for attachment in contents {
if attachment.hasItemConformingToTypeIdentifier(contentType) {
attachment.loadItem(forTypeIdentifier: contentType, options: nil) { (data, error) in
let url = data as! URL
let imageData = try! Data(contentsOf: url)
// Here I'm navigating to my viewcontroller, let's say: ForwardVC
}
}
}
}
}
I don't want to recreate the same screen in Share Extension. Apart from this view controllers, I have many more classes and wrappers that I want to use within the share extension. Like, SocketManager, Webservices, etc. Please suggest me your approach to achieve the same.
P.S.: I've tried setting multiple targets to required viewControllers and using same pods for Share Extention. In this approach, I'm facing a lot of issues as many of the methods and pods are not extention compliant. Also, is it the right way to do this.

What is the best way to store large file data into Coredata in iOS

I am trying to store 30k users details in to core data. To achieve this I search and came up with a solution to have all the data in a file in CSV format.
I am able to download and read CSV file to using following code:
func readCsvFile () {
if let path = Bundle.main.path(forResource: "users", ofType: "csv") {
if FileManager.default.fileExists(atPath: path){
if let fileHandler = FileHandle(forReadingAtPath: path){
if let data = fileHandler.readDataToEndOfFile() as? Data{
if let dataStr = String(data: data, encoding: .utf8){
print(dataStr)
}
}
}
}
}
}
but now the problem is that when i read entire data from file it causes memory issue. I need to read some portion and process core data storing. Again get back and continue data read from where I left and go on.
My file has data as following:
Firstname,Lastname,Email,Phone,Title
Tanja,van Vlissingen-ten Brink,1,,Vulr(st)er
Berdien,Huismn Noornnen,2,,Filanager
Ailma,Ankit,3,,Vulr(st)er
Rzita,Salmani Samani,4,,Vulr(st)er
DeEora,Levaart,5,,Eerste Vulr(st)er
Kirsten,Veroor,6,,Vulr(st)er
Tristan,Haenbol,7,,Vulr(st)er
Manon,Bland,8,,Aankomend Vulrer
Naomi,Ruman,9,,Aankomend Vulrer
So I found that using NSFileHandler I can move or seek my file cursor to specific location in file, but it needs offset:
fileHandler = FileHandle(forReadingAtPath: path)
fileHandler.seek(toFileOffset: 10)
but I don't know how can I identify that I have read some specific number of lines, and now get back to next set of lines.
Also NSStream is the way to read File but I haven't explored it.

Load image from Firestore and save it to cache in Swift

I'm working on an app that has to load some images and data from server on every launch (to make sure it's using up-to-date info). I'm using Firestore as a DB and currently storing images in it as an URL to Firebase storage.
Is it somehow possible to store an actual image in Firestore? And how can I cache loaded image? Either from
UIImage(contentsOf: URL)
or from Firestore?
Try this Asynchronous image downloader with cache support as a UIImageView category - http://cocoadocs.org/docsets/SDWebImage
It is called sdwebimage really easy to use
I don't know if that's the most efficient way of solving my problem but I did it the following way:
In my Firestore DB I stored references to images in Cloud Storage. Then when app starts for the first time, it loads those files from Firestore DB using default methods AND saves those images in app's container (Documents folder) using Swift's FileManager().
Next time the app starts, it goes through references array and skips the files which are already in app's container.
You could use the bytes type in Firestore (see a list of types) to save whatever binary data you want (use NSData on iOS), but this is almost certainly not what you actually want to do. The limit for the size of an entire document is 1 MB, and images can easily exceed that. Also, you'll be paying the cost of downloading that image to the client any time that document is read, which could be wasteful.
You'll be far better off storing the actual file data in Cloud Storage (using the Firebase SDK on the client), then storing a reference or URL to that in the document, and fetch it from there only when needed.
You could use https://github.com/pinterest/PINRemoteImage, this framework use https://github.com/pinterest/PINCache
import PINRemoteImage
extension UIImageView {
public func setImageFrom(urlString: String!, animated: Bool = false) {
guard let urlString = urlString else {
return
}
guard let url = URL(string: urlString) else {
return
}
layer.removeAllAnimations()
pin_cancelImageDownload()
image = nil
if !animated {
pin_setImage(from: url)
} else {
pin_setImage(from: url, completion: { [weak self] result in
guard let _self = self else { return }
_self.alpha = 0
UIView.transition(with: _self, duration: 0.5, options: [], animations: { () -> Void in
_self.image = result.image
_self.alpha = 1
}, completion: nil)
})
}
}
}
....
UIImageView(). setImageFrom(urlString: "https://ssssss")

Resume downloads when download URL changes

I have an API that generates signed download links that expire after a short amount of time. I'd like to add the ability to resume downloads, but the URLSession APIs don't provide the native ability to resume downloads if the URL for the asset changes.
My attempt at solving this was to track the bytes downloaded at the time of pausing, store the data blob that was downloaded, fetch a new signed download url, resume downloading using Range headers, and then concatenate all the data blobs together when the download is completed.
Here's the code used to start the download:
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
let task = session.downloadTask(with: signedURL)
self.sessionDownloadRequest = task
The problem that I am facing is that the resume data var doesn't appear to actually contain the data that was downloaded.
self.sessionDownloadRequest.cancel(byProducingResumeData: { (data) in
print(data.count) //This surprisingly always returns the same count
}
It appears that the size of that data blob is always the same regardless of how long I let the download continue for before pausing. Where/How can I access the chunk of data that was downloaded?
Thanks!
The resume data that is returned by:
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler;
is actually a plist that includes:
NSURLSessionDownloadURL
NSURLSessionResumeBytesReceived
NSURLSessionResumeCurrentRequest
NSURLSessionResumeEntityTag
NSURLSessionResumeInfoTempFileName
NSURLSessionResumeInfoVersion
NSURLSessionResumeOriginalRequest
NSURLSessionResumeServerDownloadDate
You can access the plist with the following code:
if let resumeDictionary = try? PropertyListSerialization.propertyList(from: self, options: PropertyListSerialization.MutabilityOptions.mutableContainersAndLeaves, format: nil), let plist = resumeDictionary as? [String: Any] {
print(plist)
}
You don't actually need to store and concatenate the data blobs as you initially suggested. You can replace the current request stored in the plist (NSURLSessionResumeCurrentRequest) with a new one with your updated signed URL. After this, create a new resumeData instance to use instead of the original.
guard let bytesReceived = plist["NSURLSessionResumeBytesReceived"] as? Int
else {
return nil
}
let headers = ["Range":"bytes=\(bytesReceived)"]
let newReq = try! URLRequest(url: signedURL, method: .get, headers: headers)
let archivedData = NSKeyedArchiver.archivedData(withRootObject: newReq)
if let updatedResumeData = try? PropertyListSerialization.data(fromPropertyList: plist, format: PropertyListSerialization.PropertyListFormat.binary, options: 0) {
return updatedResumeData
}
From there you can manipulate the plist and actually create a new one to pass it thru to the instance method:
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
NOTE: If you are working with iOS 10 and macOS10.12.*, there is a bug that prevents the resume ability to work as the plist is corrupted. Check this article out for a fix. You may need to fix the plist before accessing certain properties on it.
Resume NSUrlSession on iOS10

Accessing URL and Array from within a Block of JSON Data

Let's say I have JSON data structured in the following way:
{ "fruits" : {
"apple": {
"name": "Gala"
"color": "red",
"picture": "//juliandance.org/wp-content/uploads/2016/01/RedApple.jpg",
"noOfFruit": [1, 2]
}
}
How would I access the picture and noOfFruit array using the iOS version of Firebase? I want to make a table view with a cell that lists the apple's name, the color, a picture of the apple, and then lists the number of fruit. I get how to obtain the "color" and "name" values but how would I access the array and turn it into a string and the image so that it shows the image in the table view? Any help is appreciated!
For the array, it's really simple. Wherever you have your function that listenes for the firebase changes, I'll imagine that you have the info under the apple key stored in a variable like let apple
Then, you could cast the value of noOfFruit to an array, like the following:
let apple = // what you had before
guard let noOfFruit = apple["noOfFruit"] as? [Int] else {
return
}
//Here you have the array of ints called noOfFruit
For the image, theres several options out there. The first (and bad one) is to synchrounsly fetch the data of the url and set it to an image view as the following:
let url = URL(string: picture)
let data = try? Data(contentsOf: url!) //this may break due to force unwrapping, make sure it exists
imageView.image = UIImage(data: data!)
The thing with this approach is that it's NOT OK. It will block the main thread while its making the request and dowloading the image, leaving the app unresponsive.
The better approach would be to go fetch it asynchronously.There are several libraries that really help, such as AlamofireImage, but it can be done with barebones Foundation really easy. To do that, you should use URLSession class as the following:
guard let url = URL(string: picture) else {
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
return
}
//Remember to do UI updates in the main thread
DispatchQueue.main.async {
self.myImageView.image = UIImage(data: data!)
}
}.resume()

Resources