Array appending overwrites last index of Realm object - ios

I have an array of Realm objects and before i save them in Realm DB i have my own array of objects in for loop:
var objs = [self.friendsObject] //0 values at first
for i in (0..<json.count) { //counts 2
let _id = json[i]["_id"] as? String
let userName = json[i]["userName"] as? String
let profile_pic = json[i]["profile_pic"] as? String
let phone = json[i]["phone"] as? String
self.friendsObject.id = _id!
self.friendsObject.username = userName!
self.friendsObject.profilepic = profile_pic!
self.friendsObject.phone = phone!
objs.append(self.friendsObject) //2nd element overwrites 1st one
}
self.friendsObject.save(objects: objs)
So i can see the first object with correct items inside objs before i insert second array, but in second index there are 2 array of objects with same values. i appreciate any help.
Note: It is not duplicate, i have already checked some similar questions but it doesn't apply to my issue.

As Vadian commented, the problem is that the code is not creating new instances of friendsObject but appending the same instance with different values.
Edit
Below an example of how to copy JSON to a class based on the information provided in the question:
// Simulating a JSON structure filled with some data.
var jsonData = [Int: [String: String]]()
for index in 0..<10 {
var values = [String: String]()
values["id"] = "id\(index)"
values["username"] = "username\(index)"
values["profilepic"] = "profilepic\(index)"
values["phone"] = "phone\(index)"
jsonData[index] = values
}
// Friend sample class where JSON data will be copied to.
class Friend {
var id: String
var username: String
var profilepic: String
var phone: String
init(_ id: String, _ username: String, _ profilepic: String, _ phone: String) {
self.id = id
self.username = username
self.profilepic = profilepic
self.phone = phone
}
}
// The array where to copy the values from the JSON data.
var friends = [Friend]()
// Looping through the JSON data with a sorted key.
for jsonSortedKey in jsonData.keys.sorted(by: <) {
// Obtaining a JSON element containing friend data.
let jsonFriend = jsonData[jsonSortedKey]!
// Creating a new friend's instance from the JSON friend's data.
let friend = Friend((jsonFriend["id"]!), jsonFriend["username"]!, (jsonFriend["profilepic"]!), (jsonFriend["phone"]!))
friends.append(friend)
}
The result is this:

Related

Can't return elements from two-dimensional array

I'm trying to make a feature that saves a title and link to a website
This is what I am attempting to store
[0] -> [TITLE, LINK]
[1] -> [TITLE, LINK]
[2] -> [TITLE, LINK]
This is how I am doing it
//Create array
var favoriteProducts = [[String:String]]()
//Add products
let firstArray = [titleName:String(), link:String()]
favoriteProducts.append(firstArray)
//Add to defaults
UserDefaults.standard.set(favoriteProducts, forKey: "favProducts")
The next step is to loop through using ForEach to return the title and link. For debugging I'm trying to use
UserDefaults.standard.array(forKey: "favProducts")![0][0]
Which returns
Value of type 'Any' has no subscripts
However
UserDefaults.standard.array(forKey: "favProducts")![0]
Returns
(website, link)
So my question here is how do I return both the website and link individually and not just the entire subscript?
you can store arrayOfStrings In struct array and can access the vale from struct ,Say example
var favouriteProducts = [[String:Any]]()
var listOfSite = [SiteDetail]()
var firstArray = ["titleName":"String","link":"firstlink"]
var secondArray = ["titleName":"s","link":"s"]
favouriteProducts.append(firstArray)
favouriteProducts.append(secondArray)
UserDefaults.standard.set(favouriteProducts, forKey: "favProducts")
let value = UserDefaults.standard.array(forKey: "favProducts") as? [[String:String]] ?? [[:]]
for values in value{
let siteName = values["titleName"] as? String ?? ""
let link = values["link"] as? String ?? ""
let siteDetail = SiteDetail(website: siteName, link: link)
listOfSite.append(siteDetail)
}
print("listOf \(listOfSite[0].link)")
print("listOf \(listOfSite[0].website)")
//////////////////////////
struct SiteDetail{
var website:String?
var link:String?
}
Here UserDefaults.standard.array returns an array of Any type and you are storing an array of the dictionary. So at the time of retrieve, you need to cast the array element as a dictionary.
Also, you can use the key to get the dictionary value.
let firstElement = (UserDefaults.standard.array(forKey: "favProducts")?[0] as? [String: String])
let title = firstElement?["titleName"]
let link = firstElement?["link"]

Retrieve array of dictionary from Firebase & Swift 3

