Unarchiving NSMutableArray in Swift 4 - swift

I'm adding objects to an NSMutableArray and archiving it with NSKeyedArchiver and UserDefaults.
Objects are added to the array, which I can see from the log console. I can also see that the archived objects contains the object.
The problem is that I whenever I unarchive the objects and add them to the mutable array, it always returns 1, no matter how many object I add.
Here's what I got:
Save objects to the array
trackNumberOfQuestionPlayed.add(questionsArray[randomQuestion] as AnyObject)
let saveQuestionsPlayed = NSKeyedArchiver.archivedData(withRootObject: trackNumberOfQuestionPlayed)
UserDefaults.standard.set(saveQuestionsPlayed, forKey: "QuestionsPlayed")
UserDefaults.standard.synchronize()
print(UserDefaults.standard.object(forKey: "QuestionsPlayed"))
Retrieve the objects
if let data = UserDefaults.standard.object(forKey: "QuestionsPlayed") as? Data {
if let trackNumberOfQuestionPlayed = (NSKeyedUnarchiver.unarchiveObject(with: data) as? NSMutableArray)
{
// In here you can access your array
print("Number of questions array is NOT empty: \(trackNumberOfQuestionPlayed.count)")
}
}

OK, I'm answering my own question. It turns out with a little bit tweaking you can save an array as described in my question. It turned out that some other code in my quiz game made a mess of it all. Sorted it out, and now the code below works:
Save objects to the array
trackNumberOfQuestionPlayed.add(questionsArray[randomQuestion] as AnyObject)
let saveQuestionsPlayed = NSKeyedArchiver.archivedData(withRootObject: trackNumberOfQuestionPlayed)
UserDefaults.standard.set(saveQuestionsPlayed, forKey: "QuestionsPlayed")
UserDefaults.standard.synchronize()
print("Count: \(trackNumberOfQuestionPlayed.count)")
Retrieve the objects
if let data = UserDefaults.standard.object(forKey: "QuestionsPlayed") as? Data {
if let trackNumberOfQuestionPlayed = (NSKeyedUnarchiver.unarchiveObject(with: data) as? NSMutableArray)
{
// In here you can access your array
self.trackNumberOfQuestionPlayed = trackNumberOfQuestionPlayed
print("Number of questions array is NOT empty: \(trackNumberOfQuestionPlayed.count)")
}
} else {
trackNumberOfQuestionPlayed = NSMutableArray()
print("questionsplayed is empty")
}

Related

How to read data from firebase and append it to an array

I have a userImages collection and I am trying to read data from it and appending it to my imgUrls array.
This is my code where I read the data from the database and try appending it to my array. Unfortunately, I keep getting an error because the array is apparently empty.
override func viewDidLoad() {
var ref: DatabaseReference!
let userID = Auth.auth().currentUser?.uid //holds the current user uid
ref = Database.database().reference()
var imgUrls = [String]() //array to hold the image urls from the userImages collection
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { (snapshot) in //read from userImages collection only from the subcollection where the
guard let dict = snapshot.value as? [String: Any] else { return } //document Id equals the current user uid. Create a dictionary from the
//snapshot values
let values = dict.values //holds the values from the dictionary
for value in values { //for loop to go through each value from the dictionary
imgUrls.append((value as? String)!) //and append to the imgUrls array
}
}
testLabel.text = imgUrls[0] //I used this to test, but i get an error saying the array is empty
}
I posted a question before, but it was so convoluted I decided to delete it and repost it simpler.
Any help is much appreciated!
the reason you are not getting anything in your testLabel.text is because:
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { (snapshot) in //read from userImages collection only from the subcollection where the
...
}
is an asynchronous function called. That is it will be done sometimes in the future.
but your:
testLabel.text = imgUrls[0] //I used this to test, but i get an error saying the array is empty
is outside of this call. So the results in "imgUrls" are not available yet.
Put this line inside the function or wait until it has finished before using the results.
You try to use the array before the observeSingleEvent closure is executed. All observation calls to Firebase are asynchronous. This means that you test code is executed before the closure and the array is still empty.
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { snapshot in
// this closure is executed later
guard let dict = snapshot.value as? [String: Any] else { return }
let values = dict.values
for value in values {
imgUrls.append((value as? String)!)
}
// your test code should bee here
}
// this is executed before closure and the array is empty
testLabel.text = imgUrls[0]
For this reason you get the error. You need to add your test code into the closure.
However, there is another catch. Asynchronous calls (their closures) are executed on the background thread.
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { snapshot in
// this is executed on the background thread
}
However, all user interface calls must be executed on the main thread. Also wrap your test code by calling the main thread, otherwise you won't see the result in the user interface.
DispatchQueue.main.async {
// this code is executed on the main thread
// all UI code must be executed on the main thread
}
After editing, your code might look like this:
override func viewDidLoad() {
var ref: DatabaseReference!
let userID = Auth.auth().currentUser?.lid
ref = Database.database().reference()
var imgUrls = [String]()
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { snapshot in
guard let dict = snapshot.value as? [String: Any] else { return }
let values = dict.values
for value in values {
imgUrls.append((value as? String)!)
}
DispatchQueue.main.async {
testLabel.text = imgUrls[0]
}
}
}

