Why my cells do not load at the same time? - ios

I'm using background thread for getting my data from the network and main thread for presenting my cells:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {
let _ = self.http.getInfo() { (result) in
self.cellsArray = result
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
completion(completed: true)
}
})
where self.http.getInfo is a NSURLSession.sharedSession().dataTaskWithRequest method for taking my network infos.
And when I run my app my cells start showing separately as: the first appears, in a second the next one appears, in 2 seconds the next one and etc.
Why they do not appear at the same time? And also, because of the background and main thread using, my UITableView jumps when I implement infinite scroll and append new cells to exist cell array.
How can I fix it?

write self.tableView.reloadData() this statement in completion handler of NSURLSession.sharedSession().dataTaskWithRequest.

Related

Problems with asynchronous data, UITableView, and reloadRowsAt

I'm trying to implement a tableView that has 4 different possible prototype cells. They all inherit from base UITableViewCell class and implement its protocol.
For two of the cells there's asynchronous data fetching but one in particular has been giving me fits. The flow is as follows:
1) Dequeue reusable cell
2) Call configure
func configure(someArguments: ) {
//some checks
process(withArguments: ) { [weak self in] in
if let weakSelf = self {
weakSelf.reloadDelegate.reload(forID: id)
}
}
}
3) If the async data is in the cache, configure the cell using the image/data/stuff available and be happy
4) If the async data is NOT in the cache, fetch it, cache it, and call the completion
func process(withArguments: completion:) {
if let async_data = cache.exists(forID: async_data.id) {
//set labels, add views, etc
} else {
fetch_async_data() {
//add to cache
//call completion
}
}
}
5) If the completion is called, reload the row in question by passing the index path up to the UITableViewController and calling reloadRows(at:with:)
func reload(forID: ) {
tableView.beginUpdates()
tableView.reloadRows(at: indexPath_matching_forID with: .automatic)
tableView.endUpdates()
}
Now, my understanding is that reloadRows(at:with:) will trigger another dataSource/delegate cycle and thus result in a fresh resuable cell being dequeued, and the configure method being called again, thereby making step #3 happy (the async data will now be in the cache since we just fetched it).
Except...that's not always happening. If there are cells in my initial fetch that require reloading, it works - they get the data and display it. Sometimes, though, scrolling down to another cell that requires fetching DOES NOT get the right data...or more specifically, it doesn't trigger a reload that populates the cell with the right data. I CAN see the cache being updated with the fresh data, but it's not...showing up.
If, however, I scroll completely past the bad cell, and then scroll back up, the correct data is used. So, what the hell reloadRows?!
I've tried wrapping various things in DispatchQueue.main.async to no avail.
reloadData works, ish, but is expensive because of potentially many async requests firing on a full reload (plus it causes some excessive flickering as cells come back)
Any help would be appreciated!
Reused cells are not "fresh". Clear the cell while waiting for content.
func process(withArguments: completion:) {
if let async_data = cache.exists(forID: async_data.id) {
//set labels, add views, etc
} else {
fetch_async_data() {
// ** reset the content of the cell, clear labels etc **
//add to cache
//call completion
}
}
}

Refresh Control Issue with custom cells - Swift 4 IOS 11

I am building an iOS app and I am trying to implement a pull-down refresh control on my project. The data is fetched correctly from an API and displayed on my table. But the problem rises when I do pull down to refresh. The following situations happen:
If I pull down for a long distance from the top, and the tableview.reloadData() function is called, the cells in the non-visible portion of the table come with the default tableview cells on top of them, overlapping...
if I pull down multiple times in quick succession the same issue happens.
I believe that it is because tableview.reloadData() is called multiple times in quick succession. But why are the default cells getting dequeued on top of my custom cells? Here is the section of code in the function to handle the pulldown:
#objc func refreshFunc(){
//let offset = scrollView.contentOffset.y
if myRefreshControl.isRefreshing{
readJson { (activities) in
self.activities = activities
self.tableView.reloadData()
self.myRefreshControl.endRefreshing()
}
}
}
Any help would be appreciated. Thanks in advance
UPDATE:
Changing the code to the code below seems to remove the error, but the problem is that now I need to pull down twice in order to get the results updated on the table:
#objc func refreshFunc(){
readJson { (activities) in
self.activities = activities
}
self.tableView.reloadData()
self.myRefreshControl.endRefreshing()
}
Please note that running the reloadData on the main thread gives the same result, I still need to pull down twice to update.
Please try to give some delay before refresh table view may it resolve your problem.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayTime) { [weak self] in
self?.tableView.reloadData()
}
Hope it works
Cheers :)
DispatchQueue.main.async {
self.tableView.reloadData
}
Try this, always, if u want change UI you have to call on main thread

tableView reloadData in loop not drawing cells on each pass