I have a json database on firebase and trying to get them and put into local array of dictionaries.
My json model on Firebase
My struct model is also like below
struct Places {
var type:String!
var country:String!
var name:String!
var image:String!
var coords:[Coords]!
init(type: String, country: String, name: String, image: String, coords: [Coords]) {
self.type = type
self.country = country
self.name = name
self.image = image
self.coords = coords
}
init(snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as! [String:Any]
type = snapshotValue["type"] as! String
country = snapshotValue["country"] as! String
name = snapshotValue["name"] as! String
image = snapshotValue["image"] as! String
coords = snapshotValue["coords"] as! [Coords]!
}
}
And also [Coords] struct model like below:
struct Coords {
var latStart:String!
var latEnd:String!
var lonStart:String!
var lonEnd:String!
init(latStart: String, latEnd: String, lonStart: String, lonEnd: String) {
self.latStart = latStart
self.latEnd = latEnd
self.lonStart = lonStart
self.lonEnd = lonEnd
}
}
And I am trying to get and put json data by below code:
placesRef.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
print("data not exist")
return
}
var plc: [Places] = []
for eachPlace in (snapshot.children){
let place = Places(snapshot: eachPlace as! FIRDataSnapshot)
plc.append(place)
}
self.allPlaces = plc
The problem is that I can get the array of dictionary except coords dictionary inside array of dictionary. [Coords] dictionary seems null and I would like to know what the problem is. Thanks for any suggestion.
Because snapshotValue["coords"] as! [Coords]! are not Coords yet. They are just dictionaries. You have to go through each dictionary in snapshotValue[“coords”] and init a Coords object, then when you’re finished group them all into an array and assign it to self.coords of the Places struct. The map function is really convenient for this.
Example:
I would change the Coords init function to something like:
init(dictionary: [String : AnyObject]) {
self.latStart = dictionary["lat1"] as? String
//...
//...
}
Then in Places init use something like:
coords = (snapshotValue["coords"] as? [[String : AnyObject]])?.map({ Coord(dictionary: $0) })
I didn't test this and making some assumptions here, but you should be able to make something similar work.

How to access a certain group of key values in Firebase Database with Swift?

Im new to using Firebase and am struggling to understand how to reach certain points of my firebase database using an ios query with swift.
My database looks something like this:
JSON DATABASE
Im trying to retrieve all the data and then target the location data to put pins on a mapview.
I have the Firebase and FirebaseDatabase pods built into my app with no problem but don't really know where to go from there.
Any help would be greatly appreciated
What I would do is the following:
First I would create a struct for People, to save a model for every Person entry.
You create a new Swift file and enter the following:
struct People {
var name: String = ""
var age: String = ""
var latitude: String = ""
var longitude: String = ""
var nationality: String = ""
}
Then, in your ViewController class, you create an NSArray of People and instantiate is as empty.
var peoples: [People] = []
Then you create a function to download your desired data.
func loadPeople() {
// first you need to get into your desired .child, what is in your case People
let usersRef = firebase.child("People")
usersRef.observeEventType(.Value, withBlock: { snapshot in
if snapshot.exists() {
// since we're using an observer, to handle the case
// that during runtime people might get appended to
// the firebase, we need to removeAll, so we don't
// store people multiple times
self.peoples.removeAll()
// then we sort our array by Name
let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "Name",ascending: false)])
for element in sorted {
let name = element.valueForKey("Name")! as? String
let age = element.valueForKey("age")! as? String
let location = element.valueForKey("location")! as? NSDictionary
let nationality = element.valueForKey("nationality")! as? String
// then we need to get our lat/long out of our location dict
let latitude = location.valueForKey("latitude")! as? String
let longitude = location.valueForKey("longitude")! as? String
// then we create a model of People
let p = People(name: name!, age: age!, latitude: latitude!, longitude: longitude!, nationality: nationality!)
// then we append it to our Array
self.tweets.append(t)
}
}
// if we want to populate a table view, we reload it here
// self.tableView.reloadData()
})
}
We need to call the function in viewDidAppear, after the UITableView for example, is loaded.
override viewDidAppear() {
loadPeople()
}
Now we have an Array of People and are able to populate a UITableView or print the values:
for p in peoples {
print("name = \(p.name)")
print("longitude = \(p.longitude)")
print("latitude = \(p.longitude)")
}

Override old object in array swift ios

I have an array of custom objects. For simplicity, lets say each object has 3 properties : id, timestamp and text. I am now obtaining a JSON response from my server which contains updated text for my objects in the array. I need to do the following :
Obtain the object with the given id from my array.
Set the text of the object to the updated text WITHOUT changing any of its other properties.
Override the old object with the new and updated one.
I have found the following extension which allows me to find the object in my array.
extension CollectionType {
func find(#noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
return try indexOf(predicate).map({self[$0]})
}
}
I am then using the following logic to obtain the item from the array.
let my_object = questionImageObjects.find({$0.id== myId})
Now I set the text using my_object.text = currText.
The last step is to override the old object in the array with the updated one. This is where I am stuck.
The extension find will return a copy of the original struct. Whatever you do to it, the original won't be affected. You can modify it through the index:
struct DataModel: CustomStringConvertible {
var id: Int
var timestamp: NSDate
var text: String
init(id: Int, timestamp: NSDate, text: String) {
self.id = id
self.timestamp = timestamp
self.text = text
}
// For debug purposes
var description: String {
get { return "( id = \(id), timestamp = \(timestamp), text = \(text) )" }
}
}
// Initial data
var arr = [
DataModel(id: 1, timestamp: NSDate(), text: "Anakin Skywalker"),
DataModel(id: 2, timestamp: NSDate(), text: "Luke Skywalker")
]
// Now the server returns a JSON that says Anakin has turned to the Dark Side
let jsonString = "{ \"id\": 1, \"text\": \"Darth Vader\" }"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
// Update the text
if let dict = json as? [String: AnyObject],
let id = dict["id"] as? Int,
let newText = dict["text"] as? String,
let index = (arr.indexOf { $0.id == id }) {
arr[index].text = newText
}
print(arr)
If those if lets confuse you, here's a step-by-step guide:
if let dict checks that the JSON can be converted into a Dictionary
let id checks that it has an id key of type Int
let newText checks that it has a text key of type String
let index checks that the array contains an element with that id

