First time i use Realm, i use it in a Swift Project. I use the last version of Realm 0.96.3.
I got two Realm Models: Jog and Marker
class RealmCurrentRecJog: Object {
dynamic var id: Int = 0
dynamic var duration: Double = 0.0
let markers = List<RealmCurrentRecMarker>()
// Specify properties to ignore (Realm won't persist these)
override static func primaryKey() -> String? {
return "id"
}
}
class RealmCurrentRecMarker: Object {
dynamic var order: Int = 0
dynamic var latitude: Double = 0.0
dynamic var longitude: Double = 0.0
dynamic var speed: Double = 0.0
dynamic var accuracy: Double = 0.0
dynamic var altitude: Double = 0.0
dynamic var time: Double = 0.0
}
As you can see in the Jog model i got a <List> of Markers and id is the primary key. In the Marker model nothing crazy.
So i fill my Realm Models with this function:
private func fillDbWithMarker() {
let saveJog = RealmCurrentRecJog()
let saveMarker = RealmCurrentRecMarker()
let jogToSave = self.jogRecorder.getDraftJog()
saveJog.id = Int(jogToSave.id)
saveJog.duration = jogToSave.duration
saveMarker.order = Int((jogToSave.markers.last?.order)!)
saveMarker.longitude = (jogToSave.markers.last?.longitude)!
saveMarker.latitude = (jogToSave.markers.last?.latitude)!
saveMarker.accuracy = (jogToSave.markers.last?.accuracy)!
saveMarker.altitude = (jogToSave.markers.last?.altitude)!
saveMarker.speed = (jogToSave.markers.last?.speed)!
saveMarker.time = (jogToSave.markers.last?.time)!
do {
let realm = try Realm()
do {
try! realm.write {
saveJog.markers.append(saveMarker)
realm.add(saveJog, update: true)
}
}
} catch let error as NSError {
print(error)
}
}
And at the end when i look at my Realm Browser the result is that i just got the last marker in my list even if my Marker table is full.
I don't understand where is my mistake .
Edit: Here is the code to make it work
do {
let realm = try Realm()
do {
try! realm.write {
saveJog = realm.objects(RealmCurrentRecJog).last!
saveJog.markers.append(saveMarker)
realm.add(saveJog, update: true)
}
}
} catch let error as NSError {
print(error)
}
fillDbWithMarker creates a Jog whose markers list contains a single marker. When it's saved with Realm.add(_:update:), it updates the existing Jog with the same primary key so that its properties match the passed-in object's properties. This results in the existing Jog object being updated to contain only the single new marker.
If you tweak fillDbWithMarker to retrieve the existing Jog with the given ID and append the marker to its markers list you'll get the behavior you're after.
Related
in realm i given id = 0 and it is as primary key and it will be auto increment, but problem is while updating it is saving in the index path 0 as declare as id : Int = 0.
Where ever i update also it is only updating in 0th index only.
i want to update as per selected object.
What to do?
Program :-
class Discount: Object {
#objc dynamic var id : Int = 0
#objc dynamic var offerName : String = ""
#objc dynamic var percentage: Float = 0.00
#objc dynamic var segmentIndex : Int = 0
#objc dynamic var dateWise: Date?
override class func primaryKey() -> String? {
return "id"
}
//Incrementa ID
func IncrementaID() -> Int{
let realm = try! Realm()
if let retNext = realm.objects(Discount.self).sorted(byKeyPath: "id").last?.id {
return retNext + 1
}else{
return 1
}
}
}
Generally speaking, auto-incrementing primary keys are challenging to deal with and can cause headaches long term.
What's generally most important is ensuring primary keys are unique and using UUID strings is ideally suited for that.
class Discount: Object {
#objc dynamic var discount_id = UUID().uuidString
override static func primaryKey() -> String? {
return "discount_id"
}
}
There may be concern about ordering and often times that managed by either adding a class var to determine ordering; like a timestamp for example or if you want to preserve ordering, objects can be added to a List, which keeps the order, like an array.
To answer your specific question, the code in your question is not complete (it was partially pulled from another question). The reason is that for each object that's created, it must be written to realm first, then the next object's primary key is based on the prior object.
Here's an example.
#objcMembers class User: Object {
dynamic var uid: Int = 0
dynamic var username: String?
func getNextUid() -> Int {
let realm = try! Realm()
if let lastObject = realm.objects(User.self).sorted(byKeyPath: "uid").first {
let lastUid = lastObject.uid
let nextUid = lastUid + 1
return nextUid
}
return 1
}
override static func primaryKey() -> String? {
return "uid"
}
}
now the sequence to use this is as follows
let u0 = User()
u0.uid = u0.getNextUid()
u0.username = "User 0"
let realm = try! Realm()
try! realm.write {
realm.add(u0)
}
let u1 = User()
u1.uid = u1.getNextUid()
u1.username = "User 1"
try! realm.write {
realm.add(u1)
}
as you can see, each object needs to be written to realm in order to the next object to be queried to get the prior objects primary key.
It's a whole lot of potentially unnecessary work and code.
My advice: Stick with the UUID().uuidString for primary keys.
How can I filter out the RealmFilter.objectIds that has a given Int?
func delete(ids: [Int]) {
let filterResultsToDelete = realm.objects(CRMRealmFilterResult.self).filter("ANY objectIds IN %#",ids)
//Crashes
}
class RealmFilterResult : Object {
#objc dynamic var filterId: Int = 0
let objectIds = List<Int>()
override static func primaryKey() -> String {
return "filterId"
}
}
This may not be at all what you want but it was a good exercise. Maybe this will help.
Let me re-state what I think you're asking: You've got a series of objects that each have a List property of Int's and you want to be able to query for all objects that have a particular int in their list
Using a more real-world example, suppose we have a list of teams and we keep a list of game scores (a list) within each team
class TeamObject: Object {
#objc dynamic var object_id = NSUUID().uuidString
let scoreList = List<ScoreObject>()
override static func primaryKey() -> String? {
return "object_id"
}
}
and we have a score object that stores a score as an Int (and maybe other details like who they played or the date)
class ScoreObject: Object {
#objc dynamic var score = 0
let teamsWithScores = LinkingObjects(fromType: TeamObject.self, property: "scoreList")
}
For simplicity, let's create three scores and two teams and give each team two scores in their list.
let score1 = ScoreObject()
score1.score = 1
let score2 = ScoreObject()
score2.score = 2
let score3 = ScoreObject()
score3.score = 3
let t1 = TeamObject()
t1.scoreList.append(score1)
t1.scoreList.append(score3)
let t2 = TeamObject()
t2.scoreList.append(score2)
t2.scoreList.append(score3)
and write them to realm
try! realm.write {
realm.add(t1)
realm.add(t2)
}
from there, we can get any team that has a score of 1, which solves the question of getting the objects that have a list that contain a given int.
let results = realm.objects(ScoreObject.self).filter("score IN %#", [1])
if results.count > 0 {
for aScore in results {
let teamsWithThisScore = aScore.teamsWithScores
for team in teamsWithThisScore {
print("score: \(aScore.score)")
print(" id: \(team.object_id)")
}
}
} else {
print("no teams with those scores")
}
you can expand on this to get teams (object) that have several scores (ints)
let results = realm.objects(ScoreObject.self).filter("score IN %#", [1,3])
As I said, it may be off base but it does provide a solution in a more object oriented way.
Querying List of primitives (i.e. non Object subclasses, like Int in your case) is not yet supported by Realm.
You can follow the status of this on this GitHub issue.
I am trying to read from Firestore into a Dictionary[Any] type using Struct. I can get the values loaded into variable "data" dictionary with Any type.
However I cannot loop thru it to access normal nested Dictionary variable.
I cannot get Key, values printed.
Following is my code:
class PullQuestions {
//shared instance variable
**public var data = [Any]()**
private var qdb = Firestore.firestore()
public struct questionid
{
let qid : String
var questions : [basequestion]
var answers: [baseans]
}
public struct basequestion {
let category : String
let question : String
}
public struct baseans {
let answer : String
}
class var sharedManager: PullQuestions {
struct Static {
static let instance = PullQuestions()
}
return Static.instance
}
static func getData(completion: #escaping (_ result: [Any]) -> Void) {
let rootCollection = PullQuestions.sharedManager.qdb.collection("questions")
//var data = [Any]()
rootCollection.order(by: "upvote", descending: false).getDocuments(completion: {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
guard let topSnapshot = querySnapshot?.documents else { return }
// var questiondoc = [basequestion]()
for questioncollection in topSnapshot {
rootCollection.document(questioncollection.documentID).collection("answers").getDocuments(completion: {
(snapshot, err) in
guard let snapshot = snapshot?.documents else { return }
var answers = [baseans]()
for document in snapshot { //There should be only one Document for each answer collection
//Read thru all fields
for i in 0..<document.data().count
{
let newAns = baseans(answer: answer)
print("Answer Docs=>", (answer))
answers.append(newAns)
}
}
let qid = questioncollection.documentID
let category = questioncollection.data()["category"] as! String
let question = questioncollection.data()["question"] as! String
let newQuestions = basequestion(category: category ,question: question)
let newQuestionDict = questionid(qid: qid, questions: [newQuestions], answers: answers)
PullQuestions.sharedManager.data.append(newQuestionDict)
//Return data on completion
completion(PullQuestions.sharedManager.data)
})
}
}
})
}
}
I can print like this
print("Count =>", (PullQuestions.sharedManager.data.count))
// print(PullQuestions.sharedManager.data.first ?? "Nil")
print(PullQuestions.sharedManager.data[0])
for element in PullQuestions.sharedManager.data
{
print("Elements in data:=>", (element))
}
I could access only the key.. how do i go and get the nested values ?
First of all, consider using Swift code conventions (e.g. your structs are named with small letters, but you should start with capital), this will make your code more readable.
Returning to your question. You use an array instead of dictionary (this piece of code: public var data = [Any]()). And here you are trying to print values:
for element in PullQuestions.sharedManager.data
{
print("Elements in data:=>", (element))
}
In this context element is an Any object, thus you cannot access any underlying properties. In order to do this you have two options:
1. You should specify the type of array's objects in it's declaration like this:
public var data = [questionid]()
or you can user this:
public var data: [questionid] = []
These two are equals, use the one you prefer.
2. If for any reasons you don't want to specify the type in declaration, you can cast it in your loop. Like this:
for element in PullQuestions.sharedManager.data
{
if let element = element as? quetionid {
print("Elements in data:=>", (element))
// you can also print element.qid, element.questions, element.answers
} else {
print("Element is not questionid")
}
}
You could of course use the force cast:
let element = element as! questionid
and avoid if let syntax (or guard let if you prefer), but I wouldn't recommend this, because it (potentially) can crash your app if element will be nil or any other type.
I am in the process of creating an application that will display a list of stocks that a user saves in a tableView. It will also allow the user to add or remove items from their favorites. I need help defining the database structure and setting up for the adding and constant updating of the user's favorite stocks.
I currently have a StockData struct that works with my tableView and a button for adding to the user's list:
struct StockData {
let currentPrice: Double
// meta data
let name: String
let ticker: String
let interval: String
let lastRefreshed: String
let change: Double
}
// In an actual ViewController
#IBAction func addButtonClicked(_ sender: Any) {
print("Add clicked")
// Handle adding the item to the user's list
}
Now as far as my current realm model is concerned, I have:
class User: Object {
#objc dynamic var name = ""
#objc dynamic var id = ""
var stockFavs = List<StockItem>()
}
class StockItem: Object {
#objc dynamic var currentPrice: Double = 0.0
// meta data
#objc dynamic var name = ""
#objc dynamic var ticker = ""
#objc dynamic var interval = ""
#objc dynamic var lastRefreshed = ""
#objc dynamic var change: Double = 0.0
}
// Setting up the user and creating test values
let newUser = User()
newUser.name = "John Smith"
newUser.id = "coolId123"
var stockArr = List<StockItem>()
for i in 0..<12 {
let item = StockItem()
item.name = "Microsoft Corporation"
item.change = -3.55
item.currentPrice = 123.45
item.interval = "1day"
item.lastRefreshed = "Now"
item.ticker = "MSFT"
stockArr.append(item)
}
newUser.stockFavs = stockArr
try! realm.write {
realm.add(newUser)
}
So far, I have been able to create a user object for the current user of the device and add test values, but I am unsure how to implement constant realm updating (the method would have self.tableView.reloadData(), but apart from that, I'm clueless) in conjunction with the ability to add StockItem's to the user's array.
Any help would be much appreciated!
You use a function for every time you want to add to the database.
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 100)
button.addTarget(self, action: #selector(add), for: .touchUpInside)
func add() {
let currentData = [MyFakeData1, MyFakeData2, etc.]
try! realm.write {
realm.add(currentData)
}
// You need to get the updates in the database
// You could have an array in your model that you update and then append all
// the new items to it and only do database lookups when you restart the
// device
update()
}
func update() {
let realm = try! Realm()
var newArray = realm.objects(StockItem.self)
myViewController.modelArray = newArray
table.reloadData()
}
I have my Device class defined as such:
class Device: Object {
dynamic var asset_tag = ""
}
I have an array like this ["43", "24", "23", "64"]
and I would like to loop through the array and add each one into the asset_tag attribute of the Device entity in Realm.
To create an array in Realm you use List. According to Realm's docs, List is the container type in Realm used to define to-many relationships. It goes on to say, "Properties of List type defined on Object subclasses must be declared as let and cannot be dynamic." This means you need to define an entirely separate object to create a list in Realm, there are no native types that will allow you to do something like
let assetTagList = List<String>().
You need to create an AssetTags object and make a list of it in your Device object as follows:
class AssetTags: Object {
dynamic var stringValue = ""
}
class Device: Object {
var asset_tags: [String] {
get {
return _assetTags.map { $0.stringValue }
}
set {
_assetTags.removeAll()
_assetTags.append(objectsIn: newValue.map({ AssetTags(value: [$0]) }))
}
}
let _assetTags = List<AssetTags>()
override static func ignoredProperties() -> [String] {
return ["asset_tags"]
}
}
Now you can do this to add asset tags to Device.
//adding asset tags
let realm = try! Realm()
try! realm.write {
let device = Device()
device.asset_tags = ["1", "2"]
realm.add(device)
}
//looking at all device objects and asking for list of asset tags
for thisDevice in realm.objects(Device.self) {
print("\(thisDevice.asset_tags)")
}
SECOND EDIT
This is not the way I would do it but I think this is what you might find easier to understand.
class AssetTags: Object {
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
dynamic var tagValue = ""
}
class Device: Object {
let assetTags = List<AssetTags>()
}
So now Device has a list of asset tags. I added a primary id for Asset Tags because you indicated you might want to add more properties so an id should help make each one unique. It is optional though, so you can remove it if you do not need it.
To add assetTags objects to a Device object in a for-loop style (note: device in this code exampl is the device object you want to save the asset tags to):
var assetTagArray: [String] = ["1", "2"]
let realm = try! Realm()
for assetTag in assetTagArray {
let newAssetTag = AssetTags()
newAssetTag.tagValue = assetTag
newAssetTag.id = (realm.objects(AssetTags.self).max(ofProperty: "id") as Int? ?? 0) + 1
do {
try realm.write {
device?.assetTags.append(newAssetTag)
}
} catch let error as NSError {
print(error.localizedDescription)
return
}
}