Firebase paginating data returns same data?

Currently, I am initially loading the user's messages through :
func fetchMessages() {
if started == true {
let messageRef = Database.database().reference().child("messages").queryOrdered(byChild: "convoID").queryEqual(toValue: convoID).queryLimited(toLast: 10)
messageRef.observe(.childAdded) { (snapshot) in
if let value = snapshot.value as? NSDictionary {
let newMessage = message()
newMessage.messageText = value["content"] as? String
newMessage.sender = value["sender"] as? String
newMessage.messageID = snapshot.key
self.messageList.append(newMessage)
self.queryingStatus = true
self.messagesTableView.reloadData()
self.scrollToBottom()
}
}
}
}
Now, to minimize the data download, I decided to break the messages into chunks as such so that the user will download ten subsequent messages each time they pull up on the table view:
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
let lastIDDictionary = messageList[0]
let lastIDQueried = lastIDDictionary.messageID
let messageRefAddition = Database.database().reference().child("messages").queryOrdered(byChild: "convoID").queryLimited(toLast: 10).queryEnding(atValue: convoID, childKey: lastIDQueried!)
messageRefAddition.observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let messageValue = child.value as? NSDictionary {
let newMessage = message()
newMessage.messageText = messageValue["content"] as? String
newMessage.sender = messageValue["sender"] as? String
newMessage.messageID = child.key
self.messageList.insert(newMessage, at: 0)
self.messagesTableView.reloadData()
}
}
}
refreshControl.endRefreshing()
}
The problem is, when I pull up on the table view, the first time it returns some new messages (I am not sure whether the order is even correct). However, when I pull on the table view again to refresh, it adds those same ten messages again. I printed the lastIDQueried in the refresh method, and after the initial load the ID remains the same even though I am accessing the first item in the array of dictionaries? Basically,when I refresh the table view, it is not querying the correct data and my pagination implementation does not seem to be working correctly.
Basically, the problem was that I was inserting the post in the wrong place in the array and the last item was still being added to the array which was always the same (as I ended on the value). As such, I added a counter that incremented each time a value was added. Then, I inserted the subsequent post at the counter value in the array then again incremented. Finally, if the message ID was equal to the current first message in array, I would not insert it.

Swift JSON to Dictionary<String: Any>. Then cast "Any" as NSMutableArray