Insert object in RLMArray property on already persisted object?

I have a fairly simple and straightforward issue I'm trying to solve with Realm. I have objects which have an array property on them (threads). When I fetch all the threads via our API, they're all persisted into Realm since the parent objects are individually saved and thus all child objects (messages & users) within the array property are properly persisted as well. But during the lifecycle of the app I need to add new messages into that array property. Here's what I'm attempting to do:
func addPubNubMessageToThread(notification: NSNotification) {
if let info = notification.userInfo as? Dictionary<String, AnyObject> {
var embeddedMessage = Message(json: (info["data"] as? NSDictionary)!)
let threadId = (info["thread"]! as String)
// Persist the message to Realm for future use
var respectiveThread = Thread(forPrimaryKey: threadId)
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
respectiveThread.conversation.insertObject(embeddedMessage, atIndex: UInt(0)) // Always fails here in XCode with the error below
realm.addOrUpdateObject(respectiveThread)
realm.commitWriteTransaction()
}
}
But each time I get the following error:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Setting unique property '_id' with existing value '540729b543dd5d1868a42b5d''
For more context, here are my Realm models:
class Message: RLMObject {
dynamic var _id = ""
dynamic var type = ""
dynamic var text = ""
dynamic var author = User()
dynamic var created = NSDate()
dynamic var lastUpdated = NSDate()
}
class Thread: RLMObject {
dynamic var _id = ""
dynamic var name = ""
dynamic var conversation = RLMArray(objectClassName: Message.className())
dynamic var participants = RLMArray(objectClassName: User.className())
dynamic var created = NSDate()
dynamic var lastUpdated = NSDate()
}
class User: RLMObject {
dynamic var _id = ""
dynamic var name = ""
dynamic var firstName = ""
dynamic var lastName = ""
dynamic var email = ""
dynamic var phone = ""
dynamic var username = ""
dynamic var avatar = NSData()
dynamic var created = NSDate()
dynamic var lastUpdated = NSDate()
}
Each message has a property called author, and the _id it's complaining about is the _id of the author (or user object) of the message. The error message is hard to decipher. I think it's saying that I'm trying to create a new user object with a primary key that already exists. If that's the issue, what should I do instead to add new Realm objects to an array property on an already persisted object?
Edit
I am setting the primary key for each model like so:
override class func primaryKey() -> String {
return "_id"
}
And _id is a GUID generated by MongoDB...so it's globally unique.
The problem is that when calling
insertObject
your object and all child objects are created in the Realm instead of updated if they already exist. If you first explicitly update your object (which applies to all child objects as well), then this should avoid the issue:
var persistedMessage = realm.addOrUpdateObject(embeddedMessage)
respectiveThread.conversation.insertObject(persistedMessage, atIndex: UInt(0))
Your problem is probably in your the Message(json:) initializer. When you process the JSON for the message, I'm guessing the author ID is passed (and perhaps the rest of the author data). In your initializer you are probably instantiating a new User, rather than getting an instance of the existing User in the Realm. I recreated your model objects, and then created an existing user, thread and message. I was able to deserialize a message from JSON and add the new message to the existing conversation array, as long as my deserialization logic grabbed an existing user if it was provided. Here is my example initializer:
init(json : NSDictionary) {
self._id = json["_id"] as String
self.type = json["type"] as? String ?? ""
self.text = json["text"] as? String ?? ""
self.created = json["created"] as? NSDate ?? NSDate()
self.lastUpdated = json["lastUpdated"] as? NSDate ?? NSDate()
if let author = json["author"] as? NSDictionary {
if let authorId = author["_id"] as? String {
self.author = User(forPrimaryKey: authorId)
}
}
super.init()
}
In this case, my example JSON:
{
"_id": "2",
"text": "new message",
"author": {"_id": "1"}
}
And finally, the code that reads the JSON and adds to an already saved thread:
var error: NSError?
var jsonDictionary : NSDictionary!
if let dictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.allZeros, error: &error) as? NSDictionary {
jsonDictionary = dictionary
} else {
NSLog("\(error)")
return
}
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
let newMessage = Message(json: jsonDictionary)
self.thread.conversation.addObject(newMessage)
realm.addOrUpdateObject(self.thread)
realm.commitWriteTransaction()

Resources