Deleting Objects with subclasses in Swift Realm - swift

I have a really simple database in Swift Realm for a todo app:
Items and their parent Categories.
The user can delete both the Items and the Categories with a simple swipe action. The action works fine, there are no issues when deleting Items. If I delete a Category, that works too, but I can still see the Items in the Realm Browser, those remain in the database even though there are no parent anymore. Obviously the user can't see these, they are doing nothing but still, it would be better to get rid of these with the parent Category. Are there any simple ways to do this?
class Category: Object{
#objc dynamic var name: String = ""
#objc dynamic var color: String = ""
#objc dynamic var order = 0
let items = List<Item>()
override static func primaryKey() -> String? {
return "order"
}
static func incrementalIDCat() -> Int {
let realm = try! Realm()
return (realm.objects(Category.self).max(ofProperty: "order") as Int? ?? 0) + 1
}
}
class Item: Object {
#objc dynamic var title: String = ""
#objc dynamic var done: Bool = false
#objc dynamic var dateCreated: Date?
#objc dynamic var order = 0
var parentCategory = LinkingObjects(fromType: Category.self, property: "items")
override static func primaryKey() -> String? {
return "order"
}
static func incrementalIDItem() -> Int {
let realm = try! Realm()
return (realm.objects(Item.self).max(ofProperty: "order") as Int? ?? 0) + 1
}
}
override func updateModel(at indexPath: IndexPath) {
if let categoryForDeletion = self.categories?[indexPath.row] {
do {
try self.realm.write {
self.realm.delete(categoryForDeletion)
}
} catch {
print("Error deleting category, \(error)")
}
}
tableView.reloadData()
}

You just delete items first.
self.realm.delete(categoryForDeletion.items)
self.realm.delete(categoryForDeletion)
Or, with this extension, you can do this.
self.realm.delete(categoryForDeletion, cascading: true)

Related

saving array into realm database swift

I have an application which uses realm database to persist data and everything works fine but the issue I have now is I want to save photo: Data array to the database and I tried using the standard swift array format but got an error at build time. below is my code
class TodoListModel: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var photo: Data? = nil
#objc dynamic var createdDate: Date?
override static func primaryKey() -> String? {
return "id"
}
let parentCategory = LinkingObjects(fromType: CategoryModel.self, property: "items")
}
how do I now make the photo into an array
this is the way I create my List
func createTodoList(createdDate: Date, photo: Data) -> Void {
let todoList = TodoListModel()
todoList.createdDate = createdDate
todoList.photo = photo
TodoListFunctions.instance.addData(object: todoList)
}
update model
func updateTodoList(update: TodoListModel, createdDate: Date, photo: Array<Data>) -> Void {
update.createdDate = createdDate
update.photo.append(objectsIn: photo)
}
To be able to store several objects of type Data in a single property of an Object subclass, you need to use List.
class TodoListModel: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var createdDate: Date?
let photos = List<Data>()
let parentCategory = LinkingObjects(fromType: CategoryModel.self, property: "items")
override static func primaryKey() -> String? {
return "id"
}
}
Then you can create a TodoListModel instance like this:
func createTodoList(createdDate: Date, photos: Array<Data>? = nil) -> Void {
let todoList = TodoListModel()
todoList.createdDate = createdDate
if let photos = photos {
todoList.photos.append(objectsIn: photos)
}
TodoListFunctions.instance.addData(object: todoList)
}

Get notified when value in List is updated using realm

