Swift - Updating a UITableView with remote JSON data - ios

i am trying to access data from a remote json file. When something is typed into the searchbar a new NSURLSessionDataTask is made to access the json file. I have successfully been able to extract all the data i need from the json file, but have not been able to update the table view as soon as the data comes in. To show the data i must either scroll so one cell is not visible on the screen. This will make it so that only that one cell has the correct values in it, alternatively, i can also type in a other letter into the searchable which will generate a new json file, but the data from the previous request is only shown now.
I have tried updating the table view in the main thread but that doesn't seem to make any difference. I have also tried multiple other methods, but still no luck.

You can try like this
if let resData = swiftyJsonVar["loginNodes"].arrayObject {
self.arrRes = resData as! [[String:AnyObject]]
}
if self.arrRes.count > 0 {
self.tableView.reloadData()
}
Note :arrRes is the name of array.
complete details are :How get JSON Data in Swift and add it to URL

To get the best answer you'll want to post code, specifics, and maybe even a link to the full project, but based on what you are saying the data is loaded in the code properly and is not being refreshed in the table view.
To fix this you first need a reference to the table view that is not loading, for this example I'll call it tableView. Then you can force it to reload all the visible data by calling
tableView.reloadData()
After your JSON data has finished loading.

When your data is loaded successfully just call
self.table.reloadData()
What it will do is it will populate the cells again with the updated data.

Related

How to handle data deletions in SwiftUI (iOS) without crashing the app

