Most Efficient way to Store Data in Swift - ios

I am currently working on a Swift project and I would like to save permanent user data in the following manner:
USERNAME
- List of Strings (String array)
This so that when I search for a Username, I am either given a nil return (if the username does not exist), or a list of Strings that I can iterate through. I know of a few methods of saving user data, however, as I am new to Swift, I do not know all methods of permanently save user data. Nor do I know which ones best suit my needs.

Something like this…
let people: [String: [String]] = ["Jim": ["bread", "fruit", "meat"],
"Alan": ["pears", "peas", "turnip"],
"Sue": ["cabbage", "rice", "bblueberries"]]
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).last!
let fileUrl = documentsUrl.appendingPathComponent("myfile")
(people as NSDictionary).write(to: fileUrl, atomically: true)
and then…
let people = NSDictionary(contentsOf: fileUrl) as! [String: [String]]
For system urls… https://developer.apple.com/reference/foundation/filemanager/1407726-urls
NSDictionary…
https://developer.apple.com/reference/foundation/nsdictionary?language=swift

Related

Parsing local JSON file with search String

really need your help.
I'm trying to parse local JSON file (list of cities) using search String, I want to get back a selective set of data, which is relevant to my search String, i.e. I type in the name of the city, and get back the list of cities that match the String from the JSON file.
Usually when the data is on the web it is very easy to do such operation by simply adding a query parameter in URL, however I'm not sure how to do it when parsing local JSON file.
Here's my code to parse the whole file:
guard let filePath = Bundle.main.path(forResource: "cityList", ofType: "json") else {
fatalError("Failed to create path to cityList file")
}
let fileURL = URL(fileURLWithPath: filePath)
do {
let jsonData = try Data(contentsOf: fileURL)
let cityList = try JSONDecoder().decode([City].self, from: jsonData)
print(cityList[0].name)
} catch {
print(error)
}
I'm hoping I don't have to parse the whole file, as it is quite big, and then apply the search to the resulting array of data I get back from parsing.
Just filter the array
let query = "New"
let filteredCities = cityList.filter{$0.name.range(of: query, options: .caseInsensitive) != nil) }
This syntax filters all cities which contain new (case insensitive) in their name.
If you want to filter the cities which start with new add the .anchored option
let filteredCities = cityList.filter{$0.name.range(of: query, options: [.caseInsensitive, .anchored]) != nil) }
And there is an API url(forResource:withExtension:) in Bundle which returns an URL

How to read a multidimensional String Array from a plist in Swift?

I'm using the following code to save a 2D String array to a plist:
func saveFavourites(favouriteStops: [[String]]) {
let directories = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
if let library = directories.first {
if let libraryUrl = URL(string: library) {
let favouritesUrl = libraryUrl.appendingPathComponent("favourites.plist")
// Write favourites to disk
let favsArray = favouriteStops as NSArray
print(favsArray)
favsArray.write(toFile: favouritesUrl.path, atomically: true)
}
}
}
The above snippet properly creates the .plist file (confirmed by looking at the simulator's filesystem in ~/Library/Developer/CoreServices). However, when I try reading it back to a NSArray with the following snippet, it results in nil:
let directories = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
if let library = directories.first {
if let libraryUrl = URL(string: library) {
let favouritesUrl = libraryUrl.appendingPathComponent("favourites.plist")
// favsToLoad is nil
let favsToLoad = NSArray(contentsOf: favouritesUrl)
// Do stuff with favsToLoad, if it would load properly
}
}
You're doing two very basic things wrong.
First, never make a URL from a file path by saying URL(string); this is a file on disk, so you must use URL.fileURL.
Second, don't start with a file path at all! Obtain the directory as a URL right from the start.
(Also, though I do not know whether this is the source of the issue, do not read and write directly in the Library directory. Use the Documents directory, the Application Support directory, or similar.)
So, for example, I would write:
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let favouritesurl = docsurl.appendingPathComponent("favourites.plist")
I see your problem. You misspelled "favorites". :)
But seriously...
Plists can only contain a very small set of "property list objects": (dictionaries, arrays, strings, numbers (integer and float), dates, binary data, and Boolean values).
If your array's "object graph" (the objects the array contains and any container objects inside the array recursively contain) contain anything other than the above types, the save will fail.
I don't honestly know what gets saved when it fails. Have you tried opening the plist file in a text editor and looking at it?
My guess is that something other than a string has snuck into your array, it's not one of the above types, and THAT'S why it's failing.

Saving NSData from CoreData Managed Object in a CloudKit Asset