I am trying to write an application in OS X using a Realm database. I want to trigger notification when where there is change in value of List which is inside another object
Below is the Class
final class Profile: Object {
#objc dynamic var gradient1 = ""
#objc dynamic var gradient2 = ""
#objc dynamic var fontColor = ""
#objc dynamic var selectedFont = ""
#objc dynamic var selectedTitleFont = ""
#objc dynamic var fontFamily = ""
#objc dynamic var name = ""
#objc dynamic var shortbio = ""
#objc dynamic var avatarSource = ""
#objc dynamic var userid = ""
#objc dynamic var email = ""
var features = List<Features>()
var socialLinkButtons = List<SocialLinkButtons>()
#objc dynamic var appSelectedMetaData : AppMetaData? = nil
override static func primaryKey() -> String?{
return "userid"
}
}
final class Features: Object {
#objc dynamic var uuid = ""
#objc dynamic var id = ""
#objc dynamic var label = ""
#objc dynamic var screen = ""
#objc dynamic var active = false
override static func primaryKey() -> String?{
return "id"
}
convenience init(id: String, uuid: String, label: String, screen: String, active: Bool) {
self.init()
self.id = id
self.uuid = uuid
self.label = label
self.screen = screen
self.active = active
}
}
I want to trigger notifications whenever value inside feature is updated.
You can use Realm Collection Notifications to achieve your goals. You just need to make sure that you store the returned NotificationToken in a variable that doesn't get deallocated until you don't actually need to receive the notifications anymore and that you call .invalidate() on the token when you no longer want to receive notifications.
func observeFeatureChanges(in profile:Profile) -> NotificationToken {
let notificationToken = profile.features.observe { changes in
switch changes {
case .update(_, deletions: let deletionIndices, insertions: let insertionIndices, modifications: let modIndices):
print("Objects deleted from indices: \(deletionIndices)")
print("Objects inserted to indices: \(insertionIndices)")
print("Objects modified at indices: \(modIndices)")
case .error(let error):
print(error)
case .initial(let type):
print(type)
}
}
return notificationToken
}

Realm passing selected category values

I have a task app that I am working on. users can create task and in turn create items under their categories. I am using realm as my data storage but I want to be able to pass some details from the selected category to the create items viewcontroller. I decided to print the selected category to the console but it prints nil and I dont know why. Below is my code.
class CategoryModel: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name: String = ""
#objc dynamic var color: String = ""
#objc dynamic var isCompleted = false
let items = List<TodoListModel>()
override static func primaryKey() -> String? {
return "id"
}
}
Items VC
var selectedCategory: CategoryModel?
override func viewDidLoad() {
super.viewDidLoad()
print("Selected Category: \(String(describing: selectedCategory))")
}
I do not know why it keeps printing nil
Because you don't give it a value when you show the itemsVC
let item = ItemsVC() // if VC is inside IB use self.storyboard?.instantiate.....
item.selectedCategory = // set here or inside prepareForSegue if you use segues
// here present
You can parse in this way;
Items VC
let realm = try! Realm()
var category:Results< CategoryModel>?
override func viewDidLoad() {
super.viewDidLoad()
loadCategory()
}
func loadCategory(){
category = realm.objects(CategoryModel.self)
//tableView.reloadData() for example
}
//category?[indexPath.row].name for example using

Filter is returning all children for parent with matching child

