How to address key of retrieved object, in Realm, Swift? - swift

I have created DB for my iOS mobile app using Realm, writing with Swift.
I am trying to find way to look up for matching username and password in DB
This is what I have currently, Attempting filtering and get an object with matching username
I am trying to address attribute/key called password from retrieved object
#IBAction func SignInCheck(sender: AnyObject) {
let realm = try! Realm()
var currentlogin = realm.objects(UserRecords).filter("name = LogUsernameTextField.text")
//this line causes SIGABRT to be thrown
if currentlogin.password == LogPasswordTextField.text { //This is the incorrectly addressed line
....
}
}
The issue maybe that I am not understanding how objects work in the right way, nor knowing the right syntax to address what I want.
I suspect it is in form of Result but I am still unable to find way to address desired information.
Here is the table structure for your information
class UserRecords: Object {
dynamic var username: String = ""
dynamic var password: String = ""
dynamic var latitude: Float = 0
dynamic var longtitude: Float = 0
}
I am open for suggestions better solution and ways of looking up/matching passwords in the table too, if any.
Thanks in advance

You are using a property called name in your filter string but in your UserRecords class the property is called username. Also you have to create the filter string differently:
var currentlogin = realm.objects(UserRecords).filter("username = '\(LogUsernameTextField.text!)'")
Also be aware that the filter method returns a list of UserRecord objects that match the filter and not a single object. So calling if currentlogin.password == ... will cause an error.
The list only has 1 item (because the username is unique) but it is still a list. So to access the UserRecord object you can call first:
var currentlogin = realm.objects(UserRecords).filter("name = LogUsernameTextField.text!").first
Also the text property of UITextField returns an Optional, so you have to unwrap it.

Related

Query MongoDB Realm array only at first index

I need to query MongoDB Realm from synced iOS and Android app. In Swift I can write something like this:
let dictionary = realm?.objects(myReamlObject.self)
let results = dictionary?.where {
$0.senses.glosses.term == "the term I want"
}
or using predicate:
let results = dictionary?.filter("ANY senses.glosses.term == %#", "the term I want")
Both work well, but I don't want to check ALL senses.glosses.term.
Every entry has (or could have) many senses and many glosses.
I would like to check term of first senses in first glosses only.
Something I would write like this:
let results = dictionary?.where {
$0.senses[0].glosses[0].term == "the term I want"
}
But it gives error:
Referencing subscript 'subscript(_:)' on 'Query' requires that
'List<myRealmObject_senses>' conform to 'RealmKeyedCollection'
Any suggestion on how to query only first index of an array in MongoDB Realm? Thank you
Let me re-state the question
How to query a Realm objects' List property - but only query on the first
element in the List.
The answer is going to depend on the amount of results being worked with.
Here's how to do it in a way that's O.K. for small datasets but NOT RECOMMENDED
Your models were not included in the question so let me use a simplified model of a PersonClass that as a List of DogClass objects
class PersonClass: Object {
#Persisted var name = ""
#Persisted var dogList = List<DogClass>()
}
class DogClass: Object {
#Persisted var name = ""
}
The idea here is to use Swift high-level functions to only test the first item in each persons doglist for a match (this can be applied to other languages as well)
//get all the people
let peopleResults = realm.objects(PersonClass.self)
//use the high-level Swift function compactMap to return all of
// the people whose first dog is named "Spot"
let persons = peopleResults.compactMap { person -> PersonClass? in
if person.dogList.first?.name == "Spot" {
return person
}
return nil
}
The downside is that this code overrides a fundamental advantage of Realm - that Realm objects are lazily loaded if Realm functions are used e.g. as soon as a high-level Swift function is used, ALL of the objects are loaded into memory, potentially overwhelming the device.
A better option is to simply add a managed property to the PersonClass that also points to the 0'th element in the list.
class PersonClass: Object {
#Persisted var name = ""
#Persisted var dogList = List<DogClass>()
#Persisted var mainDog: DogClass?
func addMainDog(withDog: DogClass) {
self.dogList.append(withDog)
self.mainDog = withDog
}
}
as you can see, there's also a function to add that first dog to the list and also populates the mainDog property which points to the same object. It's one property so the overall impact is very low but the advantages for simple queries are very high.
From there the query becomes trivial
let peopleResults = realm.objects(PersonClass.self).where { $0.mainDog.name == "Spot" }
Expanding on this, you could save the 0th element of each List object in the Parent object or even have a property in each child object that points to the first element in it's respective list.