I have a SwiftUI calendaring app with a UI similar to the built-in Calendar.app. I'm getting crashes whenever I try to delete events. The overall lifecycle of my app is as follows:
Download calendar data from server and populate models ([Events], [Users], [Responses] etc)
Transform the source data into a more structured format (see https://stackoverflow.com/a/58583601/2282313)
Render list view of events, each event linking to a Detail View and an Edit modal (very similar to calendar.app)
When an event is deleted, I tell the server to delete the event (if it's a recurring event, the server will delete multiple events), then refresh my data from the server by re-downloading the data, re-populating the models and re-generating the structured data (which causes the list to refresh).
When I do this, I get crashes coming from my calculated values because event data displayed in the detail view is no longer available. For example, I get the array index of a user's RSVP as follows:
var responseIndex: Int {
userData.responses.firstIndex(where: { $0.user == response.user && $0.occurrence == response.occurrence })!
}
I thought this was because I hadn't dismissed the view displaying the deleted event before updating the data, but even if I delay the data refresh until the view is no longer displayed, I still get the crash (SwiftUI seems to keep these views in memory).
What is the right way to handle data deletion? Do I need to keep deleted events in my UserData EnvironmentObject and just mark them as "deleted/hidden" to avoid this issue, or is there a better way to handle it?
There's quite a bit of code involved in this, so it's tricky to provide a sample I'm happy to add relevant bits if asked.
EDIT: I found this article which clarifies something really well: https://jasonzurita.com/swiftui-if-statement/
SwiftUI is perfectly happy to try and render nil views, it just draws nothing. Counter-intuitively, a good way to avoid crashes and make the compiler happy is to set your code up around this.
Original "answer" follows...
I don't know if this is the "right" way to do this, but I ended up making sure that none of my UserData is ever deleted to avoid the crashes. I added a "deleted" bool to my Occurrence (i.e. Event) object, and when I refresh my structured data, I get the latest data from the server, but check to see if any of the old ones are no longer present. Steps are:
Get latest list of occurrences from server
Create a second init() for my structured data which takes the existing data as an argument
Inside the new init(), flatten the structured data, check for deleted items against the new data, update data which hasn't been removed, cull duplicates, then merge in net new data. Once that's done, I call my original init() with the modified data to create new structured data
Code looks like this:
init(occurrences: [Occurrence], existing: [Day]) {
// Create a mutable copy of occurrences (useful so I can delete duplicates)
var occurrences = occurrences
// Flatten the structured data into a plan array of occurrences again
var existingOccurrences = existing.compactMap({ $0.occurrences }).flatMap { $0 }
// Go through existing occurrences and see if they still exist.
existingOccurrences = existingOccurrences.map {
occurrence -> Occurrence in
let occurrenceIndex: Int? = occurrences.firstIndex(where: { $0.id == occurrence.id })
// If the occurrence no longer exists, mark it as "deleted" in the original data
if occurrenceIndex == nil {
var newOccurrence = occurrence
newOccurrence.deleted = true
return newOccurrence
// If it still exists, replace the existing copy with the new copy
// (in case it has changed since the last pull from the server)
// Remove the event from the "new" data so you don't get duplicates
} else {
let newOccurrence = occurrences[occurrenceIndex!]
occurrences.remove(at: occurrenceIndex!)
return newOccurrence
}
}
// Merge the existing data (with deleted items marked) and the updated data (with deleted items removed)
let finalOccurrences = existingOccurrences + occurrences
// Re-initialize the strutured data with the new array of data
self = EventData(occurrences: finalOccurrences)
}
Once this was done, I had to update my code to make sure I'm always using my structured data as the source of truth (which I wasn't doing before because accessing the "source" flat data was often easier, and I've updated my ForEach in my list view to only render a row if deleted is false.
It works! It's perhaps a sub-optimal way to solve the problem, but no more crashes. Still interested to hear better ways to solve the problem.

Reload UiTableView After Data Has Been Retrieved From Database - Swift

How do I reload a UiTableView only after the data has been loaded from the database. I have a datasource class that contains the function to load in all the data, and stores the data after it has been loaded into an array.
When I access the data using the Tableview though it displays nothing because the tableview loads before the database has returned its data. The only way to load the data is for me to have a button that manually reloads the tableview.
How do I cause the tableview to automatically reload once the array in the datasource object has been populated from the database?
Being a new programmer it took me a while to learn this was even possible, but I just realized completion blocks are the way to go. It will carry out some specified code only after your function has finished fetching from the database. In this case, I can update the tableview AFTER I've retrieved the data from the database. Here's an example.
Function definition:
func getDataFromDatabase(someInput: Any, completion: #escaping(String?) -> Void) {
let retrievedData: String = databaseCallFunction()
// 'Get data from database' code goes here
// In this example, the data received will be of type 'String?'
// Once you've got your data from the database, which in this case is a string
// Call your completion block as follows
completion(retrievedData)
}
How to call the function:
getDataFromDatabase(someInput: "Example", completion: { (data) in
// Do something with the 'data' which in this example is a string from the database
// Then the tableview can be updated
let tableView.title = data
tableView.reloadTableView
})
The code in the completion block will happen AFTER the database call has completed.
Why not just call tableView.reloadData() once you've populated your data? It'll repopulate the entire table (well technically it'll only re-create whatever cells are visible).
The point is that your tableView:cellForRowAt: method should have the ability to access your data so as to fill in each table cell appropriately based on that data. Anytime your data changes, just call tableView's reloadData() again. It's efficient in that only the visible cells will be created, and the rest will be created whenever they are about to become visible, if ever.
Also, tableView:numberOfRowsInSection: should similarly be looking at your data to judge how many rows shall exist. It too will be called again during each call to reloadData().
Your data should always be prepared to be interrogated by the tableView delegate methods, even when it doesn't contain anything yet. When there's no data, your tableView:numberOfRowsInSection: would decide there should be 0 rows.

AWS DynamoDB queries using Swift

I am using swift and AWS DynamoDB for mobile app. I followed the tutorial and can save data successfully. However , when I try to load data , i found I the saving and loading data always come after all tasks in the viewdidload finished, so I can not pass the data out in the same view? Is there any way to save or retire data immediately ?
below is my code
mapper.query(Table.self, expression: queryExpress).continueWith{(task: AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
print("test")
if let error = task.error as NSError? {
print("The requst failed. Error: \(error)")
}
if let paginatedOutput = task.result {
for item in paginatedOutput.items
{
print("quring")
//pass info out to array
}
}
return nil
}
Fetching data from the network is an asynchronous action. You can't delay loading the screen while it completes. It may take a long time. It might not ever complete.
Your view controller must handle the case that it doesn't have data yet, and update itself when that data becomes available. The first step to this is avoiding making network queries in your view controller. View controllers should never directly query the network. They should query model objects that outlive the view controller. The model objects are responsible for making queries to the network and updating themselves with the results. Then the view controller will update itself based on the model. The name for this pattern is Model View Controller and is fundamental to Cocoa development. (Search around for many tutorials and discussions of this pattern.)
But regardless of where you make the queries and store the data, you will always have to deal with the case where the data is not yet available, and display something in the meantime. Nothing can fix this in a distributed system.
When the query finishes successful, load the data into your view. You can send the query in your viewDidLoad method, but you need to present the data when it arrives using another method you call when the data did arrive.

iOS Core Data Fetch Request, how to use

I am using Core Data to see whether messages in a table view have been seen before by the user. The way I do this is to save the message Id to Core Data the first time it is seen, and then I run a fetch request when I update the table view to see if there is an entry in the persistent memory with the same Id.
Now what I want to know is how I should most effectively implement my fetch request, based on how time consuming it is. Should I either run a request that returns all saved message Ids as an array when the view is loaded, and then in cellForRowAtIndexPathcheck if that array contains that cell's message Id, or run the fetch request with a predicate in cellForRowAtIndexPath? The latter would be my preferred method, but If i have 100 or so cells I wondered if this would be poor etiquette.
Any help would be very much appreciated.
This is my fetch Request :
func persistQuery(predicateValueString: String!) -> Bool! {
let fetchRequest = NSFetchRequest(entityName: "LogItem")
let predicate = NSPredicate(format: "itemText == %#", predicateValueString)
fetchRequest.predicate = predicate
var didFindResult = true
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
if fetchResults.count == 0 {
didFindResult=false
}
}
return didFindResult
}
The best way is to use a NSFetchedResultsController. It will optimize the fetching and the memory footprint as well. It is specifically designed for table views.
To get started, take a look at the Xcode template (Master/Detail, check Core Data). It is really quite simple.
Make sure you also implement the delegate methods - they will automatically be called when your managed objects change, so there is only minimal code that is executed to update the UI (an only if the object is actually on screen).
Presumably each of your table view cells represent a LogItem (the NSManagedObject subclass) with a property to indicate the read status. Once you change that, the delegate method will try to update it based on the index path.
That's all there is to it. With the fetched results controller you get a lot of optimization for free, so I would strongly recommend using it whenever you populate a table view with Core Data entities.

iOS Swift - TableView data source - saving data when app close

I'm working on an swift application that uses a TableView. It's cells are filled-up with data that comes from a remote server. That's how my app works now, but I want to change this mode because I'm sure that it's not the correct way to do things:
First: when the tableview did load, I call a function that get data (json) from server with this method:
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue())
After that, I save my data as an object in NSUserDefaults. The object contains strings such as username, description, location, picture... etc. I know that this is not correct to store images in NSUserDefaults even if these images are not so big...
Second: I fill up table's cells with data obtained from NSUserDefauts, where I have stored my json data.
First of all I want to know how to auto append data to the table when the user hits the last cell of the table, like all apps that uses tableviews for fetching user posts...
In Instagram, after the you close the app, disconnect from the internet, and reenter the app, it will show you the first 10 cells from the last table you've seen before closing the app, so Instagram saves somehow, somewhere, maybe in coredata, data for the first 10 cells.
How do you suggest me to do this?
Question is not particular.....
Load Data error.
Or you fetch the result from coreData error?
CoreData can save View and some informations.
Post your code.

Resources