I'm using Realm for Swift and I have a structure as follows:
class Navigation: Object {
dynamic var key = 0
dynamic var title: String?
let companies = List<Companies>()
override static func primaryKey() -> String? {
return "key"
}
}
class Companies: Object {
dynamic var key = 0
dynamic var name: String?
let locations = List<Locations>()
override static func primaryKey() -> String? {
return "key"
}
}
class Locations: Object {
dynamic var key = 0
...
dynamic var zip: String?
let contacts = List<Contacts>()
override static func primaryKey() -> String? {
return "key"
}
}
class Contacts: Object {
dynamic var key = 0
dynamic var firstName: String?
dynamic var lastName: String?
...
override static func primaryKey() -> String? {
return "key"
}
}
I'm trying to filter out locations by zip code, so that only locations that match the given zip code are displayed. I'm doing that like this
companies = realm.objects(Navigation.self).filter("key = 4").first!.companies.filter(predicate).sorted(byKeyPath: "key")
The key = 4 bit is because the filter is only supposed to search in companies under one specific category.
The problem that I'm having is that it returns all locations for a company that has a matching location. So if my zip to find is 12345, and companyA has a location that matches, all the locations under companyA are returned, even if they aren't a match.
How can I limit the results to be only locations with a match?
How can I limit the results to be only locations with a match?
Right now you're returning a Results<Companies>, but it seems like you want locations. I accomplished what I think you're looking for by adding some inverse relationship properties to the Companies and Locations models, and then queried the Realm for Locations matching zip == '12345' && ANY parentCompanies.parentNavigation.key == 4.
Here's a sample app that demonstrates this:
import UIKit
import RealmSwift
class Navigation: Object {
dynamic var key = 0
dynamic var title: String?
let companies = List<Companies>()
override static func primaryKey() -> String? {
return "key"
}
}
class Companies: Object {
dynamic var key = 0
dynamic var name: String?
let locations = List<Locations>()
let parentNavigation = LinkingObjects(fromType: Navigation.self, property: "companies")
override static func primaryKey() -> String? {
return "key"
}
}
class Locations: Object {
dynamic var key = 0
// ...
dynamic var zip: String?
let contacts = List<Contacts>()
let parentCompanies = LinkingObjects(fromType: Companies.self, property: "locations")
override static func primaryKey() -> String? {
return "key"
}
}
class Contacts: Object {
dynamic var key = 0
dynamic var firstName: String?
dynamic var lastName: String?
// ...
override static func primaryKey() -> String? {
return "key"
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
_ = try? FileManager.default.removeItem(at: Realm.Configuration.defaultConfiguration.fileURL!)
let realm = try! Realm()
// Objects Matching Query
try! realm.write {
// Locations
let locations = Locations()
locations.zip = "12345"
// Companies
let companies = Companies()
companies.name = "Companies A"
companies.locations.append(locations)
// Nav
let nav = Navigation()
nav.key = 4
nav.companies.append(companies)
// Add to Realm
realm.add(nav)
}
let locationsIn12345AndNavigationKey4 = realm.objects(Locations.self)
.filter("zip == '12345' && ANY parentCompanies.parentNavigation.key == 4")
print(locationsIn12345AndNavigationKey4)
return true
}
}
This prints:
Results<Locations> (
[0] Locations {
key = 0;
zip = 12345;
contacts = RLMArray <0x6000000f2100> (
);
}
)

MVVM with realm: Passing Realm-results across threads?

