tableView reloadData off of delegate - ios

Have a right view controller that slides in and out over the main view controller. This right view controller has a table in it to contain the passed information from the main.
I can access and pass the data to the controller from the main without issue but in the right view I need to then bind the data passed to it from the main.
The problem is that even though I try binding the data after the view comes into focus it gives nil on the tableView.reloadData().
RightViewController has 2 functions that are used by the main
func loadAlerts(){
self.tableView.reloadData()
}
func setAlerts(alerts: Alerts){
self.alerts = alerts
}
Alerts is just a custom object. It does contain values. self.alerts is a class variable.
MainViewController calls these 2 functions this way
self.rightViewController = self.storyboard?.instantiateViewController(withIdentifier: "RightViewController") as! RightViewController
Set the data after getting it from the api call
if let count = self.alerts?.Alerts.count {
if count == 0 {
return
}
//set on controller
rightViewController.setAlerts(alerts: self.alerts!)
}
This is defined at class level like
private var rightViewController: RightViewController!
Then I have a delegate defined for when the right controller is opened from a gesture and it calls like this
func rightDidOpen() {
rightViewController.loadAlerts()
}
This works fine for everything but the a tableView. Even by telling the tableView to load on the main thread like so
DispatchQueue.main.async{
self.tableView.reloadData()
}
Didn't change anything. At this point the alerts has values.
I don't mind refactoring the entire thing if need be so any ideas, thoughts or info of how I can get this to work is appreciated. If more info is needed just let me know.
--
Here the table delegate and source defined
class RightViewController : UIViewController, UITableViewDataSource, UITableViewDelegate
and from front end assigned to the uicontroller (its calle Alerts Scene). Forgot to mention that if I do the api call directly in the right controller it works fine but I'm trying to reduce api calls so am refactoring this.
Here are the methods. Sorry for the misunderstanding.
//MARK: Tableview delegates
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = alerts?.Alerts.count{
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let alertD = alerts?.Alerts[indexPath.row] {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "AlertTableViewCell") as! AlertTableViewCell
cell.name.text = alertD.Summary
cell.icon.image = Helpers.listImage24dp(id: alertD.TOA)
cell.selectionStyle = .none
cell.name.textColor = UIColor.blue
return cell
}
return UITableViewCell()
}

Related

TableView not working after leaving and coming back to the view

I am puzzled by the behavior of tableView if you leave their view and come back.
I have a screen with one tableView in it that works when I first enter the view. Adding, removing, and updating table cells work. However, when I press a button to segue into the next view and immediately come back, the tableView no longer works. The code that is supposed to execute ( tableView.reload() and all the associated methods) run as they should. However, the screen does not get updated even though internally the arrays get updated, and reload gets ran and executes the code that should update the screen( that is, tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell runs jus fine).
What am I missing? Does tableView require any special treatment if I leave the view and come back to ti?
Thanks.
Edit:
The code for the class where the tableView is something like:
class DebugViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var table: UITableView!
var array = [M]()
override func viewDidLoad() {
super.viewDidLoad()
self.searchbar.delegate = self
self.table.delegate = self
self.table.dataSource = self
search_view = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Search_view") as? SomeViewController
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellManage") as? TableCellManage else { return UITableViewCell() }
let idx = indexPath.row
let value = array[idx]
cell.lbl_time.text = value.month
cell.lbl_background.text = value.color
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 130
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.array.count
}
#IBAction func tapped_addsongs(_ sender: Any) {
self.present( search_view, animated: true, completion: nil)
}
}
Where is the tableView.reload() part? The issue might be generated because you're trying to update the table in a background thread. All UI Updates must be done in main thread:
func someFunc() {
...
DispatchQueue.main.async {
tableView.reload()
}
...
}
After looking at pretty pictures of the life cycle of apps and googling I found the issue and the solution.
The problem was that I had listeners set up to update my table view in a troublesome way. Specifically, I was using the viewDidAppear/viewDidDisappear to bring up and down the listeners, and there was some conflict in the code that managed the state because of this.
Instead, I now bring up the listeners on viewDidLoad. They stay active, regardless of how many views are pushed (within reason, but I only push one), and update my tableview so that when I come back to that view everything is already updated. I don't even see the updates happening, they happen before I get to my view. As for detaching the listeners there is a handy-dandy function I did not know about until 5 minutes ago: deinit. This is the equivalent of destructor in Swift, so I detach my listener when my class object for this view is released from memory.
That solves my issue...and increases performance and I no longer have dangling connections for not managing the listeners well. So a win-win-win.
Thank you all for trying to help! I hope this helps other folks.

