Swift - read plist file to an array? - arrays

I have created a mini translation from English words to Spanish words. I would like to use the englishArray.plist instead of my englishArray = ["the cat"] How can I create this?
I have also used a localizable.strings to retrieve the value "the cat" for "el gato" but I would like to retrieve this from englishArray.plist
I started off with this but not sure if I'm on the right path
let path = NSBundle.mainBundle().pathForResource("englishArray", ofType: "plist")
let plistEnglishArray = NSArray(contentsOfFile: path!)
Here is the rest of my code:
var englishArray: [String] = ["rainbow", "boots", "heart", "leaf", "umbrella", "tired", "angry", "cry", "the cat" ]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.translateTextField.delegate = self
picker.delegate = self
picker.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func translateButtonTapped(sender: UIButton) {
let emptyString = self.translateTextField.text
if (emptyString!.isEmpty) {
print("please enter a word")
}
for transIndex in englishArray.indices {
if englishArray[transIndex] == emptyString!.lowercaseString {
//englishArray
//translateLabel.text = "\(spanishArray[transIndex])"
translateLabel.text = NSLocalizedString(emptyString!.lowercaseString, comment:"")
print(emptyString)
return
}
}
}

Swift 4
The absolute simplest way to do this is
let url = Bundle.main.url(forResource: "Sounds", withExtension: "plist")!
let soundsData = try! Data(contentsOf: url)
let myPlist = try! PropertyListSerialization.propertyList(from: soundsData, options: [], format: nil)
The object myPlist is an Array or Dictionary, whichever you used as the base of your plist.

Change your root object to Array, then
var myEnglishArray: [String] = []
if let URL = NSBundle.mainBundle().URLForResource("englishArray", withExtension: "plist") {
if let englishFromPlist = NSArray(contentsOfURL: URL) as? [String] {
myEnglishArray = englishFromPlist
}
}

Swift 4
You can use Codable which is pure swift type.
Firstly load Plist file from bundle then use PropertyListDecoder
Complete code -
func setData() {
// location of plist file
if let settingsURL = Bundle.main.path(forResource: "JsonPlist", ofType: "plist") {
do {
var settings: MySettings?
let data = try Data(contentsOf: URL(fileURLWithPath: settingsURL))
let decoder = PropertyListDecoder()
settings = try decoder.decode(MySettings.self, from: data)
print("array is \(settings?.englishArray ?? [""])")//prints array is ["Good morning", "Good afternoon"]
} catch {
print(error)
}
}
}
}
struct MySettings: Codable {
var englishArray: [String]?
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
englishArray = try values.decodeIfPresent([String].self, forKey: .englishArray)
}
}

This will read a resource in your bundle with the name "englishArray.plist" and store it in the immutable variable english. It will be an Optional that you should test before using.
It uses a closure to read the file and return the array, this lets you use a immutable value rather than a mutable variable that can be changed. It's a good idea to use immutables wherever you can - they promote stability.
import Foundation
let english:[String]? = {
guard let URL = NSBundle
.mainBundle()
.URLForResource("englishArray", withExtension: "plist") else {
return nil
}
return NSArray(contentsOfURL: URL) as? [String]
}()

Here is the solution for swift 3. For this solution you do not need to change types in your plist structure (keep Dictionary, Array, as is). Also note that since your array's name in plist is also englishArray so the (value for key) argument in the second if statement is also englishArray.
var myEnglishArray: [String] = []
if let URL = Bundle.main.url(forResource: "englishArray", withExtension: "plist") {
guard let englishFromPlist = NSDictionary(contentsOf: URL) else { return [] }
if let englishArray = englishFromPlist.value(forKey: "englishArray") as? [String] {
for myEnglish in englishArray {
myEnglishArray.append(myEnglish)
}
}
}

Related

How to set custom class array data in UserDefalts in swift 4

I have A Array List
private var deviceArray: [SearchPeripheral]? = []
I want to hold data of device array in UserDefaults but its crashing when I store it.
please help me on it
Thank you.
You can't store custom models in UserDefaults. You can make the following improvements to save your objects as [[String:Any]]
struct SearchPeripheral: Codable {
let name: String
let model: String
}
extension SearchPeripheral {
var dictionary: [String:Any] {
let data = try! JSONEncoder().encode(self)
let any = try! JSONSerialization.jsonObject(with: data)
return any as! [String:Any]
}
init?(_ dict: [String:Any]) {
guard let peripheral = (try? JSONSerialization.data(withJSONObject: dict)).flatMap({
try? JSONDecoder().decode(SearchPeripheral.self, from: $0)
}) else {
return nil
}
self = peripheral
}
}
Saving Array of SearchPeripheral:
func save(_ peripherals: [SearchPeripheral]) {
let allPeripherals = peripherals.compactMap({$0.dictionary})
UserDefaults.standard.set(allPeripherals, forKey: "peripherals")
}
Getting Array of SearchPeripherals:
func getPeripherals() -> [SearchPeripheral] {
let allPeripherals = UserDefaults.standard.array(forKey: "peripherals") as? [[String:Any]] ?? []
let peripherals = allPeripherals.compactMap(SearchPeripheral.init)
return peripherals
}