I have a tableView.reloadData() call in a loop in several places in my app. the basic idea is like this:
let dataSourceArray = []
for arrayItem in anotherArray {
dataSourceArray.append(arrayItem)
self.tableView.reloadData()
}
What I'm trying to achieve is to have the tableView draw each cell as it is loaded into the model array.
What is currently happening is that all the cells appear once the loop is complete.
This is all being done in the main thread, however I tried anyway to dispatch to main thread for the reloadData call. That made no difference. I tried tableView.layoutIfNeeded() after reloadData and that also made no difference.
Placing break points in the cellForRowAtIndexPath and heightForRow... methods indicate normal tableView calls as expected at each pass in the loop. However for some reason the cells don't draw on screen until the loop is completed.
Is there anyway to force the tableView to draw the cell at each pass other than what I have tried above?
I belive the reason is because the looping of your elements will be done very quickly (milliseconds) therefore you will not notice the incremental changes.
You need to run outer loop in global queue like below and dispatch reloadData to main queue.
var dataSourceArray = [ String ]()
#IBAction func
DoA( _: Any? ) {
let anotherArray = [ "A", "B", "C" ]
DispatchQueue.global().async {
for arrayItem in anotherArray {
self.dataSourceArray.append( arrayItem )
DispatchQueue.main.async {
self.tableView.reloadData()
}
Thread.sleep( forTimeInterval: 1 )
}
}
}
If you want a neat animation thats visible then you can probably force a timer on it, between each append; with a UIView.animate or UIView.transition... I'd prefer the transition method, it gives you a few options that make your changes even neater.
Here is a sample of what you can do (dont copy paste it directly, adjust it to your own, i wrote it in playground without testing it)
var i = 0
func addItem(){
if i == anotherArray.count {
return
}
UIView.transition(with: tableView, duration: 0.3, options: [.allowAnimatedContent, .transitionCrossDissolve], animations: { // content that is going to animate
dataSourceArray.append(anotherArray[i])
tableView.reloadData()
}) { // completion block
i += 1
addItem()
}
}
Other than that, you can change the content mode of your tableView (most probably from the storyboard) to be set to .top so on every change your tableView keeps on sticking to the top rather than jumping around (https://developer.apple.com/documentation/uikit/uiview/1622619-contentmode). This specially helps if you're using autolayouts

Reloading data from a tableView within a collectionViewCell

I have a tableView inside a collectionViewCell and I get an error when I try to reload the data.
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.6.21/UITableView.m:1610
I tried using Dispatch.main.async and it seems to get rid of the problem. The only thing is that it doesn't reload the data and nothing changes in the tableView
func cleanItems(completion: #escaping (Bool) -> Void) {
Dispatch.main.async {
cell.tableView.beginUpdates()
// Make changes in the Data Source
cell.tableView.deleteRows(at: selectedItems, with: .fade)
cell.tableView.endUpdates()
// Reloading sections accordingly depending on what the user has deleted
// Do I need to reload data here again? It used to work without Dispatch, but it wasn't stable
cell.tableView.reloadData()
// Updating items with if statements to reload data in Firebase
completion(true)
}
}
This doesn't reload the data at all and nothing seems to change. The good thing is that I don't get a random crash, which was the case before implementing Dispatch.main.async
I've retrieved the numberOfRows in each section to see how many rows there are after ending updates.
print(cell.tableView.numberOfRows(inSection: 1))
and I get the same number of rows that are in the current view.
This is crucial, because if the tableView sections are all zero, the collectionViewCell should disappear. And we never get here in the completion block as it says that the numberOfRows has never changed. Leaving us with a non updated tableView.
I solved this by moving Dispatch.main.async outside the function call.
Dispatch.main.async {
cleanItems(completion: { (success) in
etc.
})
}

chaining UITableview Updates on main thread

So what i am attempting to do is conceptually very simple however I have not been able to find a solution for it:
I am trying to remove cells from a tableView animated with the:
self.coolTableView?.deleteRowsAtIndexPaths
function, to do this I change the dataSet and perform this action, right after it is done i would like to change the data set again and use:
self.coolTableView?.insertRowsAtIndexPaths
to reflect and animate the second change to the dataset.
The Problem I run into is that if I use:
dispatch_async(dispatch_get_main_queue()) { () -> Void in
//Update tableview
}
they seem to lock each other out, the used memory just keeps skyrocketing and it hangs. I am assuming they are interfering with each other. now my next thought was to change the code to sync so:
dispatch_sync(dispatch_get_main_queue()) { () -> Void in
//Update tableview
}
however the first update hangs and and ceases operation. With the research I have done it sounds like I am putting the execution of the block in the main queue behind my current running application and vuwala, that is why it hangs.
what is the correct way to block execution until I can complete an animation on the main thread so i do not blow up my data source before animations can take place?
The animations in iOS take a block that they can execute when the animation terminates. Try putting operations on coolTableView into a closure (remember about unowned self to not create memory leaks) and pass it as completion closure to your animation.
example:
let someAnimations: () -> Void = {
//some animations
}
let manipulateTableView: (Bool) -> Void = {
isAnimationFinished in
// perform some data manipulation
}
UIView.animateWithDuration(0.5, animations: someAnimations, completion: manipulateTableView)

Resources