I am trying to read from a JSON String and draw a graph with its data:
{"y-axis-data":{"min":0.0,"max":1000,"Step":100.0},"x-labels":[1994,2000,2005],"y-values":[20,305,143]}
I wrote a function to create a dictionary from the string:
func jsonToDictionary(jsonString: String) -> [String: Any]? {
if let jsonData: Data = jsonString.data(using: String.Encoding.utf8) {
do {
return try (JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any])!
} catch {
some bla bla
}
}
return nil
}
The return dictionary should count 3 elements inside when I pass my JSON string, and it does.
I can then change of some variables (Double) which are 0 until now and give them the values of min max and Step from the "y-axis-data" key of my dictionary, using {"min":0.0,"max":1000,"Step":100.0} as a dictionary it self. Works fine.
My trouble comes when trying to initialize other atributes:
self.my_view!.x-labels = (jsonToDictionary!["x-labels"]) as? NSMutableArray
my_view has already been initialized as UIViewCustomClass(frame: someFrame)
myview.x-labels is an NSMutableArray and it is initialized as nil. After executing that line of code it is still nill, of course myview.x-labels.count is nil. if I do it this way:
self.my_view!.x-labels = (jsonToDictionary!["x-labels"]) as! NSMutableArray
I get a warning :
Treating a forced downcast to NSMutableArray as optional will never produce nil.
It then crashes on runtime with this error:
Could not cast value of type '__NSArrayI' (0x110ed5448) to 'NSMutableArray' (0x110ed4598).
of course the exact same thing happens with "y-values"
What is the right way to do this?
It was because your json!["x-labels"] is implicitly treated as NSArray, so you somehow had to do a "double-force-cast"
// get the converted json
let j = jsonToDictionary(jsonString: dict)
// double cast
let m = (j!["x-labels"] as! NSArray).mutableCopy() as! NSMutableArray
Result:
I think, JSONSerialization class converts into Array, and then Swift cannot cast Array to NSMutableArray. You can do this (Swift4):
let array = (jsonToDictionary!["x-labels"]) as? [Int]
if array != nil {
self.my_view!.x-labels = NSMutableArray(array: array!)
}

How to get some data out of an array in swift?

Here is my problem: I have an plist file, which have a simple strut:
root object is an array, and there are 2 sections in the array, each of them are dictionary including 2 pair key-value data:
and I'm going to create a tableView to show the datas, but I can't get the content out of the array :
here is how i declared my dataArray:
var plistArray = [AnyObject]()
can some one help me?
You need to properly cast at each level:
if let innerArray = plistArray[0] as? [AnyObject] {
if let dataDic = innerArray[indexPath.row] as? [String:String] {
if let imageName = dataDic["Pic"] {
cell?.imageView?.image = UIImage(named: imageName)
}
}
}
But why are you using AnyObject when you know what the plist contains? Use proper types. You know it's an array of arrays of dictionaries with String keys and String values.
var plistArray = [[[String:String]]]()
Then all you need is:
if let imageName = plistArray[0][indexPath.row]["Pic"] {
cell?.imageView?.image = UIImage(named: imageName)
}

Saving an array of objects in UserDefaults

Using swift 3.0, I'm trying to add an array of objects into user defaults. As this isn't one of the regular UserDefault types I've realised that the object or array of objects (can't work out which one) will need to be parsed to the type NSData to then be added to UserDefaults.
My attempt so far is the following:
Within the object
func updateDefaults()
{
let data = NSData(data: MyVariables.arrayList)
UserDefaults.standard.set(data, forKey: "ArrayList")
}
Where MyVariables.arrayList is the array of objects.
Lastly, if possible it would be nice to know how to retrieve this from UserDefaults and convert it to the original one-dimensional array of objects.
Thanks
REPOSITORY: https://github.com/Clisbo/ModularProject
You can't save your custom array in NSUserDefaults. To do that you should change them to NSData then save it in NSUserDefaults Here is the code.
var custemArray: [yourArrayType] {
get {
if let data = NSUserDefaults.standardUserDefaults().objectForKey("yourKey") as? NSData {
let myItem = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? yourType
}
}
set {
let data =NSKeyedArchiver.archivedDataWithRootObject(yourObject);
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "yourKey")
NSUserDefaults.standardUserDefaults().synchronize()
}
}