How to reload tableview after adding new entry? - ios

I am creating a cloudkit tableview. I load the app and my tableview appears with my entries from cloud kit.
I then use my add method insertNewObject which adds the record to cloud kit but this does not show up in my tableview. It will only show up on my next run of the app.
func insertNewObject(sender: AnyObject) {
let record = CKRecord(recordType: "CloudNote")
record.setObject("New Note", forKey: "Notes")
MyClipManager.SaveMethod(Database!, myRecord:record)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
This is my add method. I am calling tableview reload as you can see but nothing is happening.
My tableview creation code:
// Tableview stuff --- Done
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
/////// Get number of rows
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
//// FIll the table
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let object = objects[indexPath.row]
cell.textLabel!.text = object.objectForKey("Notes") as? String
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
As requested: Method that saves to CloudDB
func SaveMethod(publicDatabase: CKDatabase, myRecord: CKRecord ) -> CKRecord {
publicDatabase.saveRecord(myRecord, completionHandler:
({returnRecord, error in
if let err = error {
self.notifyUser("Save Error", message:
err.localizedDescription)
} else {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("Success",
message: "Record saved successfully")
}
}
}))
return myRecord
}
My viewdidload method in masterview:
override func viewDidLoad() {
super.viewDidLoad()
// Database loading on runtime
Database = container.privateCloudDatabase
///Build Query
let query = CKQuery(recordType: "CloudNote", predicate: NSPredicate(format: "TRUEPREDICATE"))
///Perform query on DB
Database!.performQuery(query, inZoneWithID: nil) { (records, error) -> Void in
if (error != nil) {
NSLog("Error performing query. \(error.debugDescription)")
return
}
self.objects = records!
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}

You should not reload your entire tableView when you insert a single object. Only do that when you know ALL the data has changed.
To do what you want, this is the order:
Insert a new data object into your datasource (self.objects). Make sure you get the index of where it ends up in the array.
Call insertRowAtIndexPath: with the correct indexPath on your tableView. This will make sure your data and tableView are in sync again, and tableView:cellForRowAtIndexPath: is called for at least your new data object (and possible others, as certain cells might now be reused to display other data).
Note that the order is always: update your data first, then update your UI (the only place I know of that his is hairy is when using a UISwitch).

Related

saving API response to coreData, still shows nil when accessing

func getGenreKeys(complition: #escaping (_ genre : GenreListModel?) -> ())
{
let genreUrl = URL(string: "\(baseUrl)\(genreListUrl)\(apiKey)")!
urlSessionManager(url: genreUrl,toUseDataType: GenreListModel.self) { json in
//json will contain genreList Object , which can be used to get keys
switch json
{
case .success(let genreListData) :
complition(genreListData)
CoreData.shared.saveGenreList(json: genreListData)
case .failure(let error) :
print(error)
}
}
}
this above is the api completion code
func saveGenreList(json: GenreListModel){
let context = persistentContainer.viewContext
let genreList = GenreList(context: context)
json.genres?.forEach({ Genres in
genreList.name = Genres.name
do{
try context.save()
}
catch{
print("error in saving")
}
})
}
this is what i did to save data after completion of api fetch.
var coreGenre : GenreList?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coreGenre?.name?.count ?? 0
this above code is the part of VC that requires to get the coreGenre.name to give the count but it is nill
but when i try to access from viewController by creating a variable of the core data entity class , it returns nill
What's wrong?
I'll base my answer on code there. In your GitHub account where you shared that code.
First issue:
You have:
var genrelist : GenreListModel? {
didSet{
//after getting data a table needs to reload and ui elements needs to be used in main thread only
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
var coreGenre : [GenreList]?
override func viewWillAppear(_ animated: Bool) {
...
ApiManager.shared.getGenreKeys { genre in
self.genrelist = genre
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coreGenre?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
genrelist?.genres?[indexPath.row].id
...
cell.genreLabel.text = genrelist?.genres?[indexPath.row].name
...
}
First issue:
coreGenre IS NEVER SET! genreList is set, but not coreGenre. So don't expect it to be something else that nil!
You didn't write coreGenre = something
So tableView(_:numberOfRowsInSection:) will always return 0.
Make your choice. Either genreList or coreGenre:
ApiManager.shared.getGenreKeys { genreList in
self.coreGenre = genreList.genres
}
&
var coreGenre: [GenreList] {
didSet { DispatchQueue.main.async { self.tableView.reloadData() } }
}
And remove var genreList
Or, remove coreGenre.
That should fix the UITableView, and that's where your question is misleading: There is no CoreData here. In the completion of getGenreKeys(), you return the value. CoreData saves the value in the method, but that's all.
It's:
API -> Parse Value -> Save in CoreData
-> Completion -> Reload TableView to Display
Now in your CoreData stack, you have a method saveGenreList(json:). Assuming it works, you save it but never retrieve it.
There is no NSFetchRequest in your code. Look how to execute fetch in CoreData.
Just stop thinking about CoreData, to understand the issue:
let retrieveValueFromAPI = "I got it"
let savedValue = retrieveValueFromAPI // (1)
return retrieveValueFromAPI
In (1), you'll have an unused variable warning from Xcode. It's exactly the same issue. Here its a "RAM value" instead of a disk value, but that's the same logic. You don't use what you've saved.
You need to continue working and debug. And debug is not only fixing the issue. There are a few steps:
Reproduce the issue
Find its origin/understand what could be the issue
Fix the issue
Finding its origin, and why, are the steps you were missing, either by misconcept, or not enough step by step research in your code.

Removing value from UITableView causes crash

I have a UITableView and when I click on a cell I update a value in firebase to be true. I also implemented the ability to delete the UITableViewCell. The problem is that if the cell has been clicked on (updated) and then deleted the app crashes. When I have a look in firebase the value that has been updated has not been deleted (everything else has been), this only happens if the cell has been updated. This is what I got
// remove task
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let taskToDelete = groupTask[indexPath.row]
if editingStyle == UITableViewCellEditingStyle.delete {
groupTask.remove(at: indexPath.row)
DataService.instance.REF_GROUPS.child(group!.key).child("task").child(taskToDelete.id).removeValue(completionBlock: { (error, refer) in
if error != nil {
} else {
}
})
self.tableView.reloadData()
}
}
// update task value
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? GroupTaskCell else { return }
if cell.isSelected == true {
let selected = groupTask[indexPath.row]
DataService.instance.REF_GROUPS.observe(.value) { (snapShot) in
DataService.instance.updateTaskStatus(desiredGroup: self.group!, selected: true, atIndexpath: indexPath.row, childPath: selected.id, handler: { (complete) in
self.tableView.reloadData()
})
}
} else {
}
self.tableView.reloadData()
}
// update task status
func updateTaskStatus(desiredGroup: Group, selected: Bool, atIndexpath: Int, childPath: String, handler: #escaping (_ taskArray: [Task]) -> ()) {
REF_GROUPS.child(desiredGroup.key).child("task").child(childPath).updateChildValues(["selected": selected])
}
EDIT!
Could not cast value of type 'NSNull' (0x1b5915f18) to 'NSString' (0x1b5921ad8).
2018-01-01 17:38:53.431901+0200 WireUp[587:103199] Could not cast value of type 'NSNull' (0x1b5915f18) to 'NSString' (0x1b5921ad8).
(lldb)
What firebase looks like before I delete the task:
And after:
As you can see it is not removing the value that has been updated and that is what's causing the crash. I appreciate all help.
func addTask(withTask task: String, andPeople people: String, forUID uid: String, withGroupKey groupKey: String?, selectedStatus selected: Bool, sendComplete: #escaping (_ taskCreated: Bool ) -> ()) {
if groupKey != nil {
REF_GROUPS.child(groupKey!).child("task").childByAutoId().updateChildValues(["taskToDo": task, "peopleToDoTask": people, "senderId": uid, "selected": selected])
sendComplete(true)
} else {
}
}
func updateTaskStatus(desiredGroup: Group, selected: Bool, childPath: String, handler: #escaping (_ taskArray: [Task]) -> ()) {
REF_GROUPS.child(desiredGroup.key).child("task").child(childPath).updateChildValues(["selected": selected])
}
func configureTaskCell(taskToDo task: String, nameForPerson name: String, isSelected: Bool ) {
self.task.text = task
self.name.text = name
if isSelected {
self.chechImg.isHidden = false
} else {
self.chechImg.isHidden = true
}
}
Instead of using self.tableView.reloadData() from within the table view delegate functions try updating and deleting cells using the beginUpdate() and endUpdate() methods of UITableView. You won't crash and you won't be reloading the whole table. In Obj-C, it looks like this for removing and updating a cell:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
[tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects: indexPath, nil] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
It seems as though the code you've provided isn't the issue. It's being deleted correctly but as you delete it, it's also updating again with just the selected: true. Somewhere in your code, it'll be trying to fetch the rest of the data, including the selected: true BUT it's not there; resulting in the crash. You may want to do some debugging to see where you're actually updating this selected value to your database and get rid of it

Deleting an object in a tableView cell from Parse

I am attempting to delete certain objects from Parse. The user will be able to save posts and I am trying to implement a way for them to delete them. The saved posts can be found in a TableView.
I currently have the following.
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .Default, title: "Remove") { (action: UITableViewRowAction!, indexPath: NSIndexPath!) -> Void in
let query = PFQuery(className: "SavedObjects")
query.orderByDescending("createdAt")
query.whereKey("savedByUser", equalTo: (PFUser.currentUser()?.username)!)
query.findObjectsInBackgroundWithBlock({ (objects : [PFObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
object.deleteInBackground()
}
}
})
self.tableView.reloadData()
}
deleteAction.backgroundColor = UIColor.redColor()
return [deleteAction]
}
The object.deleteInBackground() does work, but it deletes all objects that were saved by the user. I would like it to only delete the object that the cell represents.
I have also tried to use object.deleteInBackgroundWithTarget([indexPath.row], selector: nil) but have had no luck.
if I understood your question correctly, I think you need to add another query.WhereKey() filtering by the objectId of the corresponding row saved on Parse. This way the call to object.deleteInBackground() will only delete the cell you selected from the TableView. I hope this was helpful!
Valerio

Pull to Refresh: data refresh is delayed

I've got Pull to Refresh working great, except when the table reloads there is a split second delay before the data in the table reloads.
Do I just have some small thing out of place? Any ideas?
viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl?.addTarget(self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged)
self.getCloudKit()
}
handleRefresh for Pull to Refresh:
func handleRefresh(refreshControl: UIRefreshControl) {
self.objects.removeAll()
self.getCloudKit()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
refreshControl.endRefreshing()
})
}
Need the data in two places, so created a function for it getCloudKit:
func getCloudKit() {
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil { // There is no error
for play in results! {
let newPlay = Play()
newPlay.color = play["Color"] as! String
self.objects.append(newPlay)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
} else {
print(error)
}
}
}
tableView:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
let object = objects[indexPath.row]
if let label = cell.textLabel{
label.text = object.matchup
}
return cell
}
This is how you should do this:
In your handleRefresh function, add a bool to track the refresh operation in process - say isLoading.
In your getCloudKit function just before reloading the table view call endRefreshing function if isLoading was true.
Reset isLoading to false.
Importantly - Do not remove your model data before refresh operation is even instantiated. What if there is error in fetching the data? Delete it only after you get response back in getCloudKit function.
Also, as a side note, if I would you, I would implement a timestamp based approach where I would pass my last service data timestamp (time at which last update was taken from server) to server and server side would return me complete data only there were changes post that timestamp else I would expect them to tell me no change. In such a case I would simple call endRefreshing function and would not reload data on table. Trust me - this saves a lot and gives a good end user experience as most of time there is no change in data!

