Deleting items that have been saved using NSUserDefaults - ios

I have a table view that has its data saved with the following function.
func saveWebsites() {
let websitesData = NSKeyedArchiver.archivedDataWithRootObject(loadedWebsites)
NSUserDefaults.standardUserDefaults().setObject(websitesData, forKey: KEY_WEBSITES)
NSUserDefaults.standardUserDefaults().synchronize()
}
I have tried to allow the user to delete an item from the table view, but what happens is that the user can delete an item from the table view, but when the app reloads the item is still there. The code I am using to try and delete it is this:
func deleteWebsites() {
if let data = NSUserDefaults.standardUserDefaults().objectForKey(KEY_WEBSITES) as? NSData {
let site = NSKeyedUnarchiver.unarchiveObjectWithData(data)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
Anybody know my problem?

Related

Is there any pagination like code for saving JSON data to Realmdatabase

I have a button and below it is the table view. Table view cell has some random data.On button click I am calling the the api(function name is : api.urlRequest(userID: 80, businessUnitID: 2) ) .I have an API that has 35,0000 entries. What I want is to save that data in Realm database. The problem is that, when I am calling the save function, my UI freezes. I am appending the JSON data to Model and then saving it to database. I can get the start index and end index of the the JSON data.
What I tried was to call the API on background thread and when saving function is called, I am calling it on main thread. But this didn't worked.
class ViewController: UIViewController,getAdhocJSONDelegate{
let realm = try! Realm()
#IBOutlet weak var tableViewRef: UITableView!
var array = [NSDictionary]()
var adhocData : [AdhocModel] = []//for appending the JSON data to the model
var adhocDB : Results<AdhocDB>?// for accessing the database
let api = AdhocAPIParamteres()
var adhocJSONDatafromAPI : NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
adhocDB = realm.objects(AdhocDB.self)
}
#IBAction func buttonPressed(_ sender: Any) {
print("BUtton Tapped")
api.urlRequest(userID: 80, businessUnitID: 2)
api.delegate = self
}
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
adhocData.append(model)
print("data appended")
DispatchQueue.main.async {
self.saveToDb(data:model)
}
}
func saveToDb(data: AdhocModel) {
let adhoc = AdhocDB()
try! realm.write {
adhoc.SiteId = data.site_id
adhoc.AtmId = data.atm_id
adhoc.SiteAdress = data.site_address
realm.add(adhoc)
}
}
}
I want to save data in such a way that my UI doesn't freeze.
There are a few issues with the code and writing data to Realm on a background thread is covered in the documentation so I won't address that. Following that design pattern will correct the UI lockup.
This is another issue
func saveToDb(data: AdhocModel) {
**let adhoc = AdhocDB()**
You want to write your populated model to realm, but AdhocDB is a Results object, not a Realm model object. Additionally the realm object created in appTutorialData which is model, is passed to saveToDb, then another object is created and then populated with data from the first object. There's no reason to do that (in this code)
Assuming AdHocModel is a Realm object, this is much cleaner
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
try! realm.write {
realm.add(model)
}
}
}
You're going to want to wrap that write within a background thread (again, see the documentation) something like this
DispatchQueue(label: "background").async {
autoreleasepool {
.
.
.
try! realm.write {
realm.add(model)
}
}
}
You may ask about populating your array adhocData.append(model). We don't know what you're doing with it but if you're using it as perhaps a dataSource for a table view or some other UI element, you may want to consider using a Results object instead of an Array.
A significant advantage is, if you have 35,000 objects, that's a pretty sizable array and if you have more, it could overwhelm the device as ALL of that data is stored in memory. However, Results objects are lazily loaded so you could have a much larger dataset without overwhelming the device.
Additionally, when Realm objects are stored in an array, they 'Disconnect' from Realm and loose Realm functionality - they will not auto-update nor will changes to the actual object in Realm be reflected in that array nor can you just update the object - it doesn't appear to have a primary key.
However, if you populate a Results object with those models, they will be live updating - so if for example the atm_id changes in Realm, that object will automatically be updated. If you need to change a property you can change it directly on that object within a write transaction.
So the pattern would be to have a class var of Results and load your objects into those results within viewDidLoad. As you add more models, the results object will automatically be updated.
To keep your UI fresh, you would want to add observers (aka Notifications)to those Results so you can be notified when an object is updated so you can reload your tableView for example.

How to check which items are already saved in Core Data from a given array

