Who is calling reloadData method for UITableView - ios

It can sounds weird but I don't understand why my tableView is showing cells.
I got array of items that should be shown in cells but I don't run reloadData method of my tableView anywhere in my code. It seems that some of app components or maybe frameworks inside app is calling reloadData method and I want to find out which one?
How it can be done?

A table view loads itself the first time it is added to the window hierarchy. You don't need an explicit call to reloadData for the table to load itself initially.
If you want to see how this is really done, put a breakpoint on your table view data source methods and bring up your table view. Look at the stack trace in the debugger to see the sequence of events.

If your data preparation takes some time and you do not want the table view to show any data initially you could use an approach like this:
class TableViewController: UITableViewController {
var someDataSource: [Any]!
var dataSourcePrepared = false {
didSet {
tableView.reloadData()
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
guard dataSourcePrepared else { return 0 }
return someDataSource.count
}
func doSomePreparationStuff() {
// ...
// ...
someDataSource = ["Some", "Content"]
dataSourcePrepared = true
}
}
In this case I used a Bool variable dataSourcePrepared which is false initially. As soon as you have prepared your content set it to true and the table view gets reloaded.

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
}
}
}

UICollectionView state restoration: restore all UICollectionViewCells

I searched a lot through Google and SO, so please forgive me, if this question has already been answered!
The problem:
I have a UICollectionView with n UICollectionViewCells. Each cell contains a UIView from a XIB file. The Views are used for data entry, so all cells have a unique reuseIdentifier. Each View has also a unique restorationIdentifier. Everything works in normal usage, but not when it comes to state restoration:
The first 3 or 4 cells are getting restored properly because they are visible on the screen on startup, but the remaining cells, which are not visble, are not getting restored.
Current solution:
So I've discovered so far that a View is only restored if it's added to userinterface at startup.
My current working solution is to set the height of all cells to 1 in the process of restoring. Now every cell is loaded and all views are restored.
When applicationFinishedRestoringState() is called, I reload the CollectionView with the correct height.
Now my question is: I'm not happy with this solution, is there a more clean way to achieve restoring of all the UIViews?
I think you are getting a bit confused between your data model and your views. When first initialised, your table view is constructed from a data model, pulling in stored values in order to populate whatever is in each cell. However, your user does not interact directly with the data model, but with the view on the screen. If the user changes something in the table view, you need to signal that change back up to the view controller so that it can record the change to the data model. This means in turn that if the view needs to be recreated the view controller has the information it needs to rebuild whatever was in the table when your app entered the background.
I have put together a simple gitHub repository here: https://github.com/mpj-chandler/StateManagementDemo
This comprises a CustomTableViewController class which manages a standard UITableView populated with CustomTableViewCells. The custom cells contain three switch buttons, allowing the state of each cell to be represented by an array of Boolean values.
I created a delegate protocol for the cells such that if any of the switches is tripped, a signal is sent back to the view controller:
protocol CustomTableViewCellDelegate {
func stateDidChange(sender: CustomTableViewCell) -> Void
}
// Code in CustomTableViewCell.swift:
#objc fileprivate func switched(sender: UISwitch) -> Void {
guard let index : Int = switches.index(of: sender) else { return }
state[index] = sender.isOn
}
// The cell's state is an observed parameter with the following didSet method:
fileprivate var state : [Bool] = Array(repeating: false, count: 3) {
didSet {
if state != oldValue, let _ = delegate {
delegate!.stateDidChange(sender: self)
}
}
}
CustomTableViewController is registered to the CustomTableViewCellDelegate protocol, so that it can record the change in the model as follows:
// Code in CustomTableViewController.swift
//# MARK:- CustomTableViewCellDelegate methods
internal func stateDidChange(sender: CustomTableViewCell) -> Void {
guard let indexPath : IndexPath = tableView.indexPath(for: sender) else { return }
guard indexPath.row < model.count else { print("Error in \(#function) - cell index larger than model size!") ; return }
print("CHANGING MODEL ROW [\(indexPath.row)] TO: \(sender.getState())")
model[indexPath.row] = sender.getState()
}
You can see here that the function is set up to output model changes to the console.
If you run the project in simulator and exit to the home screen and go back again you will see the state of the tableView cells is preserved, because the model reflects the changes that were made before the app entered the background.
Hope that helps.