Load two json files of Arrays of Dictionaries. Add missing dictionaries from the file A to file B

I need to sync two json files to add new content from File A (located in the app bundle) to File B after an app update.
Both json files are arrays of dictionaries. I need to iterate the dictionaries form File A, and based on the "id" value, if a dictionary is not present in File B I need to append those missing dictionaries and save File B back to the file system.
I have a solution below that does this, and seems to work. But it's SO ugly! Granted I put this together in about 15 minutes cringing the whole way but I'm sure there has to be a better way of handling this. Also, I don't want to further muddy the waters by converting these dictionaries to structs or models for the comparison only to convert them back to dictionaries -> json.
Any advise here would be great! I prefer clean code and this is a mess.
typealias JSON = [[String: Any]]
static private func uglySync() {
let fileName: String = "someFileName"
guard let sourceUrl = Bundle.main.url(forResource: fileName, withExtension: "json") else { return }
guard let destinationDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let destinationUrl = destinationDirectory.appendingPathComponent("Data/" + fileName + ".json")
do {
let sourceData = try Data(contentsOf: sourceUrl)
do {
if let sourceArray = try JSONSerialization.jsonObject(with: sourceData, options: .mutableContainers) as? JSON {
do {
let destinationData = try Data(contentsOf: destinationUrl)
do {
if let destinationArray = try JSONSerialization.jsonObject(with: destinationData, options: .mutableContainers) as? JSON {
var mutableArray = destinationArray
sourceArray.forEach({ (item) in
if let itemId = item["id"] as? String {
let foundItem = destinationArray.filter { $0["id"] as! String == itemId }.first
if foundItem == nil {
mutableArray.append(item)
}
}
})
do {
let jsonData = try JSONSerialization.data(withJSONObject: mutableArray, options: .prettyPrinted)
try jsonData.write(to: destinationUrl)
} catch let error as NSError {
print("Couldn't write to file: \(error.localizedDescription)")
}
} else {
print("Cound not process json")
}
} catch {
print(error.localizedDescription)
}
} catch {
print(error.localizedDescription)
}
} else {
print("Cound not process json")
}
} catch {
print(error.localizedDescription)
}
} catch {
print(error.localizedDescription)
}
// oh wow the try catches :/
}
I've grouped converting the files to jsonArray to simplify the do...catch. Alternatively, if you don't need to print the error message, you could opt to have Optional try? as well to remove the do...catch block.
typealias JSONArray = [[String: Any]]
private func jsonArray(from fileURL: URL) -> JSONArray? {
do {
let fileData: Data = try Data(contentsOf: fileURL)
guard let jsonArray = (try JSONSerialization.jsonObject(with: fileData, options: .mutableContainers)) as? JSONArray else {
debugPrint("Failed to find JSON Array table")
return nil
}
return jsonArray
} catch {
print(error.localizedDescription)
return nil
}
}
func sync() {
let fileName: String = "someFileName"
guard
let fileURL: URL = Bundle.main.url(forResource: fileName, withExtension: "json"),
let destinationDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first,
let destinationURL: URL = destinationDirectory.appendingPathComponent("Data/" + fileName + ".json"),
let sourceArray = jsonArray(from: fileURL),
let destinationArray = jsonArray(from: destinationURL)
else {
return
}
var mutableArray = destinationArray
let destinationIDArray = destinationArray.compactMap { $0["id"] as? String }
mutableArray.forEach { (item) in
if let itemId = item["id"] as? String, !(destinationIDArray.contains { $0 == itemId }) {
mutableArray.append(item)
}
}
// Update File
do {
let jsonData = try JSONSerialization.data(withJSONObject: mutableArray, options: .prettyPrinted)
try jsonData.write(to: destinationURL)
} catch {
print("Couldn't write to file: \(error.localizedDescription)")
}
}
I think you can put the different trys in the same do block.
do {
try function1()
try function2()
} catch {
print(error.localizedDescription)
}
So afterwards your function may look like
typealias JSON = [[String: Any]]
static private func moderatelyOkSync() {
let fileName: String = "someFileName"
guard let sourceUrl = Bundle.main.url(forResource: fileName, withExtension: "json") else { return }
guard let destinationDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let destinationUrl = destinationDirectory.appendingPathComponent("Data/" + fileName + ".json")
do {
let sourceData = try Data(contentsOf: sourceUrl)
if let sourceArray = try JSONSerialization.jsonObject(with: sourceData, options: .mutableContainers) as? JSON {
let destinationData = try Data(contentsOf: destinationUrl)
}
var mutableArray = destinationArray
sourceArray.forEach({ (item) in
if let itemId = item["id"] as? String {
let foundItem = destinationArray.filter { $0["id"] as! String == itemId }.first
if foundItem == nil {
mutableArray.append(item)
}
}
})
let jsonData = try JSONSerialization.data(withJSONObject: mutableArray, options: .prettyPrinted)
try jsonData.write(to: destinationUrl)
} catch {
print(error.localizedDescription)
}
}
The way I would do it is to Decode the json files with struct and then encode(serialization) it to the other files. Because the code to do that would be a 2 liner, but you would first have to layout all the variables in the struct. Probably still not optimal