My goal is
I want to store an array of data into coreData.
Here is my array
let storageArr = ["Teja", "Teja two", "Teja Ri", "Bhanu", "Stack", "Stack over", "Stack over flow"] as NSObject
And if user is typing in the Textfield I need to show them(related to that character) in drop down (tableView).
Let's say user typed "Te". I need to show Teja, Teja two, Teja Ri in table view.
I have done everything. But am unable to fetch only Teja, Teja two, Teja Ri from array.
Here is the code which I tried
override func viewDidLoad() {
super.viewDidLoad()
savingDataToLocalDB()
}
#IBAction func searchBtnClicked(_ sender: Any) {
setUpData(searchKeyword: searchTF.text)
}
func savingDataToLocalDB(){
let saveContext = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
let data: SearchList = NSEntityDescription.insertNewObject(forEntityName: "SearchList",
into: saveContext!) as! SearchList
data.names = storageArr
do {
try saveContext?.save()
print("data saved")
} catch {
print(error)
}
}
func setUpData(searchKeyword: String){
let fetchContext = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
let fetchReq = NSFetchRequest<NSManagedObject>(entityName: "SearchList")
do {
let data = try fetchContext?.fetch(fetchReq)
var filteredData = data as! [SearchList]
print(filteredData[0].names) // Am getting data here
//following line is not working
let wasfilteredData = filteredData.filter { $0.names!.lowercased().contains(searchKeyword.lowercased()) } as NSArray
} catch {
print(error)
}
}
Here in viewDidLoad() am calling savingDataToLocalDB() which means storing in coreData
And in #IBAction am fetching data from coreData.
But in setUpData(searchKeyword: String) method am unable to filer data consists of "Te"(user entered in textfiled)
Please find the following image. That's how I created entity
Where I am doing wrong?
It appears that you have a SINGLE entity in your database, with a transformable property of 'names'. That property is a transformable Array. Which means that it is stored in the database as Data and is encoded and decoded automatically. The database cannot search or sort this array because 1) it is a single property, not collection of entities and 2) it is stored as data. So all management must be done in memory, which means you have none of the benefits of core-data while having all of the cost.
Next, in your code you fetch ALL of the SearchList. Your database contains one so you get back and array of length 1. You then filter that Array of SearchList - NOT the array of names, and you get back the same searchList Array that you started with because that SearchList indeed passes the test.
Perhaps you want:
let wasfilteredData = filteredData.first?.names.filter { $0.lowercased().contains(searchKeyword.lowercased()) } as NSArray
Or perhaps you should consider having each entity contain only term, and then you search using predicates.

How can I stop UICollectionView from showing duplicate items after Firebase update

I have two UICollection views on a page that displays data about a Room. It includes photos of the room in one UICollection View and another UICollection View which contains a list of items in that room. There's a link to edit the Room. When a user clicks on the link, they then segue to another view that let's them update it including adding additional photos.
After adding a photo, and hitting submit, in the background the photo is uploaded to Firebase storage and in the Firebase database, the record is updated to include the name of the file that was just uploaded. Meanwhile, the user is segued back to the Room view.
There's a watched on the record of the room in Firebase and when it updates, then the view is refreshed with new data. This is where the problem occurs. It appears, based on a lot of debugging that I've been doing, that the Observe method fires twice and what ends up happening, is the UICollection view that holds the images of the room will show duplicates of the last photo added.
For example, if I add one photo to the room, that photo will appear in the collection 2x. I've attempted to clear the array before the array is updated with the images, and from my analysis, it appears that the array only contains two items, despite showing three in the view. I'm not sure what is happening that would cause this?
Here's a link to the entire file, because I think it might help.
Here's the loadData() method in case this is all that's important:
func loadData() {
self.ref = Database.database().reference()
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
guard let userID = Auth.auth().currentUser?.uid else { return }
let buildingRef = self.ref.child("buildings").child(userID)
buildingRef.keepSynced(true)
buildingRef.child(self.selected_building as String).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
if ((value) != nil) {
let building_id = value?["id"] as! String
let saved_image = value?["imageName"] as! String
let user_id = userID as! String
let destination = "/images/buildings/\(userID)/\(building_id)/"
let slideShowDictionary = value?["images"] as? NSDictionary
if ((slideShowDictionary) != nil) {
self.slideShowImages = [UIImage]()
self.slideShowCollection.reloadData()
var last_value = ""
slideShowDictionary?.forEach({ (_,value) in
print("are they different? \(last_value != (value as! String))")
if (last_value != value as! String) {
print("count: \(self.slideShowImages.count)")
print("last_value \(last_value)")
print("value \(value)")
last_value = value as! String
CloudStorage.instance.downloadImage(reference: destination, image_key: value as! String, completion: { (image) in
self.slideShowImages.append(image)
self.slideShowCollection.reloadData()
})
}
})
CloudData.instance.getBuildingById(userId: user_id, buildingId: building_id, completion: { (building) in
self.title = building.buildingName as String
self.roomsCollection.reloadData()
})
}
}
})
// User is signed in.
self.getRooms()
}
I am not completely familiar with the Firebase API but if you are having issues with the observation I would suspect the following:
#IBAction func unwindToRoomsVC(segue:UIStoryboardSegue) {
loadData()
}
Triggering loadData a second time looks like it would add a second observation block. As best I can tell the .observe method probably persists the block it is given and triggers it on all changes.