Handle events in subviews in MVVM in Swift

I am trying to get into MVVM in Swift and I am wondering how to handle events in subviews in MVVM, and how these events can travel up the chain of views/viewmodels. I'm talking about pure Swift for now (no SwiftRx etc.).
Example
Say I have a TableViewController with a TableViewModel. The view model holds an array of objects and creates a TableCellViewModel for each one, since each cell represents one of these objects. The TableViewController gets the number of rows to display from its model and also the view model for each cell, so it can pass it along to the cell.
We then have a TableCell and each cell has a TableCellViewModel. The TableCell queries its model for things like user-facing strings etc.
Now let's say TableCell also has a delete button that delete's that row. I'm wondering how to handle that: Usually, the cell would forward the button press to its view model, but this is not where we need it - we eventually need to know about the button press in either TableViewController or TableViewModel, so we can remove the row from the table view.
So the question is:
How does the button event get from a TableCell upwards in the view chain in MVVM?
Code
As requested in the comments, code that goes with the example:
class TableViewController: UIViewController, UITableViewDataSource {
var viewModel: TableViewModel = TableViewModel()
// setup and such
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.viewModel.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableCell
cell.viewModel = self.viewModel.cellViewModel(at: indexPath.item)
return cell
}
}
class TableViewModel {
// setup, get data from somewhere, ...
var count: Int {
return self.modelObjects.count
}
func cellViewModel(at index: Int) -> TableCellViewModel {
let modelObject = self.modelObjects[index]
let cellViewModel = TableCellViewModel(modelObject: modelObject)
return cellViewModel
}
}
class TableCell {
var viewModel: TableCellViewModel!
// setup UI, do what a cell does
func viewModelChanged() {
self.titleLabel.text = self.viewModel.title()
}
func deleteButtonPressed(_ sender: UIButton) {
// Oh, what to do, what to do?
}
}
class TableCellViewModel {
private var modelObject: ModelObject
init(modelObject: ModelObject) {
self.modelObject = modelObject
}
func title() -> String {
return self.modelObject.title
}
}
TableViewModel is the source of truth, so all global operations should be performed in there. Pressing a button is completely UI operation and viewModel shouldn't handle this in direct way.
So, for now we know two facts:
TableViewModel should delete the cell from array and then viewController should handle the deletion animation process;
Button press shouldn't be handled in child viewModel.
According to this you can achieve it by:
Pass button pressed event up to viewController (use callback or delegate pattern);
Call TableViewModel method to delete specific cell:
viewModel.deleteCell(at: indexPath)
Properly handle deletion animation in viewController.
may be you can use nextResponder util nextResponder is VC, and VC responder to delegate (eg:CellEventDelegate) that handle delete data and cell
UIResponder *nextResponder = pressedCell.nextResponder;
while (nextResponder) {
if ([nextResponder conformsToProtocol:#protocol(CellEventDelegate)]) {
if ([nextResponder respondsToSelector:#selector(onCatchEvent:)]) {
[((id<CellEventDelegate>)nextResponder) onCatchEvent:event];
}
break;
}
nextResponder = nextResponder.nextResponder;
}

How to Pass Data Between Two Side-by-Side Instances of a UITableViewController

In interface builder, I embedded two instances of a UITableViewController in container views in a UIStackView. Both TableViewControllers are linked to the same custom class document (see code below). The only difference between them is in the data they display. Both have UITableViews that allow multiple selection – but I also want so that selecting anything in one table causes the deselection of everything in the other table, and vice versa. I tried setting this up with delegation, but I don't know how to reference one instance from the other within UITableViewController, to assign each as the delegate of the other.
I couldn't find anything relevant about delegation or about referencing a view controller by anything other than its subclass name. So in my latest attempt, I tried referring to the other child of the parent object. Here's the relevant code:
protocol TableViewSelectionDelegate: AnyObject {
func didSelectInTableView(_ tableView: UITableView)
}
class TableViewController: UITableViewController, TableViewSelectionDelegate {
weak var delegate: TableViewSelectionDelegate?
#IBOutlet var numbersTableView: UITableView!
#IBOutlet var lettersTableView: UITableView!
// Received by segue
var displayables: [Character] = []
override func viewDidLoad() {
super.viewDidLoad()
}
// (It's too soon to determine parents/children in viewDidLoad())
override func viewWillAppear(_ animated: Bool) {
guard let tableViewControllers = parent?.children else {
print("No tableViewControllers found!")
return
}
switch restorationIdentifier {
case "NumbersTableViewController":
for tableViewController in tableViewControllers {
if tableViewController.restorationIdentifier == "LettersTableViewController" {
delegate = tableViewController as? TableViewSelectionDelegate
}
}
case "LettersTableViewController":
for tableViewController in tableViewControllers {
if tableViewController.restorationIdentifier == "NumbersTableViewController" {
delegate = tableViewController as? TableViewSelectionDelegate
}
}
default: print("Unidentified Table View Controller")
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.didSelectInTableView(tableView)
}
func didSelectInTableView(_ tableView: UITableView) {
switch tableView {
case numbersTableView:
numbersTableView.indexPathsForSelectedRows?.forEach { indexPath in
numbersTableView.deselectRow(at: indexPath, animated: false)
}
case lettersTableView:
lettersTableView.indexPathsForSelectedRows?.forEach { indexPath in
lettersTableView.deselectRow(at: indexPath, animated: false)
}
default: print("Unidentified Table View")
}
}
}
Running the above and tapping in either table results in "Unidentified Table View" printed to the console, and neither table's selections are cleared by making a selection in the other.
Any insights into how I could get the results that I want would be appreciated. If something here isn't clear, let me know, and I'll make updates.
Passing information between two instances of a UITableViewController through delegation is apparently not as complicated as it at first seemed. The only noteworthy part is the setting of the delegate. Within the custom TableViewController class, when one instance is initialized, it needs to set itself as the delegate of the other instance. That's it!
In this case, to reference one instance from within another, one can use the tableViewController's parent to get to the other child tableViewController. Although there might be a better way to do this, see the code for my particular solution. Notably, since the parent property is not yet set just after viewDidLoad(), I needed to set things up in viewWillAppear(). Also note that this approach doesn't require using restorationIdentifiers or tags. Rather, it indirectly determines the tableViewController instance through its tableView property.
The delegated didSelectInTableView() function passes the selectedInTableView that was selected in the other tableViewController instance. Since the delegate needs to clear its own selected rows, the selectedInTableView is not needed for this purpose. That is, for just clearing rows, the function doesn't need to pass anything.
protocol TableViewSelectionDelegate: AnyObject {
func didSelectInTableView(_ selectedInTableView: UITableView)
}
class TableViewController: UITableViewController, TableViewSelectionDelegate {
weak var delegate: TableViewSelectionDelegate?
// Received by segue
var displayables: [Character] = []
override func viewDidLoad() {
super.viewDidLoad()
}
// (It's too soon to determine parents/children in viewDidLoad())
override func viewWillAppear(_ animated: Bool) {
guard let siblingTableViewControllers = parent?.children as? [TableViewController] else { return }
switch tableView {
case siblingTableViewControllers[0].tableView: siblingTableViewControllers[1].delegate = self
case siblingTableViewControllers[1].tableView: siblingTableViewControllers[0].delegate = self
default: print("Unidentified Table View Controller")
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.didSelectInTableView(tableView)
}
func didSelectInTableView(_ selectedInTableView: UITableView) {
// selectedTableView is NOT the one that needs to be cleared
// The function only makes it available for other purposes
tableView.indexPathsForSelectedRows?.forEach { indexPath in
tableView.deselectRow(at: indexPath, animated: false)
}
}
}
Please feel free to correct my conceptualization and terminology.

Crash at tableview.reloaddata with error Unexpectedly found nil

On the click of a create button, I navigate to tableviewcontroller screen. But nothing is populated in the tableview since the array which populates the tableview hasn't been called yet.
Now once the tableview screen is reached, after a few seconds another method is called elsewhere(in another file), which in turn calls a function in this tableviewcontroller screen. This is that method of the tableviewcontroller screen which is called...
func stopIndicator(thegrpName: String) {
stopIndicator()
let realm = try! Realm()
let chatGrp = realm.objects(ChatGroup.self)
chatGroup = chatGrp
tableview.reloadData() //CRASH HAPPENS HERE
}
In this method, once I reach tableview.reloadData() it crashes with the error Unexpectedly found nil while unwrapping an optional value..
I referred this link which seems to have a similar problem...but couldn't get much help out of it...
What could be the reason for this crash...?
EDIT 1: The numberOfRows and cellForRowAt.. is given like so...
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let chatGrp = chatGroup {
return chatGrp.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ChatGroupTableViewCell = tableView.dequeueReusableCell(withIdentifier: "chatgroupIdentifier") as! ChatGroupTableViewCell
let groupChatObj = chatGroup![indexPath.row]
cell.chatLabel.text = groupChatObj.lastMessage?.text?.text
return cell
}
It looks you are trying to create delegate method but in another file where you are trying to call delegate method stopIndicator you are calling method on singleton instead which gives you an error.
So, set delegate right. First create protocol
protocol YourProtocol {
func stopIndicator(thegrpName: String)
}
then in another file create delegate property
var delegate: YourProtocol?
now when you need to call delegate method stopIndicator call this
delegate?.stopIndicator(thegrpName: ...)
now add to your ViewController protocol
ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, YourProtocol
and now somewhere set your another file class delegate as your ViewController (if its view set it in viewDidLoad if it is another ViewController set it in prepareForSegue)
fileClass.delegate = self

Strange Swift Syntax, What's It Doing?

So I'm following this tutorial for In-App-Purchases. Here are a few things I don't get:
For the table, in the rowAtIndexPath they use a handler, what is that?
They put all the table code in an extension. I don't know why.
There's also a weird "buyButtonHandler?(product!)" call on button tap
I'd appreciate any clarification on any of the above points. Below is the table code where they put the table in an extension:
extension MasterViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return products.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ProductCell
var products = [SKProduct]() //This is actually declared elsewhere
let product = products[(indexPath as NSIndexPath).row]
cell.product = product
cell.buyButtonHandler = { product in
RageProducts.store.buyProduct(product)
}
return cell
}
}
And the above code includes the strange that I'm looking for help understanding:
cell.buyButtonHandler = { product in
RageProducts.store.buyProduct(product)
}
The table cell has a button and in the cell class this is its code:
func buyButtonTapped (_ sender: AnyObject) {
buyButtonHandler?(product!)
}
It references the below line. This button code/reference is gibberish to me:
var buyButtonHandler: ((_ product: SKProduct) -> ())?
I don't get what that buyButtonHandler is doing, it's like 50% parenthesis! Lastly, I'm including the below var declaration, in case it helps for context:
var product: SKProduct? {
didSet {
guard let product = product else { return }
textLabel?.text = product.localizedTitle
if RageProducts.store.isProductPurchased(product.productIdentifier) {
//Setup
} else {
//Alternate setup
}
}
}
The stuff you're seeing is fairly standard Swift.
Bullet #1:
It looks like the table view cells hold a closure, which is a block of code that they save and run later. The IBAction for the cell's button just invokes the handler block. (The term block and closure are interchangeable. Objective-C calls them blocks, Swift calls them closures.)
So the code in cellForRowAtIndexPath is installing a closure into the cell. That lets you configure your cells from outside. It's a neat trick.
Bullet #2:
It's considered good form to place the methods that implement a protocol in an extension. That way they're all grouped together and easy to find. It also makes the extension into a nice modular block of code. The extension is probably for the UITableViewDelegate and/or UITableViewDataSource protocol methods.
Bullet #3:
Same thing as #1. The cell stores a closure (block of code) in a variable, and when the user taps a button, the button's IBAction invokes the stored closure.
Bullet 1 and Bullet 3 mean that in the table view data source's cellForRowAtIndexPath method you can provide a custom block of code for each cell that gets invoked when the cell's button is tapped. The code in the button IBAction invokes the stored closure and passes it the current product.

Resources