IOS/Swift rendering table after JSON request

I am new to IOS and swift. I am trying to implement an api get request that returns json and then display it in a table. Below is my current code. When I run simulator I am getting the following error:
fatal error: Cannot index empty buffer
If I remove the hardcoded return 3 in the tableView function and instead use doDatItems.count nothing renders in the table because I guess the array of doDatItems starts empty before the get request is made. It seems like a timing thing? How do I ensure the get request is made before the table loads?
import UIKit
class ViewController: UIViewController, UITableViewDelegate {
var doDatItems:[String] = []
#IBOutlet weak var doDatItem: UITextField!
#IBOutlet weak var yourDoDats: UILabel!
#IBAction func addDoDat(sender: AnyObject) {
doDatItems.append(doDatItem.text)
println(doDatItems)
}
override func viewDidLoad() {
super.viewDidLoad()
let urlPath = "http://localhost:3000/dodats"
let url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if((error) != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
} else {
let dataArray = jsonResult["dodats"] as [String]
for item in dataArray {
self.doDatItems.append(item)
}
// println(self.doDatItems)
}
})
task.resume()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
println(self.doDatItems)
cell.textLabel?.text = self.doDatItems[indexPath.row]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I found several problems -
You ViewController should conform to UITableViewDatasource (which is missing, not sure how it went that far)
Do not return 3 when self.doDatItems does not have any items. It will cause a crash. As long as the data loads let the table remain empty. return self.doDatItems.count
Once data is loaded and ready to display from self.doDatItems array, just call reloadData() method of your table view.
Before that, you should have a reference (or IBOutlet) of your tableView so that you can call reloadData() from anywhere.
You need to trigger a page refresh once the data has been received and parsed.
Something along the lines of
self.tableView.reloadData()
In this delegate method, which return number of rows,
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
Don't use static number as 3. Get your Array Count & return the count in here.
After the json response comes, reload the tableView. In objective-c it's done by
[tableView reloadData];

Resources