How to organize data in Swift 3 - ios

I'm writing an app on iOS using Swift 3 and I'm quite new to programming.
The app mimics a farm where the user can manage plants.
I'm looking for an elegant-solid way to store a quite big number of objects that will never change in the course of the application (descriptions texts, number representing the growing time of a plant, etc.).
At the moment I'm storing the informations of each plant in a dictionary where for each entry the key is a code of the plant("0","1", and so on), and the value is another dictionary where the key (I used an enum "librarySelection") represents the type of data ("description","growingTime", etc.) and the value is of type AnyObject.
For example:
struct Plants {
static let library: [String: [librarySelection: AnyObject]] = [
"0":[
.description: "description1" as AnyObject,
.image: "image1.pdf" as AnyObject,
.growingTime: 10 as AnyObject,
],
"1":[
.description: "description2" as AnyObject,
.image: "image2.pdf" as AnyObject,
.growingTime: 20 as AnyObject,
]
]
}
This doesn't feel very handy. I looked around if there was someway to use CoreData and maybe create a pre-populated database to include with my app but that doesn't feel like the correct way either.
I'm sorry for my poor english.

Related

Swift iOS check if a file has been downloaded from json

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

Determining Swift Types That Can Be Stored in UserDefaults

I am in the beginning stages of developing an open-source utility for storing state in the Bundle UserDefaults.
I'm encountering an issue when I add non-Codable data types to my Dictionary of [String: Any].
I need to be able to vet the data before trying to submit it, because the UserDefaults.set(_:) method won't throw any errors. It just crashes.
So I want to make sure that the Dictionary that I'm submitting is kosher.
I can't just check if it's Codable, because it can sometimes say that it isn't, when the struct is actually good. (It's a Dictionary<String, Any>, and I can cram all kinds of things in there).
I need to validate that the Dictionary can produce a plist. If this were ObjC, I might use one of the NSPropertyListSerialization methods to test the Dictionary, but it appears as if this set of methods is not available to Swift.
According to the UserDefaults docs, there are a specific set of types and classes that are "plist-studly."
I think testing each type in the list is unacceptable. I need to see if I can find a way to test that won't be screwed the first time Apple updates an OS.
Is there a good way to test a Dictionary<String, Any> to see if it will make UserDefaults.set(_:) puke?
The Property List type set of UserDefaults is very limited. The supported types are
NSString → Swift String
NSNumber → Swift Int, Double or Bool
NSDate → Swift Date
NSData → Swift Data
Arrays and dictionaries containing the 4 value types.
Any is not supported unless it represents one of the 4 value or 2 collection types.
Property List compliant collection types can be written to UserDefaults with PropertyListSerialization (even in Swift).
There are two protocols to serialize custom types to Data
Codable can serialize structs and classes.
NSCoding can serialize subclasses of NSObject.
All types in the structs/classes must be encodable and decodable (means conform to the protocol themselves).
The APIs of PropertyListSerialization / PropertyListEncoder/-Decoder and NSKeyed(Un)Archiver provide robust error handling to avoid crashes.
UPDATE[1]: And, just because I like to share, here's the actual completed project (MIT License, as is most of my stuff)
UPDATE: This is the solution I came up with. Even though I greenchecked vadian's excellent answer, I decided to get a bit more picky.
Thanks to matt pointing out that I was looking under the wrong sofa cushions for the keys, I found the Swift variant of NSPropertyListSerialization, and I use that to vet the top level of the tree. I suspect that I'll need to refactor it into a recursive crawler before I'm done, but this works for now.
Here's the code for the _save() method at the time of this writing. It works:
/* ################################################################## */
/**
This is a private method that saves the current contents of the _values Dictionary to persistent storage, keyed by the value of the "key" property.
- throws: An error, if the values are not all codable.
*/
private func _save() throws {
#if DEBUG
print("Saving Prefs: \(String(describing: _values))")
#endif
// What we do here, is "scrub" the values of anything that was added against what is expected.
var temporaryDict: [String: Any] = [:]
keys.forEach {
temporaryDict[$0] = _values[$0]
}
_values = temporaryDict
if PropertyListSerialization.propertyList(_values, isValidFor: .xml) {
UserDefaults.standard.set(_values, forKey: key)
} else {
#if DEBUG
print("Attempt to set non-codable values!")
#endif
// What we do here, is look through our values list, and record the keys of the elements that are not considered Codable. We return those in the error that we throw.
var valueElementList: [String] = []
_values.forEach {
if PropertyListSerialization.propertyList($0.value, isValidFor: .xml) {
#if DEBUG
print("\($0.key) is OK")
#endif
} else {
#if DEBUG
print("\($0.key) is not Codable")
#endif
valueElementList.append($0.key)
}
}
throw PrefsError.valuesNotCodable(invalidElements: valueElementList)
}
}

