Parsing local JSON file with search String - ios

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

Related

iOS Share Sheet: How to share CSV data in a way Numbers app and Excel are offered for import?

I try to use CSV data via the iOS share sheet. My problem: The share sheet does not offer the apps „Apple Numbers“ or „Microsoft Excel“ for import! If I use „Save to files“ in the share sheet first and share this saved file via Files App later, Excel and Numbers are displayed as possible targets and accept the data.
I already tried many different methods to provide my data, but I was not able to get Numbers and Excel appear as targets in the share sheet.
Any idea how this can be achieved?
Three ways in which I tried to provide the data are shown below:
Providing CSV as Data:
private var dataItem:NSItemProvider?{
guard let data = csvData else {return nil}
let item = NSItemProvider(item: data as NSSecureCoding, typeIdentifier: UTType.commaSeparatedText.identifier)
item.suggestedName = "Test" + ".csv"
return item
}
Saving CSV to temporary file and providing an URL:
private var fileURLItem:NSItemProvider?{
let name = "Test" + Date().description
let fileManager = FileManager.default
guard let cacheDirectory = try? fileManager.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true),
let data = csvData else {
return nil
}
let fileLocation = cacheDirectory.appendingPathComponent("\(name).csv")
try? data.write(to: fileLocation, options: .atomicWrite)
// return NSItemProvider(item: fileLocation as NSSecureCoding, typeIdentifier: UTType.fileURL.identifier) // either way doesn't work
return NSItemProvider(contentsOf: fileLocation)
}
A combination of both, trying to mimic the types „Files“ app offers to the share sheet:
class Exporter:NSObject, NSItemProviderWriting{
static var writableTypeIdentifiersForItemProvider: [String] = [UTType.commaSeparatedText.identifier, UTType.fileURL.identifier]
func loadData(withTypeIdentifier typeIdentifier: String,
forItemProviderCompletionHandler completionHandler: #escaping (Data?, Error?) -> Void) -> Progress?{
switch typeIdentifier{
case UTType.commaSeparatedText.identifier:
completionHandler(sampleCSV.data(using: .utf8)!, nil)
case UTType.fileURL.identifier:
completionHandler(fileURL!.dataRepresentation, nil)
default:
completionHandler(nil, ExporterError.unknownUTI)
}
return nil
}
The third way doe not work at all and result in
Could not instantiate class NSURL. Error: Error Domain=NSCocoaErrorDomain Code=4864 "A URL cannot be instantiated from a representation of type “public.url” even after treating it as a string." UserInfo={NSDebugDescription=A URL cannot be instantiated from a representation of type “public.url” even after treating it as a string., NSUnderlyingError=0x6000018c20d0 {Error Domain=NSCocoaErrorDomain Code=4864 "The URL archive of type “public.url” contains invalid data." UserInfo={NSDebugDescription=The URL archive of type “public.url” contains invalid data.}}}
printed to console. This error can't even be found via a Google search.

How do I stop Swift from injecting escaped single quotes in my [String]?

I'm attempting to print out a string array without escaped single quotes. For some reason, Swift is injecting escaped single quotes when printing my array. This has a trickle down problem when I use the array to build JSON. JSON ends up not being able to parse due to the escaped single quotes.
I thought this was a problem with my code, but I've distilled this down to a single usecase that should be straightforward.
let names = ["Tommy 'Tiny' Lister", "Tom Hanks"]
print(names)
The output is:
["Tommy \'Tiny\' Lister", "Tom Hanks"]
Note: I did not include escaped single quotes in my names array.
How do I prevent this from happening?
Here is the what I'm doing later in code to create JSON. For purposes of brevity, this is a really dumbed down version of what I'm doing:
var names = ["Tommy Tiny Lister", "Tom Hanks"]
var jsonString = """
{"names": \(names)}
"""
var jsonData = jsonString.data(using: .utf8)
if let json = try? JSONSerialization.jsonObject(with: jsonData!) as? [String: Any] {
let jsonData = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
let string = String(data: jsonData, encoding: .utf8)
print(string!)
}
What you are doing is using Swift arrays' description to generate JSON:
var jsonString = """
{"names": \(names)}
""" // your JSON will contain name.description
You are relying on the fact that the implementation of description just so happens to result in the same format as JSON most of the time. As you can see, when there are ' in the array elements, the description is not valid JSON.
Basically, you should not rely on the description to generate JSON. Instead, you should use Codable to generate JSON data from a Swift array.
For the JSON you want to produce:
{
"names": ["Tommy 'Tiny' Lister", "Tom Hanks"]
}
You can use a struct like this:
struct Names : Codable {
let names: [String]
}
And then you can produce JSON Data like this:
let encoder = JSONEncoder()
do {
let obj = Names(names: ["Tommy 'Tiny' Lister", "Tom Hanks"])
let data = try encoder.encode(obj)
} catch { ... }

Swift 3: Array Value for key

New to swift, just trying to catch some data.
so here is my swift code i used
let url = Bundle.main.url(forResource: "kurdiebg", withExtension: "plist")!
do {
let data = try Data(contentsOf: url)
let dataArray = try PropertyListSerialization.propertyList(from: data, format: nil) as! [[String:String]]
// Bloody Error is this
let request = dataArray(value(forKey: "Key"))
print(dataArray.count)
} catch {
print("This error must not happen", error)
}
i am getting
Cannot call value of non-function type '[[String : String]]'
what exactly i am trying to do?
so i have this plist file and i want to allow users to search in it , i perform this via a button action with this code above, so if a user write abbey, it would get the kurdi meaning,
The error occurs because you are using wrong syntax.
dataArray is not a function, you probably mean
dataArray.value(forKey: "Key")
However using KVC methods like value(forKey is only useful if you know what KVC is and why you need KVC. In this case it's inappropriate.
dataArray – as the name implies – is an array which is subscripted by index so you can get for example the value for key english of the first item with
dataArray[0]["english"]
which is a synonym for
dataArray[0].object(forKey:"english")
Or if you want to find an item for a specific key
dataArray.first(where: { $0["english"] == "abbey"})

Most Efficient way to Store Data in Swift

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

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