I have a small image stored as NSData in a CoreData Managed Object that I need to save in a CloudKit record as CKAsset.
I was hoping to simply cast the NSData from the managed object like this:
myCKRecord["document"] = myCDRecord.document as! CKAsset
Unfortunately, this cast does not work, so I have made a work-around by writing a file with the NSData, and using the file to initialise the CKAsset.
class myManagedObject: NSManagedObject {
#NSManaged var image: NSData?
#NSManaged var name: String
func createCloudKitAsset(myCDRecord : myManagedObject) -> CKRecord
let myCKRecordID = CKRecordID(recordName: myCDRecord.name)
let myCKRecord = CKRecord(recordType: "myCKType", recordID: myCKRecordID)
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let localURL = NSURL.fileURLWithPath(dirPaths.first + "/" + recordName)
let fileManager = NSFileManager.defaultManager()
fileManager.createFileAtPath(localURL.path!, contents: myCDRecord.image, attributes: nil)
myCKRecord["image"] = CKAsset(fileURL: localURL)
...
}
This works, but is untidy, and I still have the temporary file to be cleaned up after the CK record is saved.
Hopefully, someone can show me a better way to do this.
Thanks
It is bad practice to store large documents in Core Data. The blob data type should only be used for small images such as thumbnails.
Instead, your Core Data entity should just point to the correct file, either via your own directory scheme, and/or via a URL attribute. You would then just have to create the CKAsset with the fileURL parameter from the Core Data entity. To access the data, you would use the fileURL of the CKAsset, which will be different.

how to handle PDF after opening in my app swift

My question, after I open PDF file with my app('register the document types that your application can open with iOS') I getting the files as NSCFString format)
Example:
let filemgr: NSFileManager = NSFileManager.defaultManager();
var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
let documentsDirectory: String = paths[0] as! String
let inboxPath: String = documentsDirectory.stringByAppendingPathComponent("Inbox");
do
{
let dirFiles: [AnyObject] = try filemgr.contentsOfDirectoryAtPath(inboxPath);
}
catch{}
My question is from this step I want to gather all this documents to one document and also open it with UIWebView. from what i read i need to find a way to convert it to nsdata or even draw it as PDF, but i really dont know how to handle it.
Thank you for help

iOS 9 JSON Parsing loop

I'm creating an app that should retrieve some JSON from a database.
This is how my JSON looks:
[{"id":"1","longitude":"10","latitude":"10","visibility":"5","timestampAdded":"2015-10-01 15:01:39"},{"id":"2","longitude":"15","latitude":"15","visibility":"5","timestampAdded":"2015-10-01 15:06:25"}]
And this is the code i use:
if let jsonResult = JSON as? Array<Dictionary<String,String>> {
let longitudeValue = jsonResult[0]["longitude"]
let latitudeValue = jsonResult[0]["latitude"]
let visibilityValue = jsonResult[0]["visibility"]
print(longitudeValue!)
print(latitudeValue!)
print(visibilityValue!)
}
As you can see it only gets the first chunk from the JSON and if there are no JSON at all it will crash, but if i want it to count the amount and make an array out of it like this:
var longitudeArray = [10, 15]
var latitudeArray = [10, 15]
And so on...
I also need this to be apple watch compatible so i can't use SwiftyJSON.
What do i do? I really hope you can help me!
Thanks.
SOLVED!
Problems was solved by "Eric D."
This is the code:
do {
if let url = NSURL(string: "YOU URL HERE"),
let data = NSData(contentsOfURL: url),
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [[String:AnyObject]] {
print(jsonResult)
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
print(longitudeArray)
print(latitudeArray)
}
} catch let error as NSError {
print(error.description)
}
Thank you soo much Eric!! :-)
You could use flatMap to get an array of your elements:
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
etc.
flatMap is like map but unwraps optionals, which is adequate because we need to safely cast the type of the object we get from each dictionary in the json array.
$0 represents the object in the current iteration of flatMap of the array it's applied to.
If you're currently using SwiftyJSON, then that would be:
let longitudeArray = jsonResult.flatMap { $1["longitude"].string }
let latitudeArray = jsonResult.flatMap { $1["latitude"].string }
because .string is SwiftyJSON's optional String value getter.
But as you said, you don't want to use it (anymore), so you need to use NSJSONSerialization to decode your JSON data, there's plenty of examples on the Web and on SO. Then you will be able to use my original answer.
You're already getting an array with all of the elements (not just the first one. you're simply only accessing the first one). jsonResult is an array of dictionaries. Each dictionary (in this case, based on the json you provided) contains these elements: id, longitude, latitude, visibility and timestampAdded. In order to access each of them, you can simply loop over jsonResult and access the i'th element (and not always the 0 element). This will also prevent the crash you're experiencing with the json is blank or invalid (since you'll only be going over the valid elements in jsonResult.
This will give you the flexibility to create the custom arrays you wish to create (in order to create an array of all of the longitudes, for example, you will simply add that element to the new array while looping over jsonResult). However, if you'd like to save yourself the trouble of manually building these arrays and assuming you have control over the json structure, I would recommend changing the received json to the relevant structure (a dictionary or arrays instead of an array of dictionaries), so it would better fit your needs and provide you the results in the relevant format right "out of the box".

Resources