Implementing comments on feed post in swift using firebase

I am trying to implement a comment section on each feed post in my app using swift and firebase, but am having trouble with the code that will get the comments. In my function it is returning a empty array of messageComments but I do not know what I am doing wrong. If I want my firebase database structure to look like that in the picture how can I implement the code that will download those comments in an array?
func getFeedMessages(handler: #escaping (_ feedMessages:[FeedMessages]) -> ()){
var feedMessagesArray = [FeedMessages]()
var commentArray = [messageComments]()
REF_FEEDMESSAGES.observeSingleEvent(of: .value) { (feedMessagesSnapshot) in
guard let feedMessagesSnapshot = feedMessagesSnapshot.children.allObjects as? [DataSnapshot] else {return}
for messages in feedMessagesSnapshot {
let content = messages.childSnapshot(forPath: "content").value as? String ?? "Joe Flacco is an elite QB"
let icon = messages.childSnapshot(forPath: "icon").value as? String ?? "none"
let color = messages.childSnapshot(forPath: "color").value as? String ?? "bop"
self.REF_FEEDCOMMENTS.observeSingleEvent(of: .value, with: { (feedCommentsSnapshot) in
guard let feedCommentsSnapshot = feedCommentsSnapshot.children.allObjects as? [DataSnapshot] else {return}
for comments in feedCommentsSnapshot {
commentArray.append((comments.childSnapshot(forPath: "comments").value as? messageComments!)!)
}
})
print(" comment: ")
print(commentArray)
let messages = FeedMessages(content: content, color: color, icon: icon, comments: commentArray)
feedMessagesArray.append(messages)
}
handler(feedMessagesArray)
}
}
If you also have the same data structure, there would be no need for another request for the comments since they are nested in the feed messages. This will only require some simple parsing, which can be made easier to read and understand with a few extensions.
extension DataSnapshot {
var string: String? {
return value as? String
}
var childSnapshots: [DataSnapshot] {
return children.allObjects as? [DataSnapshot] ?? []
}
func child(_ path: String) -> DataSnapshot {
return childSnapshot(forPath: path)
}
}
These two extensions take care of the snapshot operations needed to initialize the objects.
extension MessageComments {
convenience init(snapshot: DataSnapshot) {
self.comments = snapshot.childSnapshots.map { $0.string }
}
}
extension FeedMessages {
convenience init(snapshot: DataSnapshot) {
self.color = snapshot.child("color").string ?? "bop",
self.comments = MessageComments(snapshot: snapshot.child("comments"))
self.content = snapshot.child("content").string ?? "Joe Flacco is an elite QB",
self.icon = snapshot.child("icon").string ?? "none",
}
}
Just map the children snapshots to initialize each of them as a FeedMessages object.
func getFeedMessages(handler: #escaping (_ feedMessages: [FeedMessages]) -> ()) {
REF_FEEDMESSAGES.observeSingleEvent(of: .value) {
handler($0.childSnapshots.map { FeedMessages(snapshot: $0) })
}
}

Swift: Parsing Arrays out of JSONs