UISegmentedControl behaves in a weird way on UITableView Header

I created a Custom Cell to replace UITableViewSectionHeader with a protocol to process when UISegmentedControl.index is changed so i can order the table by name or value:
import UIKit
protocol OrdenarTableViewDelegate {
func ordenarTableView(cell: OrdenarTableViewCell)
}
class OrdenarTableViewCell: UITableViewCell {
var delegate: OrdenarTableViewDelegate?
#IBOutlet weak var segmentedOrdenar: UISegmentedControl!
#IBAction func alteraOrdenacao(sender: UISegmentedControl) {
delegate?.ordenarTableView(self)
}
On the UITableViewControler I referenced the protocol
class SelecionadasTableViewController: UITableViewController, UITextFieldDelegate, OrdenarTableViewDelegate {
and implemented it:
// MARK: - Ordenar Delegate
func ordenarTableView(cell: OrdenarTableViewCell) {
if cell.segmentedOrdenar.selectedSegmentIndex == 0 {
listaCervejas = bancoDeDados.selecionaCervejas(false)
} else {
listaCervejas = bancoDeDados.selecionaCervejas(true)
}
ordenarCervejas = cell.segmentedOrdenar.selectedSegmentIndex
tableView.reloadData()
}
When I select the index 1, the app works just fine, but when index 0 is selected the action is only performed if click on a UITextField on any other cell or I pull the table down for refreshing (which by the way is not implemented to refresh)
Is there anything that I'm missing? Because it just feels really awkward.
I notice that the problem only occurs when I click one option and then the other on the UISegmentedControl without touching nowhere else on the screen...
U can created a Custom View to replace tableHeaderView, try it.
When you reload the tableview, everything added to the table view will be reloaded. Check the selected segment index every time and reset this after table reloading.
I suspect that the update of the cell is not executed. Is the reloadTable executed on the mainThread?
Include a print log statement in CellForRowAtIndexPath to find out.
I actually found the solution. You can drag the IBAction to the tableViewController instead. Then it works just fine!!
Thank you all for the help.

Calling reloadData through delegate

I have a table view which conforms to custom protocol FoodItemProtocol and it implements its funciton:
func foodItemWasTaggedAsFavorite() {
tableView?.reloadData()
print("foodItemWasTaggedAsFavorite")
}
After foodItem is tagged as favorite, this function is called and print statement is executed, however table view is never reloaded.
I realized I don't actually need to use delegation for this, it works fine if I call to reloadData() in viewDidAppear(). But still I'd like to know why it's not working through delegation? I've even tried to call reloadData() on main thread like this:
dispatch_async(dispatch_get_main_queue()) {
tableView?.reloadData()
}
But I got same result.
If you are calling the delegate method from a different view controller, tableView will be nil.
To check this, modify foodItemWasTaggedAsFavorite to be:
if let tableView = tableView {
tableView.reloadData()
print("foodItemWasTaggedAsFavorite")
}
Now check if the print statement is being printed. I'm pretty sure it won't, because tableView is nil.
However, in viewDidAppear:, the table view has already been loaded, so it isn't nil.
Also, there is no reason to reload the data if the table view isn't on screen anyways.

Hide a static cell of Tableview in swift

I've an application that works with a static cells table view. In some cases in need to hide a few cells and when needed show them again. How can i do this ?
To do what you wanna do I work with numberOfRowsInSection tableview's method using a bool variabile
if section == 3 {
if !hideTableSection! { return 1 } else { return 2 }
}
Every time I need to hide some cell I change the bool variable then I reload my table.
It can manage only the latest rows of every section but I didn't find nothing better...

Resources