Swift iOS check if a file has been downloaded from json - ios

I have an app that displays and plays a list of podcasts that is fetched from a json file, I would like to add a download feature but to do this I would like to only show a download icon if the podcast has not been downloaded already, is there a way that I can save something like the id element as well as the podcast title so I could then check to see if its been downloaded and saved on the phone already? Or is there an easier way? Obviously I would like to do this before the list is shown.

You can use UserDefaults for that.
Here's an example on how to read an array from UserDefaults
// Retrieves an array of strings from disk
func getDowloadedPodcasts() -> [String] {
UserDefaults.standard.array(forKey: "downloadedPodcasts") as? [String] ?? []
}
And here's an example on how to append a new value to an array on UserDefaults
func addDownloadedPodcast(podcastId: String) {
let downloadedPodcasts = getDowloadedPodcasts()
downloadedPodcasts.append(podcastId)
UserDefaults.standard.setValue(podcastId, forKey: "downloadedPodcasts")
}
Note that this functions alone won't solve your problem nor are the best solution of your problem, they are here jsut to show how easy it can be to work with UserDefaults and to read/write from non-volatile memory

Related

Data written to defaults within an unwanted communications extension isn't persisted

I'm using 4 extensions within my app and use a group in combination with UserDefaults.init(suiteName:) to share settings between the extensions and the app.
However I've just tried adding an unwanted communications extension and found that data writing to the defaults, using the exact same way as its written in the other extensions, isn't saved.
At first I noticed data written by the UCE wasn't present when the app tried to read it, so performed an experiment and found that while the extension is running it can write data to user defaults and read it back, but the next time the extension runs, all that data has gone.
I've tried using the old UserDefaults.synchronize() method after writing the data but that makes no difference.
Why is the UC extension different from every other extension? Is it possible to write and persist data from within it?
let groupName = "group.com.mycompany.appName"
let sharedDefaults = UserDefaults.init(suiteName: groupName)
var theValue = sharedDefaults!.value(forKey: "some key")
NSLog("\(theValue)") // prints nothing, despite the extension having previously run
sharedDefaults!.set("some value", forKey: "some key"))
sharedDefaults!.synchronize()
theValue = sharedDefaults!.value(forKey: "some key")
NSLog("\(theValue)") // prints "some value"

How To Use Shared Container/App Groups To Access Files From Other Targets in Swift

I am new to Swift.
I am trying to make a two-app project, where one app contains some data files and the other accesses those files.
The solution, I think, has been to use the App Groups entitlements to allow for this, and access the files through those means. I have been able to follow along with the example here: Communicating and persisting data between apps with App Groups. In particular, the 2nd answer, which is Swift-ish (maybe an older version I guess?). It does seem to work with the right entitlements. So, now the question is how can I access the file from one app, with it being apart of the another? I'm not familiar with the API's and correct functions that I can use (new to Swift, as I said).
The apps are basic. Setup as single view applications, with everything defaulted except the ViewController's, entitlements, and one has the test data. The Xcode project structure is:
testingData/
testingData/
testingData.entitlements
TestData/
testdata.txt
AppDelegate.swift
ViewController.swift
Main.storyboard
Assets.xcassets
LaunchScreen.storyboard
Info.plist
sharedContainerTest/
sharedContainerTest.entitlements
AppDelegate.swift
ViewController.swift
Main.storyboard
Assets.xcassets
LaunchScreen.storyboard
Info.plist
Products/
testingData.app
sharedContainerTest.app
The entitlements are both the same. They each have App Groups enabled, with the same string: group.com.example.name. On the testingData target, the ViewController has the following chunk in the viewDidLoad function from that example (modified for Swift 4.x):
var userDefaults = UserDefaults(suiteName: "group.com.example.name")!
userDefaults.set("user12345", forKey: "userId")
userDefaults.synchronize()
On the sharedContainerTest target, its ViewContoller has
var userDefaults = UserDefaults(suiteName: "group.com.example.name")
if let testUserId = userDefaults?.object(forKey: "userId") as? String {
print("User Id: \(testUserId)")
}
in its viewDidLoad function. As I said, this seems to work, but now what do I need to add to this to access the testdata.txt file from the testingData app? Does it need to be stored as a variable, perhaps? Or is there a Bundle object that can do this?
If this has been answered elsewhere, please kindly point me to it and I'll take this down.
After some trial and error, the answer is as follows:
Instead of passing in the string "user12345", you need to pass in the URL to the file you want to read for the userDefaults.set method as follows:
var userDefaults = UserDefaults(suiteName: "group.com.example.name")!
userDefaults.set(Bundle.main.url(forResource: "testdata", withExtension: ".txt"), forKey: "userId")
userDefaults.synchronize()
Then in the receiver app, you call that object and set the URL:
let userDefaults = UserDefaults(suiteName: "group.com.example.name")
let test = userDefaults?.object(forKey: "userId") as? String
let testURL = URL(fileURLWithPath: test!)
From here you can read in the contents as normal.