[{"name":"Air Elemental","toughness":"4","printings":["LEA","BTD","7ED","8ED","9ED","10E","DD2","M10","DPA","ME4","DD3_JVC"]}]
I have a JSON where there is an array in each listing called "printings" as seen below, how would I take this array out of each listing and convert it into a string like "LEA-BTD-7ED". Here is what I have so far but its crashing.
let err : NSErrorPointer?
let dataPath = NSBundle.mainBundle().pathForResource("cardata", ofType: "json")
let data : NSData = try! NSData(contentsOfFile: dataPath! as String, options: NSDataReadingOptions.DataReadingMapped)
do{
var contents = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! [AnyObject]
for var i = 0;i<contents.count;++i{
let printing = contents[i]["printings"] as! String
}
}
Here's the code:
let path = dataPath!
if let JSONData = NSData(contentsOfFile: path)
{
do
{
if let dictionariesArray = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions()) as?
[[String: AnyObject]]
{
for dictionary in dictionariesArray
{
if let printingsArray = dictionary["printings"] as? [String]
{
let printingsString = printingsArray.joinWithSeparator("-")
print(printingsString)
}
}
}
}
catch
{
print("Could not parse file at \(path)")
}
}
Executing it prints "LEA-BTD-7ED-8ED-9ED-10E-DD2-M10-DPA-ME4-DD3_JVC"
You can't cast an Array (contents[i]["printings"]) to a String. What you want is Array's joinWithSeparator() method, like this:
let printing = contents[i]["printing"] as! Array
let printingStr = printing.joinWithSeparator("-")
(Actually, I'm not sure whether you need the as! Array; try it without it.)

struggling with JSON parsing in swift

I am trying to load data in JSON format from my server into IOS application.
Here is my JSON:
[
{
"BankName": "bank1",
"CurrencyName": "cur1",
"SellRate": "0.65",
"BuyRate": "0.55",
"OfficialRate": "0.6"
},
{
"BankName": "bank1",
"CurrencyName": "cur2",
"SellRate": "1.65",
"BuyRate": "1.55",
"OfficialRate": "1.6"
}
]
There are 2 files in my project:
1:
import Foundation
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
2:
import Foundation
class JsonTest {
func loadJson(completion: ((AnyObject) -> Void)!) {
var urlString = "http://a.com/g.php"
let session = NSURLSession.sharedSession()
let sourceUrl = NSURL(string: urlString)
var task = session.dataTaskWithURL(sourceUrl!){
(data, response, error) -> Void in
if error != nil {
println(error.localizedDescription)
} else {
var error: NSError?
var jsonData = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as NSArray
var shots = [Shot]()
println(jsonData)
for shot in jsonData{
let shot = Shot(data: shot as NSDictionary)
shots.append(shot)
}
println(shots) //[jsontest.Shot, jsontest.Shot]
}
}
task.resume()
}
}
I am trying to populate array automatically when my app starts. To do it I have a code in my mainViewController class.
override func viewDidLoad() {
super.viewDidLoad()
let api = JsonTest()
api.loadJson(nil)
}
The problem occurs when I try to print shots variable in the second file.
it returns [jsontest.Shot, jsontest.Shot] when I was expecting the array of dictionaries.
println(jsonData) works fine and shows JSON data from URL.
Can anybody advise what is wrong in my program?
"shots" is an array of instances of Shot, not a dictionary:
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
let sourceUrl = NSURL(string: urlString)
// Using NSData instead of NSURLSession for experimenting in Playground
let data = NSData(contentsOfURL: sourceUrl!)
var error: NSError?
// As I'm using Swift 1.2 I had to change "as" with "as!"
let jsonData = NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers, error: &error) as! NSArray
for shot in jsonData{
let shot = Shot(data: shot as! NSDictionary)
shots.append(shot)
}
println(shots[0].bankName)
Update for Swift 2
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
// Using NSData instead of NSURLSession for experimenting in Playground
if let sourceUrl = NSURL(string: urlString) {
NSURLSession.sharedSession().dataTaskWithURL(sourceUrl, completionHandler: { (data, response, error) in
if error == nil {
if let data = data, jsonData = try? NSJSONSerialization.JSONObjectWithData(data, options: []), jsonArray = jsonData as? [NSDictionary] {
for item in jsonArray {
let shot = Shot(data: item)
shots.append(shot)
}
print(shots[0].bankName)
} else {
print("no JSON data")
}
} else {
print(error!.localizedDescription)
}
}).resume()
}
Seems like there are two problems:
You're trying to use println to debug instead of setting a breakpoint and checking your objects values.
You have not created a description or debugDescription property for your object, so println on your object is just using some default implementation.
shots is an array of your custom object, so when you call println, it's using the description for Array, which prints out the objects in the array, comma separated, and within square brackets.
The default description property for classes in Swift just prints the class name.
Ideally, you should just use a break point to check the values of your object to be certain it initialized correctly, but if it's actually important to get them to print right, it's only a matter of implementing the description property:
override var description: String {
get {
// build and return some string that represents your Shot object
}
}

Resources