I've read 3 pages of google results and none worked for me, here's detailed description of the problem:
Every time I launch my app If I tap on the tab bar item that shows the vc that contains my table view it is empty. I have to go to another tab and return to it, just then my table view fetches. Spent 8h trying to fix it already.
Same exact VC shown with a button tap is displayed correctly and loads every time.
What i've already tried:
triple checked if I have:
tableView.delegate = self
tableView.dataSource = self
Tried moving the fetching to viewDidAppear or viewWillAppear, none worked.
Diagnosed it, number of sections is returned but number of rows = 0, cellForRowAt never executes on first tab bar click, even tho my posts are fetched correctly.
Tried fixing any constraints that exist, didn't work.
It simply seems like tableView.reloadData() doesn't work. I surrender, please help me!
You still haven’t provided a very clear picture. At a glance, though, I’d say you need to add a call to your table view’s reloadData() method inside the braces on your call to posts(for:completion:). (I’m guessing about the exact name of that function since you don’t include it.)
If your posts(for:completion:) invokes its completion handler on a background thread then you’ll need to wrap your call to reloadData() in a call to DispatchQueue.main.async() (Since TableView.reloadData() is a UIKit call and you can’t do UIKit calls on a background thread.)
The code might look like this:
StartTestingViewController.posts(for: UUID as! String) { (posts) in
DispatchQueue.main.async {
self.posts = posts
self.tableView.reloadData()
}
}
(I didn’t copy your whole viewDidLoad method, just the bit that calls your function that fetches posts.)
The UITableView.reloadData() method tells your table view “The number of sections/rows of data or their contents may have changed. Please re-ask your data source for contents and re-build your cells.” The table view will ask for the number of sections again, ask for the number of rows in each section, and ask for enough cells to fill the table view’s frame with cells.
If you don’t call reloadData() your table view has no idea that there is new data so it doesn’t reload itself.
I suspect that the reason that you can go another tab and come back and see your data is that the data has been loaded while you were on another tab, and so is available immediately. I’d have to see the exact details of your tab displaying code to be sure.
Related
I'm having a difficult time understanding what I should even be looking for to debug this issue.
I have a UITableView that populates correctly.
I select one of the UITableViewCells which upon selection presents a new view to the user with more detail.
If I click Back on this new window, the original UITableView shows none of the UITableViewCells anymore.
I guess the problem is when you are coming back, your array is being empty somehow. In your first "ViewController" override the "viewWillAppear()" method and print the count of your array in this method. Then load the app and check the print result, then go to second "ViewController" and press the back button and see the print results again. if it shows zero "0" then check your code and try to find out where your array is being empty.
Hope this will help.
I ended up setting the delegate and datasource in the viewDidAppear() rather than the viewDidLoad(). Once I did that, I added a tableView.reloadData() inside of the completion handler for retrieving the data from the API and the tableView was then being populated as expected.
In my tvOS app, I have a collection view where I've set its remembersLastFocusedIndexPath to true. As this is not enough to get this behaviour, I've also overridden a method in my UIViewController like this:
override weak var preferredFocusedView: UIView? {
return collectionView
}
This was working fine until I started reloading the collection view for some reasons. It does work if, being the collection view visible, I call collectionView.reloadData().
This doesn't work, tho, if I do the reload when the collection view is not visible, for example, when I'm in a detail view after tapping one of the items. When I come back to the grid, the focused index path is not the one I tapped.
As a workaround, I'm managing this non-ideal scenario:
As the reload is triggered by fresh data coming from my backend, I only call collectionView.reloadData() when the collection view is visible (because I know that the last focused index doesn't change here).
I call collectionView.reloadData() in viewDidAppear(animated: Bool) to have the latest content available when the user comes back.
How can I do this properly? Thanks in advance.
Recently I got this issue also, my situation is pretty much same to you.
I also called collectionView.reloadData() in viewDidLoad and viewWillAppear
And then
I changed from
collectionView.reloadData()
to
collectionView.reloadItemsAtIndexPaths(collectionView.indexPathsForVisibleItems())
It works for me most of time, there is still approximately <5% chance to mess up the last focus index
One option is to set remembersLastFocusedIndexPath to false, and implement indexPathForPreferredFocusedViewInCollectionView: to achieve the same behavior.
I declare an array at the beginning of my UITableViewController:
class ArchiveTableViewController: UITableViewController {
var dataSource: [[Book]]!
(…)
And then I have a function getDataSource() that updates the array from a database.
My first thought was to call it at viewWillAppear, but it seems that the table view loads before that, so it ends up not matching the array.
I could call the function from every single table view method, but that seems a little stupid. So where is the best place to do it? It must get called every time the view appears, so viewDidLoad won't work, but it must get called before the tableview methods, so viewWillAppear won't work either. It's like I need something in between. Or is there a better way to do what I want?
Thanks in advance,
Daniel
Edit: I should have added, that dataSource array is made up of other arrays, each representing a section in the table view. I get the number of sections and the number of rows in a section from the array too, so it must stay the same throughout the tableview methods or the app will crash, it might try to populate a row that shouldn't exist anymore, for instance.
Edit 4: Ah, ok, I got it! I'm sorry for wasting everyone's time looking for complicated answers. I just had to call getDataSource() from both viewDidLoad and viewWillAppear, and call tableView.reloadData() only in viewWillAppear.
I was trying all combinations of where to put things and with reloadData inside getDataSource() it would sometimes get called repeatedly forever until it crashed… or on other attempts I would call getDataSource() from viewWillAppear and not from viewDidLoad, and then it would crash the first time… anyway, I missed the most obvious combination and now I can't understand how I didn't see it before. Thank you all so much for your time.
Set it in override func loadView().
EDIT: As a general practice this is how you should go it:
Show loading overlay on screen while table data is being fetched.
Once data is available:
2.1. remove the loading overlay.
2.2. update data source model.
2.3. reload your table view so latest model could be picked.
Are you are showing other view controller and then going back to the one with tableView and that's why you want to update the tableView every time the view appears?
If so - it should just work the way you initially tried, if you put the getDataSource in viewWillAppear() and it calls tableView.reloadData() in the end (as you have it here in the source sample) then it will effectively show just what you have prepared for it (all the tableview methods will be called again after reloadData() even if they were already called before).
So I would advise debugging why it is not working when you call it from viewWillAppear(). Please add some screenshots or other data of how it is not correct with this method.
As a sidenote, if getDataSource() takes significant time, it may delay showing your view controller if you put it in viewWillAppear. In such case you'll need to do the way #Abhinav laid out for you (but you can start it from viewWillAppear).
Update: You're right, depending on how you've written numberOfSectionsInTableView() and tableView(_, numberOfRowsInSection) you may need to call getDataSource() also from viewDidLoad() or else you may crash because you're not handling empty datasets.
In that case you don't need tableView.reloadData() because table view methods will be called anyway, so as you noted, it's good to separate it from your getDataSource().
Regarding getDataSource() called repeatedly until crash - it probably happened when you added getDataSource() in one of UITableViewDataSource methods (like numberOfSectionsInTableView()) because reloadData() inside getDataSource() would trigger this method again.
Every function needs to access your var dataSource. So that's where the call to getDataSource must go: You create an optional variable that contains either dataSource or is nil, and then you give dataSource a getter which returns that optional variable unwrapped if not nil, and calls getDataSource otherwise.
I'm very new to swift programming. I have been playing around with this for a while, but I am not getting anywhere so asking here.
I have a tableview which I can load the data into the view from CoreData no problem. I have an ADD button at the top that segue's to a new tableview, with a long list of options they can pick from. This tableview also works fine, and includes a search bar.
When the user selects an item row from the second tableview, it inserts that item into CoreData and segue's back to the first tableview. This is where my problem is, the data does NOT update on the visible view.
I call tableview.reloaddata() and I can see my code calling the fetchedResultsController with the new query that would return with the new data. But the code never gets to the cellForRowAtIndexPath func so therefore the visible data view never changes. It remains the same display that was visible when the add button was pressed.
How does the visible data get updated? What am I missing?
When using an NSFetchedResultsController, if you want it to "automatically" update the contents of the tableview then there are a couple of things you need to make sure of...
You have to become the NSFetchedResultsControllerDelegate and implements the methods necessary for the updating of the table view. These are quite lengthy and can be found on the Ray Wenderlich website. The code on here is in Objective-C but it's fairly easy to convert to Swift.
The second thing you need is to make sure that the core data update is done on a background thread. Again the website linked above shows this.
Once you've done that then you don't actually need to run [tableview reloadData] because the fetched results controller methods will manage everything for you.
A simple tableviewController, empty. A modal that can be launched from a button. No data in the data source for the tableview, and no rows displayed.
I open the modal, use it to add an item, and return to the tableview controller. The tableview updates itself automatically, and the new row is displayed.
I add a second item. The table view does NOT update automatically.
I can tell by logging inside numberOfSectionsInTableView that even if I go to add the first item and cancel, the tableview refreshes - it asks for the number of sections, rows, and the cell to display (if there is one). But not if there is one pre-existing row in the table.
I've scoured my code but can't find anything that would cause the tableview to know when the first item is added, but none afterwards.
Any ideas?
EDIT - I have further narrowed my issue so will close this shortly and have posted a more specific question at Why does an empty tableView check the number of sections but a non-empty one does not?
from Apple's documentation:
UITableView overrides the layoutSubviews method of UIView so that it
calls reloadData only when you create a new instance of UITableView or
when you assign a new data source.
maybe you are setting data source of table view after showing that modal? So at the second time data source does not changes and tableView does not update.
another key (I'm not sure about that) may be the showing of the modal first time. The method -addSubview for modal is causing -layoutSubviews.