initialise Codable result with var

I need to initialise the result of JSONDecoder in a var object defined outside the API Call.
apiService.GETAPI(url: apiStr, completion: {(success, result) in
if(success) {
let apiResponse = result.value as! NSDictionary
let data = apiResponse.value(forKey: "data") as! NSDictionary
do {
let profileData = try JSONSerialization.data(withJSONObject: data.value(forKey: "profile"), options: .prettyPrinted)
print(profileData)
let profile = try JSONDecoder().decode(Profile.self, from: profileData)
print(profile.name)
}
catch {
print("json error: \(error.localizedDescription)")
}
}
completion(success)
})
But I am unable to do so. This is my Profile Codable struct
struct Profile : Codable {
var id : Int
var name : String
var member_id : Int
var category_id : Int
var membership_id : Int
var information : String
var city : String
var love_count : Int
var vendor_price : String
var locality_name : String
var phone : [String]
var address : [Address]?
var status : Int?
var managed_by_wmg : Int?
}
How to do it. I need it to be var since I need to perform operation and access it later in the other code.
As we have already discussed in the comments of your question, you need to declare variable of Profile type outside the closure.
So now the problem become "How to change Profile struct, so I can declare variable of it?"
For this you have several options. I will list them and comment on which to choose and why.
Option 1
Add default values to all the variables of Profile struct as so: var id : Int = 0, so you can declare it later with var profile: Profile?. With this solutions you need to have some knowledge about the objects you are expecting and their default values in a way they make sense to your business logic. Example for such default values are "" for string or 0 integer. This might seem good but yet their is better solution.
Option 2
Make ALL the variables of Profile optional as so: var id : Int?. This might sounds strange at first, but is the optimal solution for working with server data. So this method has several benefits:
It will never crash, no matter what the server sends you as a data
All the variables have default values equaling nil
You do not need to think about your business logic and what default value suits your needs
However, with this method there is one drawback: some properties that with your logic could never be nil will have nil initial value. So you need to add validations for unwrapping the nullable properties.
Option 3 Add explicit unwrapping to the variable type as so var id : Int!. Note this also adds nil as initial value to your properties, but tells the compiler that in every time you are gonna use them, they will not be nil. I would personally not recommend this method, because if the variable is not received from the server, or the parsing fails, or something else unexpected happens, your app will crash with found nil while unwrapping optional value. For reference you might have noticed all the IBOutlets are defined this way.
Having said all that, I would recommend you going with Option 2, which best suits server communication, but the choice is yours!
Happy coding.
I have found two solutions to my question. Thanks to #dvp.petrov.
Solution 1 var profile : Profile! comes handy but it becomes difficult if this is not an optional variable and have to be used at many places. One will have to put lot of checks and unwrap it every time. This is easy to use when one has to use it at very less places
Solution 2 : Create an init function giving default values to all the variable and then you can create var profile : Profile(). This way your object will not be nil and will be able to use it at places.
struct Profile : Codable {
var id : Int
var name : String
var member_id : Int
init() {
self.id = 0
self.name = ""
self.member_id = 0
}

Realm order object by date inside of a list of dates

So I have the following objects
class Chat: Object {
dynamic var chatID = ""
var participants = List<Friend>()
var messages = List<Message>()
/// Set the primary key
override static func primaryKey() -> String? {
return "chatID"
}
}
class Message: Object {
dynamic var chat: Chat!
dynamic var from: Friend!
dynamic var message = ""
dynamic var date = Date()
dynamic var isRead: Bool = false
}
Now I obtain a list of all the chats that have been created. When I have the chats I want to be able to order them based on the last message. So what I need to do is order de list of chats by the date in the list of messages.
The chat which contains the message with the newest date needs to be on at the top and so on.
I've tried to order the list as follows
realmManager.chatResults.sorted(byKeyPath: "messages.date", ascending: false) but that throws the following error
Terminating app due to uncaught exception 'Invalid key path for sort',
reason: 'Cannot sort on 'messages.date': sorting on key paths that
include a to-many relationship is not supported.'
I currently can't figure out how I would go about fixing this. Does someone know how I could achieve the correct sort behaviour?
You are trying to access a property of a single instance on a list, in particular you are trying to sort a List<Message> collection based on a property of a single Message instance.
If you want to sort Chat instances, first you have to sort Chat.messages and then sort by the last message. To mitigate that error you could use Swift's built in sorted function together with that of Realm.
let sortedChats = realm.objects(Chat.self).sorted{
guard let currentLatestDate = $0.messages.sorted(byKeyPath: "date",ascending: false).first?.date, let nextLatestDate = $1.messages.sorted(byKeyPath: "date",ascending: false).first?.date else {return false}
return currentLatestDate > nextLatestDate
}
The code has been tested in a playground and returns the Chat instances of your Realm sorted by the date of their latest Message in descending order.

.prepare vs. .select

I have a working connection to a database in an iOS10 app, using SQLite.swift.
I want to select details for a specific university where I have an IHE_ID passed in from another view controller.
I would like to just select the row for that specific university, but I can't get a query to work. I can however loop through all the data with a prepare and then choose the one I need from that, which of course is more resource intensive than I need since I already have the specific IHE_ID passed in as anIHE Int from the sending view controller.
connection is working so omitting that code...
do {
let db = try Connection(destinationDBPath, readonly: true)
let IHEs = Table("IHE")
let IHE_ID = Expression<Int>("IHE_ID")
let IHE_Name = Expression<String>("IHE_Name")
let IHE_COE_Url = Expression<String>("IHE_COE_URL")
let IHE_Sector = Expression<Int>("IHE_Sector")
let IHE_COE_Name = Expression<String>("IHE_COE_Name")
for ihe in try db.prepare(IHEs){
if (ihe[IHE_ID] == anIHE){
// build this string, otherwise ignore rest of dataset (doing this because query isn't working)
buildIHE = "Name: \(ihe[IHE_Name])\n"
buildIHE.append("URL: \(ihe[IHE_COE_Url])\n")
// buildIHE.append("Sector: \(ihe[IHE_Sector])\n")
if (ihe[IHE_Sector] == 0) {
buildIHE.append("Sector: Public\n")
} else {
buildIHE.append("Sector: Private\n")
}
buildIHE.append("College of Education Name: \(ihe[IHE_COE_Name])\n")
}
}
print ("Got through building IHE portion of view")
What I'd like to do is use this instead of the for loop.
if let query = IHEs.select(IHE_ID,IHE_Name,IHE_COE_Url,IHE_Sector,IHE_COE_Name).filter(IHE_ID == anIHE){
print("Query successful for \(anIHE) with name \(query[IHE_Name])")
// more actions to build the string I need would then occur
} else {
print("Query has failed or returned nil")
}
Finally, I'll use the selected elements if I can get the query to work.
I think I probably just have something wrong with my syntax on the query, but any help is appreciated.
The line with the "if let query" has this error in Xcode:
Initializer for conditional binding must have Optional type, not 'Table'.
This leads me to think it's something with my use of the .select statement and just new to using SQLite.swift and swift in general.
Last thing is that anIHE comes into this function as an Int, and IHE_ID is Expression as shown in this code. I'm thinking this may be part of the problem.
The Initializer for conditional binding must have Optional type error means that the expression on the right of the if let v = expr statement is not an Optional: there is no point using if let, and the Swift compiler says that you should just write let v = expr.
And indeed, IHEs.select(...).filter(...) returns a non-optional value of type Table.
It is not the database row you expect, because the query has been defined, but not executed yet. After all, you weren't using db: where would the rows be loaded from?
The solution is to bring back the database connection, and load a single row. This is done with the pluck method.
if let ihe = try db.pluck(IHEs.select(...).filter(...)) {
print("Name \(ihe[IHE_Name])")
}

Error when I want to access a row via the indexPath in a tableViewController

I am making a chat app. I am having a problem while accessing an array of objects via indexPath(row) of a tableViewController. It is giving me an error: " Cannot subscript a value of type '[AnyObject]?'" The code is below. How do I fix this?
var user1 = PFUser.currentUser()
var user2 = self.objects[indexPath.row] as! PFUser
You may change the declaration of your self.objects to:
var objects = Array<PFUser>()
and use it like:
let user = self.objects[indexPath.row] as BusinessLocation!
Since you have specified the type, it won't consider it as AnyObject.