How to store and retrieve tabledata using userdefault Swift 4 - ios

My task is user data appending into items array with strut method and assigning to tableView tableData array. In my code multiple places I used tableData for some validations.
Now, my problem is I can able to see my table data when I move background to foreground but If I remove application from background then again If I am open my application, There is empty tableView. So, I need to understand. how tableData store into UserDeafult and then retrieve to load tableView for avoid data loss.
// Array declaration
var items = [Item]()
var tableData = [Item]()
public func documentPicker(_ controller: UIDocumentPickerViewController,didPickDocumentsAt urls: [URL]) {
// Here I am getting user selected file url and its name from iCloud.
// I skipped to paste here.
// User picked file data appending into items array
items.append(Item(url: bookurl, title: name))
// Assign items data to tableData
if let data = UserDefaults.standard.data(forKey:"items") {
do {
let itemsUser = try PropertyListDecoder().decode(Array<Item>.self, from: data)
tableData = itemsUser
} catch { print(error) }
}
}
// MARK - TABLE VIEW DELEGATIONS
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData.count
}
// TableView data-load
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let item = tableData[indexPath.row]
cell.name_label.text = item.name
}
return cell
}

For above scenario UserDefault should be outside of the picker delegation. If we maintain within viewDidload with some logic then It will work well.
//Within ViewDidLoad
if let data = UserDefaults.standard.data(forKey:"items") {
do {
let itemsUser = try PropertyListDecoder().decode(Array<Item>.self, from: data)
tableData = itemsUser
} catch { print(error) }
}

Related

Handling asynchronous Firestore data reading in tableView - iOS

I would like to retrieve data from my simple Firestore database
I have this database:
then I have a model class where I have a method responsible for retrieving a data which looks like this:
func getDataFromDatabase() -> [String] {
var notes: [String] = []
collectionRef = Firestore.firestore().collection("Notes")
collectionRef.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
notes = documents.map { $0["text"]! } as! [String] // text is a field saved in document
print("inside notes: \(notes)")
}
print("outside notes: \(notes)")
return notes
}
and as a UI representation I have tableViewController. Let's take one of the methods, for example
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("tableview numberOfRowsInSection called")
return model.getDataFromDatabase().count
}
Then numberOfRows is 0 and the output in the console is:
and I am ending up with no cells in tableView. I added a breakpoint and it doesn't jump inside the listener.
And even though I have 3 of them, they are kinda "late"? They are loaded afterwards. And then the tableView doesn't show anything but console says (later) that there are 3 cells.
If needed, there is also my method for showing the cells names:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Cells")
let cell = tableView.dequeueReusableCell(withIdentifier: "firstCell", for: indexPath)
cell.textLabel!.text = String(model.getDataFromDatabase()[indexPath.row].prefix(30))
return cell
}
but this method is not even loaded (no print in the console) and this method is written below the method with numberOfRowsInSection.
I have also 2 errors (I don't know why each line is written twice) and these are:
but I don't think it has something to do with the problem.
Thank you for your help!
As #Galo Torres Sevilla mentioned, addSnapshotListener method is async and you need to add completion handler to your getDataFromDatabase() function.
Make following changes in your code:
Declare Global variable for notes.
var list_notes = [String]()
Add completion handler to getDataFromDatabase() method.
func getDataFromDatabase(callback: #escaping([String]) -> Void) {
var notes: [String] = []
collectionRef = Firestore.firestore().collection("Notes")
collectionRef.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
notes = documents.map { $0["text"]! } as! [String] // text is a field saved in document
print("inside notes: \(notes)")
callback(notes)
}
}
Lastly, call function on appropriate location where you want to fetch notes and assign retrieved notes to your global variable and reload TableView like below:
self.getDataFromDatabase { (list_notes) in
self.list_notes = list_notes
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Changes in TableView:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("tableview numberOfRowsInSection called")
return self.list_notes.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Cells")
let cell = tableView.dequeueReusableCell(withIdentifier: "firstCell", for: indexPath)
cell.textLabel!.text = String(self.list_notes[indexPath.row].prefix(30))
return cell
}
All you need to do is refresh the table cell every time you retrieve the data. Put this code after you set your data inside the array.
self.tableView.reloadData()