How can I prevent TableView cells from duplicating? Swift

So I am building a notes app and have tried everything but can not figure this issue out. I have 3 UIViewController's. When you click a button on the first UIViewController, it shoots you over to the third one which consist of a UITextView, UITextField and a Static UILabel which gets updated with some information. When you fill out these fields and tap the back button it brings you to the second view controller which is a table that gets updated with this information.
The issue is: when I tap the UITableViewCell it loads the information back to the third view controller so the user can edit his notes but when I come back to the UITableView it creates a brand new cell instead of just updating the old one.
If I could just update my array with the same object I sent back to be edited by the user I think this issue would be solved but I have no idea how to do this. Thanks for the help!
VC2 - this is how I am sending my data from the tableView to the textView Back
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextView = segue.destinationViewController as! TextViewController
guard let indexPath = self.tableView.indexPathForSelectedRow else {
print("Didnt work")
return
}
let dataToSendBackToBeEdited = textViewObjectData[indexPath.row]
print(dataToSendBackToBeEdited.dreamText)
print(dataToSendBackToBeEdited.dreamTitle)
print(dataToSendBackToBeEdited.dreamType)
print(dataToSendBackToBeEdited.description)
print(dataToSendBackToBeEdited)
nextView.objectSentFromTableViewCell = dataToSendBackToBeEdited
}
This is how I am saving the information the the user taps back to go to the tableView
func determineSave() {
guard var savedDreamArray = NSKeyedUnarchiver.unarchiveObjectWithFile(TextViewController.pathToArchieve.ArchiveURL.path!) as? [Dream] else {
//First dream object saved
let dreamObject = Dream(dreamTitle: titleForDream.text!, dreamType: typeOfDreamLabel.text!, dreamText: textView.text, currentDate: NSDate())
dreamArray.append(dreamObject)
NSKeyedArchiver.archiveRootObject(dreamArray, toFile: pathToArchieve.ArchiveURL.path!)
return
}
//print(savedDreamArray.count)
//print(savedDreamArray.first!.dreamTitle)
let dreamObject = Dream(dreamTitle: titleForDream.text!, dreamType: typeOfDreamLabel.text!, dreamText: textView.text!, currentDate: NSDate())
savedDreamArray.append(dreamObject)
NSKeyedArchiver.archiveRootObject(savedDreamArray, toFile: pathToArchieve.ArchiveURL.path!)
}
I was having this issue as well. Came in here, and got the answer. The array!
I was appending the array as well, which apparently was causing duplicate cells to appear.
I just reset my array back to empty before I retrieved the data again and reloaded the table.
I'm using Firebase so my code looks like this:
DataService.instance.dateProfileRef.observeEventType(FIRDataEventType.Value) { (snapshot: FIRDataSnapshot)
in
//have to clear the array here first before reloading the data. otherwise you get duplicates
self.posts = [] //added this line
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
let snapUserID = (snap.childSnapshotForPath("userID").value!)
if snapUserID as? String == USER_ID {
if let profileDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = Post(postKey: key, postData: profileDict)
self.posts.append(post)
You do a .append() on your array, this will add a new cell.
You must find the index of the object you want to update and then assign it:
array[index] = newObject

Data Persistence

When the app is loaded, I want to know whether I have already saved the data to disk. If it is saved, I just load from disk, if not, I save.
for example, I want to save an array(person) using NSUserDefaults.standardUserDefaults()
Save Method:
let defaults = NSUserDefaults.standardUserDefaults()
if let savedPeople = defaults.objectForKey("people") as? NSData
people = NSKeyedUnarchiver.unarchiveObjectWithData(savedPeople) as! [Person]
But how can I know if I have saved the data or not in viewDidLoad()? I don't want to save every time I open the app, and it will also overwrite the data I modified before.
This is how you will do it:
override func viewDidLoad() {
super.viewDidLoad()
if let savedPeople = NSUserDefaults.standardUserDefaults().objectForKey("people") {
// Use savedPeople based on your need
} else {
NSUserDefaults.standardUserDefaults().setObject(myArray, forKey: "people")
}
}

Resources