How to Store Levels Data

Im creating a game using SpriteKit but I’m not using sks files fore levels. I don’t to enter in details about the idea of the game before I release it but basically each level is auto generated based on a few numbers. So essentially what defines a level would be these numbers I would like to know where I could store this numbers. If I used sks files I would just have a file per level but in this case should I have them sorted in an array of levels? Should the array be in the level selection viewcontroller ? Should it be in a singleton class?
Basically what would be a good way to go about storing these values?
So the levels are auto-generated at runtime?
You could use an array of levels, or a file per level. I would just write them to one or more files in your app's documents directory. (I'd probably use one file per level, just to keep it simple and make it so you can easily add more levels without rewriting the whole game layout file each time.)
If you build your level structures out of scalar types, arrays, and dictionaries, (property list objects) then you can write the "object graph" to a property list using the NSArray or NSDictionary method write(to:).
Alternately you could make your level object conform to the Codable protocol, convert it to JSON, and save the JSON data to a file. The Codable protocol is easy to use, it's well documented by Apple, and there are tons of tutorials online.
EDIT
Note that you could also write your data to a property list using the Codable protocol. Just like the JSONEncoder and JSONDecoder classes, there are PropertyListEncoder and PropertyListDecoder classes that will convert your object graph back and forth to property list format. (Binary properties lists are more compact and faster to read and write than JSON.)
Below is a sample playground that defines a custom struct FooStruct, makes it Codable, and then uses a PropertyListEncoder to write the data to the playground's shared data directory (which you will have to set up if you want to test this code)
import UIKit
import PlaygroundSupport
struct FooStruct: Codable {
let aString: String
let anotherString: String
let anInt: Int
}
let fooArray: [FooStruct] = [FooStruct(aString: "Foo 1",
anotherString: "String 1", anInt: 4),
FooStruct(aString: "Foo 2",
anotherString: "String 2", anInt: 7)
]
let encoder = PropertyListEncoder()
encoder.outputFormat = .binary
do {
print(fooArray)
let data = try encoder.encode(fooArray)
let plistURL = playgroundSharedDataDirectory.appendingPathComponent("property list.plist")
try data.write(to: plistURL)
print("Data written to \(plistURL.path)")
} catch {
print (error)
}

Getting only few properties of a table in CoreData ios swift3

I have coreData entities defined as
In TTFinds
#nonobjc public class func fetchRequest() -> NSFetchRequest<TTFinds> {
return NSFetchRequest<TTFinds>(entityName: "TTFinds");
}
I am trying to get TTFinds list as
let request: NSFetchRequest<TTFinds> = TTFinds.fetchRequest()
do {
let result = try appDel.persistentContainer.viewContext.fetch(request)
As can be seen TTFinds gets its parent TTArea object. However i want to get only few columns. Let's say name and creationDate only.How can it be done?
I have tried
let request: NSFetchRequest<TTFinds> = TTFinds.fetchRequest()
request.propertiesToFetch = NSArray.init(objects: "name", "creationDate", "image", "latitude", "longitude",
"area.name") as? [Any];
However area.name gives errors.
Forget propertiesToFetch and just fetch the entire object.
First, you can only retrieve selective attributes if you use a result type of dictionary. It will make your code more cumbersome and lengthy, without any gain in efficiency.
Second, if you just fetch the objects, even if there are a lot of them, Core Data will do all kinds of optimizations "under the hood" and just fetch what it needs. This is triggered by simply accessing the properties you need.
Thus, assuming your purpose for fetching only some properties is to conserve memory or reduce performance bottlenecks, don't worry about it until you actually hit those bottlenecks.
NB: your relationships should have inverse relationships!

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()
}
}

Resources