UITableViewCells won't load index is out of range

I am trying to load some data from CloudKit to populate a tableview with custom cells and I am having difficulty getting the data to appear.
When I define the number of rows as the count of the CKRecord array the tableview shows up, but with nothing loaded into them. They are just spaced out correctly for the images to be in there. Also, when I set breakpoints at let record = matches[indexPath.row] it won't trigger.
However, if I change the return matches.count to an actual number, the project crashes at let record = matches[indexPath.row]
with the error that the index is out of range. I want to keep the number of rows as the count for the record array, but that is the only change that will actually execute the override function that calls the tableview cell.
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matches.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Card", for: indexPath) as! MatchTableViewCell
let record = matches[indexPath.row]
if let img = record.value(forKey: "Picture") as? CKAsset {
cell.profileimg.image = UIImage(contentsOfFile: img.fileURL.path)
}
return cell
}
Any advice is appreciated
Update- here is where I load the model
func loadModel() {
let totalMatch = (defaults.object(forKey: "passedmatch") as! [String] )
let predicateMatch = NSPredicate(format: "not (UserID IN %#)", totalMatch )
ProfilesbyLocation = defaults.object(forKey: "Location") as! String
let query = CKQuery(recordType: ProfilesbyLocation , predicate: predicateMatch )
publicData.perform(query, inZoneWith: nil,completionHandler: ({results, error in
print("loading")
if (error != nil) {
let nsError = error! as NSError
print(nsError.localizedDescription)
print ("error")
} else {
if results!.count > 0 {
DispatchQueue.main.async() {
self.tableView.reloadData()
self.refresh.endRefreshing()
print("refreshed")
}
}
}
}
)
)}
It sounds like you are not calling
tableview.reloadData()
after your asynchronous data comes in. Leave numberOfRowsInSection as it is and make sure when your network request comes back you are reloading. That should do it.
If you received your data from background thread(or queue), please make sure when you update your UI elements under the main thread(or queue). Try this:
DispatchQueue.main.async {
tableView.reloadData()
}
This solved my problem.
First, disconnect the Delegate/DataSource of the TableView from the Interface Builder refer this image. When you have finally prepared your data that is matches array then add this.
`DispatchQueue.main.async {
tableView.dataSource = self
tableView.delegate = self
tableView.reloadData
}`
Hope this helps.
It looks like your matches is incorrect. Before you call func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)
, you should check your matches.
If you are still not sure about that. You can init your matches with string literals and have a try.

UITableView first row '0' wont update upon table reload - all others do?

I have a one view app with embedded UITableView that displays a list of "stores"(Realm object). By default I populate the table view of all the Store objects. IF the user wants to then narrow the results they can do so by using any combination of text fields in MasterVC. When they hit search - simply update TableView with 'filtered' Realm objects.
What works:
Populate UITableView with objects from the Realm.
Create new Realm entries via text field entries in MasterVC and repopulate table in ResultsVC.
Swipe to delete object on table / and Realm object.
What sort of works:
If user enters a search term then 'filter' the Realm object (Stores) and repopulate the table. This correctly reloads and returns the number of results. However the First Cell (0) of the TableView is always the exact same and never updates.. If there are 20 returned results in the search then Rows 1-18 are correctly displayed. Row 0 is static and never changes its text. Any obvious reasons why?
Results Table View Controller
class ResultsVC: UITableViewController {
// data source
var stores: Results<Store> = {
let realm = try! Realm()
return realm.objects(Store.self)
}()
var token: NotificationToken?
...
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stores.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! ResultsCustomViewCell
let stores = realm.objects(Store.self)
let currentStore = stores[indexPath.row]
cell.storeNumber.text = "#\(currentStore.storeNumber)"
cell.storeName.text = "\"\(currentStore.storeName)\""
return cell
}
}
Here is how I'm accessing the ResultsVC from MasterVC
Master View Controller
class MasterViewController: UIViewController {
...
#IBAction func searchDatabase(_ sender: Any) {
let CVC = childViewControllers.first as! UINavigationController
let resultVC = CVC.viewControllers[0] as? ResultsVC
result.stores = stores.filter("address = '1234 Blue Street'")
result.tableView.reloadData()
}
...
}
Turns out I had a duplicate variable which was overwriting the orig from above.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! ResultsCustomViewCell
let stores = realm.objects(Store.self) // <- OVERWRITING ORIGINAL //
let currentStore = stores[indexPath.row]
cell.storeNumber.text = "#\(currentStore.storeNumber)"
cell.storeName.text = "\"\(currentStore.storeName)\""
return cell
}

