Reloading whole ViewController after custom Button pressed in TableView - ios

I added 2 UIButton in my custom UITableViewCell.
When pressed something is done with the displayed object(in this case a User). Now I want the row/cell to disappear. My Idea was to reload the screen via triggering the viewDidLoad() and viewDidAppear() functions,
since i use PFQueries to obtain user data and display them in my table view.
What happens is, that other than deleting that row since my query shouldn't find the data, it just adds the same things again.
Is there a better way to solve this? I want to delete the row and redo my Query.

To delete a row from a table, you can use a function like this:
// Add this function to your ViewController
func tableDeleteRow(indexPath: NSIndexPath) {
// IMPLEMENT ME:
// first, remove the item from the data that drives the tableView.
// This is what I do. Yours will be different.
// self.tableData.removeAtIndex(indexPath.row)
// tell the table to delete the row
dispatch_async(dispatch_get_main_queue()) {
// Code
print("remove from table")
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
If you are looking to reload the entire table and redo the data you got from the server, do this:
// This is pseudo code.
func tableRefresh() {
// this is a pseudo code function.
// replace it with your own.
get_data_from_server() {
(data, response, error) in
// do something with data
dispatch_async(dispatch_get_main_queue()) {
print("refresh table")
self.tableView.reloadData()
}
}
}

Related

Who is calling reloadData method for UITableView

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.

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.

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

How to update, not reload, UITableView?

My question may be looks simple, but it doesn't work. I searched a lot and try a lot of methods, but it doesn't want work!
So, I have an UITableView. In the background one of my functions work and detect, if a new message has been received, it writes this value into Core Data and I want to add badges on my cells when I receive a new message.
But when I try this:
func reloadTableView() {
dispatch_async(dispatch_get_main_queue(),{
self.tableView.reloadData()
})
}
it doesn't update my tableView and doesn't show new badges. For this, I need:
Change my controller and return to it
Or I need drag my tableView to top and leave it
in these cases it will show my badges.
So, how can I update my tableView to show new added badges without described above 2 methods?
UPDATE
UPDATE 2
I have 2 files: ContactsTableViewController and Messages.swift.
In my Messages.swift I handle new messages and when I receive a new message I get in logs:
print("New message for \(user.jidStr)")
ContactsTableViewController().reloadTableView()
In my ContactsTableViewController:
func reloadTableView() {
self.tableView.reloadData()
print("updated")
}
It reloads my tableView, as I get updated message in logs, but it doesn't updated it and my badges
Try to update only cells that receive new message with:
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: row, inSection: section)], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
where row - the row of your cell in it section that receive the message
Example in Objective-C:
[_tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
Example in Swift 2.0:
self.tableView.reloadRowsAtIndexPaths([NSIndexSet, indexSetWithIndex:0], withRowAnimation: UITableViewRowAnimation.Automatic)
Did you try the NSOperationQeue?
func reloadTableView() {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.tableView.reloadData()
})
}
Edit (because I can't comment)
override func viewDidLoad() { NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "reloadTableView", userInfo: nil, repeats: true) }
If you are using something like this you call the update function every second and this doesn't make sense. You should call it after getting your data
The reloadData should work... ( I think it is better to call it on the main thread. )
Have you checked that your objects array is correct ? If it comes from a CoreData request, it may use the cache. Be sure that you have synchronized the CoreDataContext ( by calling the save method on the context after your insertion ) and that your fetch returns the last created objects.
Just a guess…

Resources