How to get unique value from Realm database in swift - swift

I do news application in swift using Realm database. In my database have same news categories. How to get unique value from Realm database?
I use primary key
class News: Object {
dynamic var newsID: String = ""
dynamic var newsTitle: String = ""
dynamic var newsFullText: String = ""
dynamic var newsImage: String = ""
dynamic var newsAutor: String = ""
dynamic var newsCommentCount: String = ""
dynamic var newsSeenCount: String = ""
dynamic var newsDate: String = ""
dynamic var newsCategory: String = ""
override static func primaryKey() -> String? {
return "newsID"
}
}
I'm try to get
let realm = try! Realm()
let menuName = realm.objects(News)
for i in menuName.filter("newsCategory") {
nameLabel.text = i.newsCategory
}
But it is not work.

Starting from Realm 3.10 it's now possible to
Add Results.distinct(by:) / -[RLMResults
distinctResultsUsingKeyPaths:], which return a Results containing only
objects with unique values at the given key paths.
Old response - before Realm 3.10
It is not possible yet to obtain a "distinct"-like functonality from a Realm query (track the open issue here)
However, there are some workarounds suggested in the thread I mentioned above (please read it to get the full context), by user apocolipse :
// Query all users
let allUsers = Realm().objects(User)
// Map out the user types
let allTypes = map(allUsers) { $0.type }
// Fun part: start with empty array [], add in element to reduced array if its not already in, else add empty array
let distinctTypes = reduce(allTypes, []) { $0 + (!contains($0, $1) ? [$1] : [] )
Or better yet, a different approach using Sets (by user jpsim):
let distinctTypes = Set(Realm().objects(User).valueForKey("type") as! [String])
Obviously the workarounds aren't as efficient as a direct DB query, so use with care (and testing under realistic load).

Related

Realm Swift Migration: String Wrapper List to String List

I am trying to migrate our Realm Swift schema from using a String wrapper class to the primitive String for collections. I am running into issues extracting the string values from the wrapper during migration.
Here is the wrapper class:
class StringDB: Object {
#objc var stringVal: String = ""
convenience init(string: String) {
self.init()
stringVal = string
}
}
Then I have an example class with a List<StringDB> property:
class MyObjDB: Object {
var emails: List<StringDB> = List<StringDB>()
#objc dynamic var id: String = UUID().uuidString
convenience init(_ emails: [StringDB]) {
self.init()
for email in emails {
self.emails.append(objectsIn: emails)
}
}
override static func primaryKey() -> String? {
return "id"
}
}
that I want to convert to a List<String>.
Here is my migration code.
let config = Realm.Configuration(schemaVersion: latestSchemaVersion, migrationBlock: {
migration, version in
if version < 2 {
migration.enumerateObjects(ofType: MyObjDB.className(), { old, new in
guard let old = old, let new = new else { return }
let oldEmails = old["emails"] as! List<StringDB>
let newEmails = List<String>()
for email in oldEmails {
newEmails.append(email.stringVal)
}
new["emails"] = newEmails
})
}
})
However, the let oldEmails = old["emails"] as! List<StringDB> cast fails. I've tried also casting the collection to List<MigrationObject> and then casting the individual objects to StringDB but that cast fails as well.
I've found a workaround that may be satisfactory (I haven't confirmed yet), of converting the MigrationObject directly to string using coercion like "\(email)" and then running a regEx that will extract a desired substring from the garbage (StringDB {\n\tstringVal = email#address.com;\n}), but I have no idea yet whether that will hold up in production, and I would prefer to work with something resembling a recommended way for doing this migration.
The Realm version is not shown in the question and there are a few typo's in the code. For older Realm versions, Lists should be defined thusly:
let emails = RealmSwift.List<StringDB>() //need RealmSwift. to differentiate it from Swift List
then newer versions should be this:
#Persisted var emails = RealmSwift.List<StringDB>()
Then this is a problem as it iterates over the emails array count times and appends the entire emails list over and over for every email in that list.
for email in emails {
self.emails.append(objectsIn: emails)
}
Also, when using older Realm versons with #Objc property types, they need to include dynamic. So on the StringDB object this
#objc var stringVal: String = ""
should be this
#objc dynamic var stringVal: String = ""
Lastly, the MyObjDB needs to have somewhere to put the new email list. You can't overwrite the old one as it's not the correct type. So add a property
class MyObjDB: Object {
var emails: List<StringDB> = List<StringDB>()
let updatedEmailList = List<String>()
Then to the question: See comments in code for the flow
How about this:
migration.enumerateObjects(ofType: MyObjDB.className()) { oldItem, newItem in
//instantiate a List of the old email objects as migration objects
let oldEmailList = oldItem!["emails"] as! List<MigrationObject>
//we're going to populate a new List with the email strings
var newEmailList = List<String>()
//iterate over the old list, extracting the string from the old object as
// a string an inject it into a new List of Strings
for oldEmailObject in oldEmailList {
let oldEmail = oldEmailObject["stringVal"] as! String
newEmailList.append(oldEmail)
}
//assign the new List of strings to a new emails property
newItem!["updatedEmailList"] = newEmailList
}

Error in Realm invalid property name when I want to filter data, property not found

I get the error *** Terminating app due to uncaught exception 'Invalid property name', reason: 'Property 'read' not found in object of type 'Book'' when I want to filter my data. My structure:
class Book: Object, Codable {
#objc dynamic var author = ""
#objc dynamic var title = ""
#objc dynamic var imageLink = ""
#objc dynamic var category = "Lk"
#objc dynamic var date = Date()
convenience init(withBookDict: [String: Any]) {
self.init()
self.author = withBookDict["author"] as? String ?? "No Author"
self.title = withBookDict["title"] as? String ?? "No Title"
self.imageLink = withBookDict["imageLink"] as? String ?? "No link"
self.category = withBookDict["category"] as? String ?? "No category"
}
}
my code for filtering data is this:
let filteredread = realm.objects(Book.self).filter({ $0.category == "read"})
but I also tried this:
let filteredread = realm.objects(Book.self).filter("category == 'read'")
also I did update my realm pod since there have been version issues.
There's actually nothing wrong with your code, other than a "typo" - but there's other stuff to consider.
Fixing the typo
let filteredread = realm.objects(Book.self).filter({ $0.category == "read"})
should be
let filteredread = realm.objects(Book.self).filter { $0.category == "read"}
note removing the () from the filter function (which is actually a Swift .filter). This is more for clarity than anything as using swift functions on Realm objects can cause issues.
That query will first read all of the Book objects, then perform a Swift filter on the results and return LazyFilterSequence<Results> object. What that means is the results are disconnected from Realm and will not auto-update, which is one of the key features of realm.
If you want to have the Results update, do not use Swift functions; use Realm functions only
let filteredread = realm.objects(Book.self).filter("category == 'read'")
which returns a Results, which will auto-update as the underlying data changes.
Note: that function matches what's in your question and is perfectly legitimate code and would not throw any errors.
The way to filter Realm results according to a string property is :
let filteredResult = realm.objects(Model.self).filter("propertyName == %#", "value")
In your case it would be:
let filteredread = realm.objects(Book.self).filter("category == %#", "read")

Generic Type array with UITableView and Realm

I am using realm for database. I have Favourite Object and History Object.
I want to show in TableViewController. However, I don't want to do duplicated code. For now, in FavouriteViewController , it has var favs: Results<OGFav>? and HistoryViewController, it has var history: Results<OGHistory>?
Most of the code are the same and different is data type.
Example: it only different like following
if let object:OGFav = self.favs?[indexPath.row] {
In some place , I use like
let fav:OGFav = favs[indexPath.row]
For History
if let object:OGHistory = self.history?[indexPath.row] {
History also use like below
let history:OGHistory = self.history[indexPath.row]
How can I clean the code ? I am using two viewcontroller and code are the same excepted OGFav and OGHistory.
Update:
OGFav and OGHistory have the same data.
class OGFav: Object {
dynamic var word = ""
dynamic var def = ""
}
class OGHistory: Object {
dynamic var word = ""
dynamic var def = ""
dynamic var updatedAt = NSDate()
}

Filtering Realm objects with Swift

I always get the following error when trying to filter my Realm database using NSPredicate:
Property 'text' is not a link in object of type 'getType'
I want to filter my Realm database to show only the items that have some specific text in them. This is what I've tried:
let realm = try! Realm()
let predicate = NSPredicate(format: "typez.text.filter = 'special'")
let filterThis = realm.objects(Publication).filter(predicate)
print(filterThis)
The relevant portion of my model classes is:
class Publication: Object, Mappable {
dynamic var id: Int = 0
var typez = List<getType>()
dynamic var url: String?
}
class getType: Object, Mappable {
dynamic var text: String = ""
}
You mentioned that the relevant portions of you model classes look like so:
class Publication: Object, Mappable {
dynamic var id: Int = 0
var typez = List<getType>()
dynamic var url: String?
}
class getType: Object, Mappable {
dynamic var text: String = ""
}
If I understand you correctly, you want to find Publication instances that have an entry in their typez list with text equal to special. You can express that as:
let realm = try! Realm()
let result = realm.objects(Publication).filter("ANY typez.text = 'special'")
print(result)
I was not liking the accepted answer here because it doesn't actually answer the question... but then it helped me more than I realized. I will now be using closures instead of NSPredicates whenever possible. The actual answer to this question should be a slightly modified version of #NSGangster's answer:
let realm = try! Realm()
//Array of publications
let realmObjects = realm.objects(Publication)
//any publication where .text property == special will be filtered. and filter out empty array
let filterThis = realmObjects.filter({ $0.typez.filter({ $0.text == "special" } != [] ) })
print(filterThis)
.. or something close to that.
But what I was looking for was a bit different. I needed a way to filter on exact words of a multi-word string, and using an NSPredicate with "CONTAINS" would match any containing substring, e.g. a search for "red" would match "fred". Realm doesn't support "LIKE" or regex yet, so using a closure was the only thing I could get to work:
//I was going for a "related terms" result for a dictionary app
let theResults = terms.filter(
{
//Looking for other terms in my collection that contained the
//title of the current term in their definition or more_info strings
$0.definition.components(separatedBy: " ").contains(term.title) ||
$0.more_info.components(separatedBy: " ").contains(term.title)
}
)
With as much of the day as I spent searching, hopefully this helps someone else with a similar issue.
I don't usually use NSPredicate's directly, instead I do an inline predicate closure within the filter paramter.
let realm = try! Realm()
//Array of publications
let realmObjects = realm.objects(Publication)
//any publication where .text property == special will be filtered. and filter out empty array
let filterThis = realmObjects.filter({ $0.getType.filter({ $0.text == "special" } != [] ) })
print(filterThis)

Dynamic Struct creation in Swift - based on user input

I am trying to create dynamic struct in swift based on user input.
struct Diagnosis {
var diagName = String() // Name of the diagnosis
var diagSymptoms = [String]() // Symptoms of the diagnosis in a ranked array to identify prevelancy
var diagSpecialization = [String]() // the specializations which would mostly encounter this diagnosis
var diagRank = Int() // the overall rank of the diagnosis
var diagSynonoms = [String]() // the other name thru which the same diagnosis is called / referred.
// func init(diagName: String(),diagSymptoms: [String](),diagSpecialization: [String](),diagSynonoms: [String]())
init( let pasdiagName: String,let pasdiagSymptoms:Array<String>) {
self.diagName = pasdiagName
self.diagSymptoms = pasdiagSymptoms
}
}
var maleria = Diagnosis(pasdiagName: "Maleria",pasdiagSymptoms: ["fever","chill","body pain"])
The above creates the structure maleria - But in future I want to have the input from user and create a structure for that inputted string
var abc = "typhoid"
let valueof(abc) = Diagnosis()
The value of function is something I just put here arbitrarily to make my explanation clear.
I know I could do this in python and I am new to swift. Thanks in advance for the help.
As #Wain suggested, you should use a Dictionary.
This is how you create a mutable dictionary where the key is a String and the value can be any type.
var dict = [String:Any]()
This is how you put key/value pairs into the dictionary
dict["year"] = 2016
dict["word"] = "hello"
dict["words"] = ["hello", "world"]
And this is how you extract a value and use it
if let word = dict["word"] as? String {
print(word) // prints "hello"
}