Swift: Load JSON from a url and store in cache or file system

I am currently in the process of writing an iOS APP that downloads information from an API in JSON format then displays it in the app.
One of the key features to this app is it being able to work offline as well as online, for this reason there should be a cached version as well as an online version.
After reading through the internet to my shock I have not found any examples what so ever of this approach.
The only thing I have found that's even come close to this is HanekeSwift but the documentation seems incomplete and there is no way to clear the cache and i'm not even sure if this is a memory based cache or a filesystem based cache.
Since there is lots of ways out there to do this, core data, file system frameworks etc.. I'm not sure which one would be the best to go for, theoretically to break down my thought process all I need to do is:
Check if the JSON file exists on the system
If not download it from the network and store it for later use (Preferably as a string format)
If file exists load it into a swiftyJSON object
I feel like core data would be overkill, I feel like the file system way is dated as most of the filesystem cocoa pods/libraries don't seem to be compatible with the current swift version (2.3)
Can anyone share some light on what the generic standard way of doing this is or what option would be the most suitable for my purpose of use and why.
Kindest regards
SwiftifyJSON makes objects that support archiving.
Try this
class HSCache: NSObject {
static var defaults: NSUserDefaults = NSUserDefaults()
class func cacheThis(key: String, object : AnyObject) {
defaults.setObject(NSKeyedArchiver.archivedDataWithRootObject(object), forKey: key)
defaults.synchronize()
}
class func getFromCache(key: String, type : AnyClass) -> AnyClass? {
if defaults.objectForKey(key) != nil {
return NSKeyedUnarchiver.unarchiveObjectWithData(defaults.objectForKey(key) as! NSData) as? AnyClass
}
return nil
}
class func deleteFromCache(key: String) {
defaults.removeObjectForKey(key)
defaults.synchronize()
}
}

Pass data from TodayExtension to app

Is it possible to pass data from a today extension to an app? (Even when its not currently running). I wish to pass an array of objects to the main app and instantiate a viewController based on the objects passed from the today extension. I know how to open the app from the extension just not to too sure how to send data to the app.
var arrayToBePassed: [MyDataSource]
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var url = ""
if currentSort == .recent{
url = "ext://recent"
}else{
url = "ext://popular"
}
//HOW DO I PASS arrayToBePassed to my app?
let myAppUrl = NSURL(string: url)!
extensionContext?.openURL(myAppUrl, completionHandler: { (success) in
if (!success) {
}else{
}
})
}
There's a few routes you could go down:
• In your app, implement a URL scheme that can quite literally take that array of objects, formatted as some sort of URL parameter. e.g. myAwesomeApp://objectArray=[these, are, strings]. You'll have to parse that URL yourself which should be a bit of fun.
• Look into NSUserDefaults. You can initialise a 'shared' user defaults object that both your app and your today extension can use, see here for more info. You could then store your array in here, and access it from the app when opened.
It really depends on what you're trying to do, but from the impression I get, I feel like the first option may be the best answer. I haven't provided any code, just an outline of how I'd go about it - but hopefully that should be enough to get you off to a good start.
Sharing of data is achieved through a new concept called “App Groups”. App Groups are allowed to share some data, including files, but it is worth noting that file access needs to be marshalled to avoid concurrent writes and so forth. This can be achieved through NSFileCoordination, but CoreData and NSUserDefaults handle this out of the box.
You can find the detial Steps here

How to pass Core Data objectID and use it with Continuity

Just trying to update some Core Data apps with Continuity and have run into a bit of an issue with using the selected objects ID in the userInfo dictionary to display the correct data on the continuing device.
My first thought was to use the ObjectID, however on the receiving device this would never find a corresponding object in the Core Data store.
As it turns out the URL representation of the objectID contains the UUID of the store itself, and because the two stores UUID's are different this is obviously going to fail.
So I guess I could replace the Core Data store's UUID in the URL with the continuing devices UUID and use this, and no doubt it would work.
The Url seems to be of the following format
Does anyone know what the correct way would be to pass a reference to an object between two devices with core data stores that are synchronised via iCloud?
I'll answer this one myself and see if there are any better answers...
I pass the url of the objectID (from objectID.URIRepresentation) using Continuity API and on the receiving device create a new URL using the following:
url is the url passed in the NSUserActivity.userInfo dictionary
let storeUUID = self.identifierForStore()
// Switch the host component to be the local storeUUID
let newURL = NSURL(scheme: url.scheme!, host: storeUUID, path: url.path!)
func identifierForStore()->NSString? {
if let store = self.persistentStoreCoordinator?.persistentStores[0] as? NSPersistentStore {
return store.identifier
} else {
return nil
}
}
This seems to work just fine - hope it helps someone

Resources