Using Xcode-8.2.1, Swift-3.0.2, RealmSwift-2.2.0, iOS-Simulator-10:
I try applying the MVVM pattern (explained by Steve Scott here) using Realm.
Everything works until the moment (inside the VIEW-part - see below) where I try to access a viewmodel-property. It says: Realm accessed from incorrect thread
How could I still make the MVVM-pattern do its job of separating model, view-model and view but, on the same time, get thread-safety with realm ?
Is there a way to make Realm-results (i.e. Results<BalancesDataEntry>) being passed across threads ??
Here is my code:
(the issue happens at the very bottom, inside the View-part)
// REALM-OBJECT:
import Foundation
import RealmSwift
class BalancesDataEntry: Object {
dynamic var category: String = ""
dynamic var index: Int = 0
}
MODEL:
import Foundation
import RealmSwift
class MVVMCBalancesModel: BalancesModel
{
fileprivate var entries = [BalancesDataEntry]()
let realm = try! Realm()
init() {
self.createDataEntries()
}
fileprivate func createDataEntries() {
let myBalance = BalancesDataEntry()
myBalance.index = 0
myBalance.category = "Love"
try! self.realm.write {
self.realm.deleteAll()
self.realm.add(myBalance)
}
}
func getEntries(_ completionHandler: #escaping (_ entries: [BalancesDataEntry]) -> Void)
{
// Simulate Aysnchronous data access
DispatchQueue.global().async {
let realmThread = try! Realm()
let returnArray: [BalancesDataEntry] = Array(realmThread.objects(BalancesDataEntry.self))
completionHandler(returnArray)
}
}
}
VIEW-MODEL:
import Foundation
import RealmSwift
class MVVMCBalancesViewModel: BalancesViewModel
{
weak var viewDelegate: BalancesViewModelViewDelegate?
weak var coordinatorDelegate: BalancesViewModelCoordinatorDelegate?
fileprivate var entries: [BalancesDataEntry]? {
didSet {
viewDelegate?.entriesDidChange(viewModel: self)
}
}
var model: BalancesModel? {
didSet {
entries = nil;
model?.getEntries({ (myEntries) in
self.entries = myEntries
})
}
}
var title: String {
return "My Balances"
}
var numberOfEntries: Int {
if let entries = entries {
return entries.count
}
return 0
}
func entryAtIndex(_ index: Int) -> BalancesDataEntry?
{
if let entries = entries , entries.count > index {
return entries[index]
}
return nil
}
func useEntryAtIndex(_ index: Int)
{
if let entries = entries, let coordinatorDelegate = coordinatorDelegate , index < entries.count {
coordinatorDelegate.balancesViewModelDidSelectData(self, data: entries[index])
}
}
}
VIEW:
import UIKit
class MVVMCBalancesViewController: UIViewController {
#IBOutlet weak var label1Outlet: UILabel!
#IBOutlet weak var label2Outlet: UILabel!
var viewModel: BalancesViewModel? {
willSet {
viewModel?.viewDelegate = nil
}
didSet {
viewModel?.viewDelegate = self
refreshDisplay()
}
}
var isLoaded: Bool = false
func refreshDisplay()
{
if let viewModel = viewModel , isLoaded {
// !!!!!!! HERE IS THE ISSUE: Realm accessed from incorrect thread !!!!
self.label1Outlet.text = viewModel.entryAtIndex(0)?.category
self.label2Outlet.text = viewModel.entryAtIndex(1)?.category
} else {
}
}
override func viewDidLoad()
{
super.viewDidLoad()
isLoaded = true
refreshDisplay();
}
}
extension MVVMCBalancesViewController: BalancesViewModelViewDelegate
{
func entriesDidChange(viewModel: BalancesViewModel)
{
}
}
You can use ThreadSafeReference to pass Realm's thread-confined types (Object, Results, List, LinkingObjects) to a different thread. The documentation's section on Passing Instances Across Threads contains this example of passing a single instance of an Object subclass across threads:
let person = Person(name: "Jane")
try! realm.write {
realm.add(person)
}
let personRef = ThreadSafeReference(to: person)
DispatchQueue(label: "background").async {
let realm = try! Realm()
guard let person = realm.resolve(personRef) else {
return // person was deleted
}
try! realm.write {
person.name = "Jane Doe"
}
}
It can be used similarly for Results.
I have found a workaround (see below): Maybe you have better solutions - please let me know!
Here is my github-code realm_mvvm_c on github
After introducing a new protocol and making (pretty much everything) conform to it, things worked out.
Here is the protocol called DataEntry:
import Foundation
protocol DataEntry: class {
var idx: Int { get set }
var category: String { get set }
}
Now, make everything conform to it, such as
--> the realm object (i.e. class BalancesDataEntry: Object, DataEntry {...)
--> the getEntries return value (i.e. func getEntries(_ completionHandler: #escaping (_ entries: [DataEntry]) -> Void))
--> the View-Model's entries (i.e. fileprivate var entries: [DataEntry]? {..)
--> all the corresponding Model- and View-Model protocols also need the DataEntry datatype (see git-repo for complete picture)
After that, it was enough to change the completion-handler return-array of the MODEL's method getEntries(..) to a newly created object-instance (ie. DataEntryDub) that is keept conform to the DataEntry protocol:
func getEntries(_ completionHandler: #escaping (_ entries: [DataEntry]) -> Void)
{
// Simulate Aysnchronous data access
DispatchQueue.global().async {
let realmThread = try! Realm()
class DataEntryDub: DataEntry {
var idx: Int
var category: String
init(idx: Int, category: String) {
self.idx = idx
self.category = category
}
}
var returnArray = [DataEntry]()
for entry in realmThread.objects(BalancesDataEntry.self) {
returnArray.append(DataEntryDub(idx: entry.idx, category: entry.category))
}
completionHandler(returnArray)
}
}
Here is my github-code realm_mvvm_c on github