Prevent delete array of object adding again in realm db - swift

I have an api who fetch data and add it in realm, what i want is whenever i delete realm object and refresh the page and called the api. the object that already delete is not showing/adding again in realm db. How to do it? here is my code
class TickerRealmStorage: RealmStorage, TickerStorage {
private var items = RealmSwift.List<Ticker>()
private var temptTicker: Ticker?
func get() -> RealmSwift.List<Ticker> {
return items
}
func save(messages: [Message]) {
messages.forEach { message in
let ticker = Ticker()
ticker.campaignID = message.campaignID
ticker.isRead = message.isRead
ticker.messageBody = message.body
if let temptTicker = temptTicker, ticker.campaignID == temptTicker.campaignID {
try! realm.write {
realm.delete(ticker)
}
}
items.append(ticker)
}
do {
try realm.write {
realm.add(items, update: .modified)
}
} catch {
fatalError("Failed to save tickers")
}
}
func delete(ticker: Ticker) {
temptTicker = ticker
try! realm.write {
realm.delete(ticker)
}
}
}
Should i add a temptItems so i check if the data already delete not adding into list items.

Related

Swift app crashing when attempting to fetch contacts

I am creating an onboarding portion of an app which gets the user's contacts to check which already have the app to add as friends. I'm using the CNContact framework. I have created several methods I'm using to get a full list of the users' contacts, check if they have the app, and enumerate them in a UITableView. However, when the view loads, the app crashes with the error "A property was not requested when contact was fetched." I already make a fetch request with the keys CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, ad CNContactImageDataKey. I've included all my code here:
import Foundation
import Contacts
import PhoneNumberKit
struct ContactService {
static func createContactArray() -> [CNContact] {
var tempContacts = [CNContact]()
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let _ = error {
print("failed to request access to contacts")
return
}
if granted {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
request.sortOrder = CNContactSortOrder.familyName
do {
try store.enumerateContacts(with: request, usingBlock: { (contact, stop) in
tempContacts.append(contact)
})
} catch {
print("unable to fetch contacts")
}
print("created contact list")
}
}
return tempContacts
}
static func createFetchedContactArray(contactArray: [CNContact], completion: #escaping ([FetchedContact]?) -> Void) -> Void {
var temp = [FetchedContact]()
getNumsInFirestore { (nums) in
if let nums = nums {
for c in contactArray {
let f = FetchedContact(cnContact: c, numsInFirebase: nums)
temp.append(f)
}
return completion(temp)
} else {
print("Error retrieving numbers")
}
}
return completion(nil)
}
static func getNumsInFirestore(_ completion: #escaping (_ nums : [String]?) -> Void ) -> Void {
var numsInFirebase = [String]()
let db = FirestoreService.db
db.collection(Constants.Firestore.Collections.users).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting user documents: \(err)")
completion(nil)
} else {
for doc in querySnapshot!.documents {
let dict = doc.data()
numsInFirebase.append(dict[Constants.Firestore.Keys.phone] as! String)
}
completion(numsInFirebase)
}
}
}
static func getPhoneStrings(contact: CNContact) -> [String] {
var temp = [String]()
let cnPhoneNums = contact.phoneNumbers
for n in cnPhoneNums {
temp.append(n.value.stringValue)
}
return temp
}
static func hasBump(contact: CNContact, completion: #escaping (_ h: Bool) -> Void ) -> Void {
let contactNums = ContactService.getPhoneStrings(contact: contact)
ContactService.getNumsInFirestore { (nums) in
if let nums = nums {
return completion(contactNums.contains(where: nums.contains))
} else {
print("Error retrieving numbers from firestore")
return completion(false)
}
}
}
static func anyContactsWithBump(completion: #escaping (_ yes: Bool) -> Void) {
let contacts = createContactArray()
var tempBool = false
let workgroup = DispatchGroup()
for c in contacts {
workgroup.enter()
hasBump(contact: c) { (has) in
if has {
tempBool = true
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
completion(tempBool)
}
}
}
Then I call the methods in the view controller class:
import UIKit
import Contacts
import FirebaseDatabase
import Firebase
import FirebaseFirestore
class AddContactsVC: UIViewController {
var fetchedContactsWithBump: [FetchedContact] = []
override func viewDidLoad() {
let cnContacts = ContactService.createContactArray()
var contactsWithBump = [CNContact]()
let workgroup = DispatchGroup()
for contact in cnContacts {
workgroup.enter()
ContactService.hasBump(contact: contact) { (has) in
if has {
contactsWithBump.append(contact)
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
print("Number of contacts with Bump: \(contactsWithBump.count)")
ContactService.createFetchedContactArray(contactArray: contactsWithBump) { (fetchedContacts) in
if let fetchedContacts = fetchedContacts {
self.fetchedContactsWithBump = fetchedContacts
} else {
print("Error creating fetchedContacts array.")
}
}
}
I also get the message "Error creating fetchedContacts array" in the console, so I know something is going wrong with that method, I'm just not sure what. Any help is appreciated!
Edit: The exception is thrown at 3 points: 1 at the first line of my FetchedContact init method, which looks like this:
init(cnContact: CNContact, numsInFirebase: [String]) {
if cnContact.imageDataAvailable { self.image = UIImage(data: cnContact.imageData!) }
It also points to the line let f = FetchedContact(cnContact: c, numsInFirebase: nums) in createFetched contact array, and finally at my completion(numsInFirebase) call in getNumsInFirestore.
To start with
let contacts = createContactArray()
will always return an empty array.
This function has a return statement outside the closure so will immediately return an empty array.
Change createContactArray to use a completion handler like the other functions you have to populate contacts from inside the closure.

How To Save Only One Instance Of Class In Realm

So instead of using user defualts I want to persist some settings using Realm.
I've created a class for the settings
import Foundation
import RealmSwift
class NutritionSettings: Object {
#objc dynamic var calories: Int = 0
#objc dynamic var proteins: Int = 0
#objc dynamic var carbohydrates: Int = 0
#objc dynamic var fats: Int = 0
}
But in my view controller I don't know how to save just one instance of it
I've tried
let realm = try! Realm()
let settings = NutritionSettings()
do {
try realm.write{
settings.calories = cals!
settings.carbohydrates = carbs!
settings.fats = fats!
settings.proteins = proteins!
}
} catch {
print("error saving settings")
}
Since I know doing realm.add would just add another NutritionSettings object which is not what I want. I was unable to clarify anything using the documentation. Any help would be appreciated thanks.
I faced a similar issue in my project when I tried to save a user session object. If you want to save a unique object, override the primaryKey() class method and set the unique key for it.
#objcMembers class NutritionSettings: Object {
static let uniqueKey: String = "NutritionSettings"
dynamic var uniqueKey: String = NutritionSettings.uniqueKey
dynamic var calories: Int = 0
override class func primaryKey() -> String? {
return "uniqueKey"
}
}
Then to receive the object just use the unique key.
// Saving
let settings = NutritionSettings()
settings.calories = 100
do {
let realm = try Realm()
try realm.write {
realm.add(settings, update: .modified)
}
} catch {
// Error handling
}
// Reading
var settings: NutritionSettings?
do {
let realm = try Realm()
let key = NutritionSettings.uniqueKey
settings = realm.object(ofType: NutritionSettings.self, forPrimaryKey: key)
} catch {
// Error handling
}
if let settings = settings {
// Do stuff
}
Hope it will help somebody.
If you look at the example realm provides https://realm.io/docs/swift/latest you can see that in order to only save one object you still have to do an add. Once you have added the object to the database you can fetch that object and do a write that modifies the internal properties
let realm = try! Realm()
let settings = NutritionSettings()
settings.id = 1
do {
try realm.write{
realm.add(settings)
}
} catch {
print("error saving settings")
}
Next you can fetch and modify that single instance that you saved
let realm = try! Realm()
let settings = realm.objects(NutritionSettings.self).filter("id == 1").first
do {
try realm.write{
settings.calories = cals!
settings.carbohydrates = carbs!
settings.fats = fats!
settings.proteins = proteins!
}
} catch {
print("error saving settings")
}

How to set contact as a favorite contact programmatically in Swift?

I am developing an application in which I need to fetch all the contacts from device and then set it to favorite contact on button press. I am able to fetch all contacts using [CNContact] in iOS 9 and 10. But don't know how to set it as a favorite contact.
Can we set CNContact as a favorite contact?
Can we make changes in CNContact?
You can store Favourites to Realm DB. Like This,
class FavouriteList: Object {
let favouriteList : List<FavouriteContact> = List<FavouriteContact>()
}
class FavouriteContact: Object {
dynamic var identifier : String? = ""
override class func primaryKey() -> String? {
return "identifier"
}
}
// Add Favourite Contact in Realm
class func add(identifier: String) -> Bool {
var realm: Realm!
do {
realm = try Realm()
realm.beginWrite()
} catch {
print(error.localizedDescription)
}
let realmTask: FavouriteList= FavouriteList()
let favContact: FavouriteContact = FavouriteContact()
// Check ID Exist or Not
let idExists: FavouriteContact? = realm.object(ofType: FavouriteContact.self, forPrimaryKey: identifier)
if idExists?.identifier != nil {
realm.cancelWrite()
return false
} else {
favContact.identifier = identifier
realmTask.favouriteList.append(favContact)
realm.add(realmTask)
}
// Realm Commit
do {
try realm.commitWrite()
} catch {
print("Realm Task Write Error : ", error.localizedDescription)
}
return true
}
// Remove Favourite Contact
class func remove(identifier: String) -> Bool {
var realm: Realm!
do {
realm = try Realm()
realm.beginWrite()
} catch {
print(error.localizedDescription)
}
// Check ID Exist or Not
let idExists: FavouriteContact? = realm.object(ofType: FavouriteContact.self, forPrimaryKey: identifier)
if idExists?.identifier != nil {
realm.delete(idExists!)
} else {
realm.cancelWrite()
return false
}
// Realm Commit
do {
try realm.commitWrite()
} catch {
print("Realm Task Write Error : ", error.localizedDescription)
}
return true
}
// Get Favourite List
class func get(completionHandler: #escaping (_ result: [CNContact]) -> ()) {
var favourites: [CNContact] = [CNContact]()
do {
let realm = try Realm()
let dataRealmContacts: Results<FavouriteList> = realm.objects(FavouriteList.self)
for item in dataRealmContacts {
for contactID in item.favouriteList {
if contactID.identifier != nil {
favourites.append(getContactFromID(identifier: contactID.identifier!))
}
}
}
completionHandler(favourites)
} catch {
print(error.localizedDescription)
completionHandler(favourites)
}
}

CloudKit shared record: how to make it editable by the user who received the link

I tried everything, read everywhere, but I'm unable to make a shared record editable by the user who received the link
Everything works fine except the edit performed by the invited user
here's the sharing code (like the WWDC16 video):
let sharingController = UICloudSharingController { (controller, preparationCompletionHandler) in
let share = CKShare(rootRecord: record)
share.publicPermission = .readWrite
share[CKShareTitleKey] = "Help me to improve data" as CKRecordValue
share[CKShareTypeKey] = "com.company.AppName" as CKRecordValue
let modifyRecordsOperation = CKModifyRecordsOperation( recordsToSave: [record, share], recordIDsToDelete: nil)
modifyRecordsOperation.modifyRecordsCompletionBlock = { (records, recordIDs, error) in
if let errorK = error {
print(errorK.localizedDescription)
}
preparationCompletionHandler(share, CKContainer.default(), error)
}
CKContainer.default().privateCloudDatabase.add(modifyRecordsOperation)
}
sharingController.availablePermissions = [.allowPublic, .allowPrivate, .allowReadWrite]
sharingController.delegate = self
controller.present(sharingController, animated: true)
The console always print:
PrivateDB can't be used to access another user's zone
thank you
when you retrive the shared record you need to add the operation to the sharedDatabase:
func fetchShare(_ metadata: CKShareMetadata) {
debugPrint("fetchShare")
let operation = CKFetchRecordsOperation(recordIDs: [metadata.rootRecordID])
operation.perRecordCompletionBlock = { record, _, error in
if let errore = error { debugPrint("Error fetch shared record \(errore.localizedDescription)") }
if let recordOk = record {
DispatchQueue.main.async() {
self.storage.append(recordOk)
}
}
}
operation.fetchRecordsCompletionBlock = { (recordsByRecordID, error) in
if let errore = error { debugPrint("Error fetch shared record \(errore.localizedDescription)") }
}
CKContainer.default().sharedCloudDatabase.add(operation)
}
but now there is a problem when you try to update the record, you need to know who is the owner, if the owner is the one who shared the record you need to save to the private db, if the owner is another person you need to save to the shared db... so:
func updateOrSaveRecord(_ record:CKRecord, update:Bool) {
var db : CKDatabase
if update == true {
guard let creatorUserID = record.creatorUserRecordID else { return }
if record.share != nil && creatorUserID.recordName != CKCurrentUserDefaultName {
debugPrint("record shared from another user")
db = CKContainer.default().sharedCloudDatabase
} else {
debugPrint("private record")
db = CKContainer.default().privateCloudDatabase
}
} else { db = CKContainer.default().privateCloudDatabase }
db.save(record) { (savedRecord, error) in
if let errorTest = error {
print(errorTest.localizedDescription)
} else {
if let recordOK = savedRecord {
DispatchQueue.main.async {
if update == false {
self.storage.append(recordOK)
} else {
self.dettCont?.updateScreen()
}
self.listCont?.tableView.reloadData()
}
}
}
}
}
to know if the record was created by the current user the trick is to compare against CKCurrentUserDefaultName

Swift Remove Object from Realm

I have Realm Object that save list from the JSON Response. But now i need to remove the object if the object is not on the list again from JSON. How i do that?
This is my init for realm
func listItems (dic : Array<[String:AnyObject]>) -> Array<Items> {
let items : NSMutableArray = NSMutableArray()
let realm = try! Realm()
for itemDic in dic {
let item = Items.init(item: itemDic)
try! realm.write {
realm.add(item, update: true)
}
items.addObject(item)
}
return NSArray(items) as! Array<Items>
}
imagine your Items object has an id property, and you want to remove the old values not included in the new set, either you could delete everything with just
let result = realm.objects(Items.self)
realm.delete(result)
and then add all items again to the realm,
or you could also query every item not included in the new set
let items = [Items]() // fill in your items values
// then just grab the ids of the items with
let ids = items.map { $0.id }
// query all objects where the id in not included
let objectsToDelete = realm.objects(Items.self).filter("NOT id IN %#", ids)
// and then just remove the set with
realm.delete(objectsToDelete)
I will get crash error if I delete like top vote answer.
Terminating app due to uncaught exception 'RLMException', reason: 'Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.'
Delete in a write transaction:
let items = realm.objects(Items.self)
try! realm!.write {
realm!.delete(items)
}
func realmDeleteAllClassObjects() {
do {
let realm = try Realm()
let objects = realm.objects(SomeClass.self)
try! realm.write {
realm.delete(objects)
}
} catch let error as NSError {
// handle error
print("error - \(error.localizedDescription)")
}
}
// if you want to delete one object
func realmDelete(code: String) {
do {
let realm = try Realm()
let object = realm.objects(SomeClass.self).filter("code = %#", code).first
try! realm.write {
if let obj = object {
realm.delete(obj)
}
}
} catch let error as NSError {
// handle error
print("error - \(error.localizedDescription)")
}
}
What you can do is assign a primary key to the object you are inserting, and when receiving a new parsed JSON you verify if that key already exists or not before adding it.
class Items: Object {
dynamic var id = 0
dynamic var name = ""
override class func primaryKey() -> String {
return "id"
}
}
When inserting new objects first query the Realm database to verify if it exists.
let repeatedItem = realm.objects(Items.self).filter("id = 'newId'")
if !repeatedItem {
// Insert it
}
The first suggestion that comes to mind is to delete all objects before inserting new objects from JSON.
Lear more about deleting objects in Realm at https://realm.io/docs/swift/latest/#deleting-objects
do {
let realm = try Realm()
if let obj = realm.objects(Items.self).filter("id = %#", newId).first {
//Delete must be perform in a write transaction
try! realm.write {
realm.delete(obj)
}
}
} catch let error {
print("error - \(error.localizedDescription)")
}
There is an easier option to remove 1 object:
$item.delete()
remember to have the Observable object like:
#ObservedRealmObject var item: Items
var realm = try! Realm()
open func DeleteUserInformation(){
realm.beginWrite()
let mUserList = try! realm.objects(User.self)
realm.delete(mUserList)
try! realm.commitWrite()
}