UITableView second load does not update until scroll

I have a view and a tableview which it shows file list. When user click button in view then I open tableview. User click the file list row then I started to download file from my server and when download start I changed the file name like "11111", when download finish change file name like "22222" (now change the file name later I will put progress view)
In first run everything is working correctly. Download and change name working. But in tableview when I come back to view and go to tableview again, download is working but not change then file name in tableview.
What is wrong in my code and how can I show text value?
PS: When print the text value in tableview before return cell, it shows correct text.
My codes:
func setProgressValue(_ dict : NSDictionary){
//Download progress value
let cellProgressValue = dict.value(forKey: "value") as! Float
if cellProgressValue < 1.0{
fileList[cellNum].title = "111111"
updateTable()
}
else{
fileList[cellNum].title = “22222”
updateTable()
}
}
func updateTable(){
self.tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return fileList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "fileCell"
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
cell.textLabel?.text = self.fileList[indexPath.row].title
return cell
}
//Call in viewdidload
func getFiles(){
for index in songList{
let urlString = "https://www.myapis.com/data/files"
Alamofire.request(urlString).validate().responseJSON { response in
let result = JSON(response.result.value)
if let items = result["items"].array {
for item in items {
//print(item)
let id = item["fileId"].stringValue
let title = ["fileName"].stringValue
let file = Files()
file.id = id
file.title = title
self.fileList.append(song)
}
self.tableView.reloadData()
}
}
}
}

UITableView not loading data from API call

In my code I have a search bar that when its search button is clicked, it triggers this function here:
func getStocks(ticker: String) {
do {
try Stocks.getStocks(ticker, completion: {stockList in
self.listOfStocks = stockList
print("Stock item is: \n", self.listOfStocks.popLast())
dispatch_async(dispatch_get_main_queue(), {
self.saveStocks(self.listOfStocks.popLast()!)
self.tableView.reloadData()
})
})
} catch {
print("Failed to get stocks")
}
}
The purpose of this function is to go through my API call, get data for the item the user has specified in the search bar, append it to a global list of items while also saving the most recent item in the global list into Core Data. Later on I have a block of code that sets the text cell label and sets it to the name property of my Stock struct:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("stockItem", forIndexPath: indexPath)
if let label:UILabel = cell.textLabel {
label.text = self.listOfStocks[indexPath.row].name
}
return cell
}
I've checked to make sure the reuse identifier is correct so that wouldn't be the issue.
You first need to track down where in your code is the issue. I would follow these steps to do that.
Confirm that your Stocks.getStocks() static function is working correctly and that the api call is returning valid data. You have not supplied code for this.
Check that your data source, in this case self.listOfStocks is being populated with the data from the API call. Set a breakpoint or use a print statement in the getStocks() method.
`
func getStocks(ticker: String) {
do {
try Stocks.getStocks(ticker, completion: {stockList in
if let list = stockList {
self.listOfStocks = list
dispatch_async(dispatch_get_main_queue(), {
if let last = self.listOfStocks.popLast() {
self.saveStocks(last)
}
self.tableView.reloadData()
})
} else {
print("ERROR: stockList is nil!")
}
})
} catch {
print("Failed to get stocks")
}
}
Review your table view delegate and dataSource delegate methods are correctly setup. Below is how I would check my cellForRowAtIndexPath method.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("stockItem", forIndexPath: indexPath)
if let datasource = self.listOfStocks[indexPath.row] {
textLabel.text = datSource.name
} else {
textLabel.text = "Row \(indexPath.row): NOT set!"
}
return cell
}

Resources