Swift use Delegate to refresh Tableview - swift

I have tableviewA in a viewController. I am using XIBs as cells for tableViewA. Inside those XIB, I have another tableViewB. Using delegate, I am trying to refresh tableViewB from viewController once I get data from server.
Code used are as follows. Don't know why tableViewB doesn’t get refreshed once data received from server
1.Define protocol
protocol tblRefresh: NSObject {
func refreshTbl()
}
2.ViewController
class HomeVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
weak var delegateTblRefresh : tblRefresh?
//tableViewA defined here
func getDataFromServer(){
// get data from server
self.delegateTblRefresh?.refreshTbl()
}
}
3.XIB as cell of tableViewA
class Challengers: UITableViewCell, UITableViewDelegate, UITableViewDataSource, tblRefresh {
#IBOutlet weak var tableViewB: UITableView!
let homeVC = HomeVC()
override func awakeFromNib() {
super.awakeFromNib()
homeVC.delegateTblRefresh = self
}
//tableViewB defined here
func refreshTbl() {
tableViewB.reloadData()
}
}

You are creating new instance of HomeVC which is not needed.
You don't need to write your own delegate method to refresh you tableView cell. Just reload the table when you get data from server. For example:
func getDataFromServer()
{
// get data from server
self.tableViewA.reloadData()
}
Or if you want to reload only a specific cell you can do this also.

Related

Swift: Update Layout and Content of ViewController when dismissing presented ViewController

I have a UIView which displays some information such as a user's Name and more, including a list of objects that all get pulled from my database. This works fine.
However, I now have a ViewController that gets presented on top of the current ViewController. In this presented ViewController, I am adding Data to my Database. When dismissing that view, I want the original ViewController to update all of its content to be up to date.
Right now, all my views are getting layedout in ViewDidLoad, meaning that they only really get loaded once and don't reload later on. I have managed to update Layout by calling self.view.layoutIfNeeded(), but if I understand correctly, this only updates constraint. Of course, I could call a new init of my original view controller. This would make it reload, but I would like to avoid that.
Another Idea I had was to set up all my content in the ViewWillAppear, which should maybe then update anytime my view controller is about to be visible. However, I don't know how to go about doing this. Can I just move all my setup code to viewWillAppear? Does this have any disadvantages?
TLDR: Is there a way to update a stackview with new elements without having to reload the full ViewController over ViewWillAppear?
The UITableView element works very smoothly with database data. If you fetch the data from your database inside viewDidLoad in your first view controller, and store it in an array, the UITableView (if you set up its dataSource correctly) will automatically populate the table with the new values from the second view controller. With this method, there is no need to use ViewWillAppear at all.
It sounds like as of now, you're using Views (inside a VStack)? to display individual objects from the database. If you want to keep whatever custom style/layout you're using with your views, this can be done by defining a custom subclass of UITableViewCell and selecting the "Also create XIB file" option. The XIB file lets you customize how the cells in your UITableView look.
Here is a simple example to show the database values in the first view controller automatically updating. I didn't include the custom XIB file (these are all default UITableViewCells), to keep it streamlined.
FIRST VIEW CONTROLLER
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var dataTable: UITableView!
var tableRows: [DataItem] = []
func loadData() {
let request: NSFetchRequest<DataItem> = DataItem.fetchRequest()
do {
tableRows = try Global_Context.fetch(request)
} catch {
print("Error loading data: \(error)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
dataTable.dataSource = self
loadData()
}
#IBAction func goForward(_ sender: UIButton) {
self.performSegue(withIdentifier: "toSecond", sender: self)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableRows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dataTableCell", for: indexPath)
cell.textLabel?.text = tableRows[indexPath.row].name
return cell
}
}
let Global_Context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveContext () {
if Global_Context.hasChanges {
do {
try Global_Context.save()
} catch {
let nserror = error as NSError
print("Error saving database context: \(nserror), \(nserror.userInfo)")
}
}
}
SECOND VIEW CONTROLLER:
import UIKit
import CoreData
class AddViewController: UIViewController {
#IBOutlet weak var itemEntry: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
itemEntry.delegate = self
}
#IBAction func addNewItem(_ sender: UIButton) {
let newDataItem = DataItem(context: Global_Context)
newDataItem.name = itemEntry.text
saveContext()
}
#IBAction func goBack(_ sender: UIButton) {
self.performSegue(withIdentifier: "toFirst", sender: self)
}
}
extension AddViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.endEditing(true)
return true
}
}
Main.storyboard:
Once you set up your view controller as a UITableViewDataSource (as in the example code), the table view should make things simpler by eliminating any need to manually manage individual Views.
Is this the functionality you were looking for? (Note about the example: it was set up in Xcode with "Use Core Data" enabled.)
Here is a link to the official documentation:
https://developer.apple.com/documentation/uikit/uitableview

How to let a UIViewController to inherits another UIViewController IBOutlets?

After Implementing the following (Class Inheritance):
class UIViewControllerA: UIViewControllerB {
}
How to let UIViewControllerA to inherits UIViewControllerB IBOutlets? How can I connect the components at Storyboard to the subclass UIViewControllerA?
If your goal is to let IBOutlets to be inherited, you should do the following:
1- Add the IBOutlets in the Super Class:
Super class (UIViewController) should not be directly connected to any ViewController at the storyboard, it should be generic. When adding the IBOutlets to the super class, they should not be connected to any component, sub classes should do that. Also, you might want to do some work (that's why you should apply this mechanism) for the IBOutlets in the super class.
Super Class should be similar to:
class SuperViewController: UIViewController, UITableViewDataSource {
//MARK:- IBOutlets
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// setting text to the label
label.text = "Hello"
// conforming to table view data source
tableView.dataSource = self
}
// handling the data source for the tableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = "Hello!"
return cell!
}
}
2- Inherit Super Class and Connect the IBOutlets:
Simply, your sub class should be similar to:
class ViewController: SuperViewController {
override func viewDidLoad() {
// calling the super class version
super.viewDidLoad()
}
}
From storyboard, assign the view controller to ViewController (Sub Class), then rebuild the project (cmd + b).
Now, after selecting the desired View Controller and selecting "Connection Inspector", you should see -in the IBOutlets section-:
you can manually connect them to the UI components that exists in your sub class ViewController (drag from the empty circle to the component). they should look like:
And that's it! Your sub class's table view and label should inherit what's included in the super class.
Hope this helped.

Pass data by protocol while using Container to view another ViewController in Swift

I started working on this question app.
I began by tableView of the categories:
For data exchange, I decided to use a protocol:
protocol Category {
func data(object:AnyObject)
}
In the first ViewController has the following code:
class ViewController: UIViewController {
var items:[String] = ["Desktop","Tablet","Phone"]
let CategoriesData:Category? = nil
override func viewDidLoad() {
super.viewDidLoad()
CategoriesData?.data(items)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In the second ViewController (tableView in Container) have the following code:
class CategoriesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, Category {
#IBOutlet var table: UITableView!
var items:[String] = []
func data(object: AnyObject) {
self.items = (object as? [String])!
print(object)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewCell = self.table.dequeueReusableCellWithIdentifier("SegueStage") as! TableViewCell
cell.nameLabel.text = items[indexPath.row]
return cell
}
}
For me, apparently it's all right. But nothing appeared on the simulator.
My question is: If the Container use to present another viewController as passing data by protocols should be done?
EDITED
I answered why the TO:s solution didn't work as intended, but I just realised that I haven't given a viable answer to how to use protocols as delegates for the ViewController -> ViewController communication. I'll leave the half-answer below until someone can possibly answer the full question better.
In the way protocol is used in your code, you define your protocol Category to be a delegate for instances of the type ViewController. When an instance of type ViewController is initialised in---and hence owned locally in the scope of---some other class, the instance can delegate callbacks to the owning class.
The problem is that your CategoriesViewController does not contain any instances of type ViewController. We note that both these classes are, in themselves, subclasses of UIViewController, but none of them contain instances of one another. Hence, your CategoriesViewController does indeed conform to protocol Category, by implemented the protocol method data(...), but there's no ViewController instance in CategoriesViewController that can do callbacks to this function. Hence, your code compile file, but as it is, method data(...) in CategoriesViewController will never be called.
I might be mistaken, but as far as I know, protocol delegates are used to do callbacks between models (for model in MVC design) and controllers (see example below), whereas in your case, you want a delegate directly between two controllers.
As an example of model-delegate-controller design, consider some custom user control, with some key property value (e.g. position in rating control), implemented as a subclass of UIView:
// CustomUserControl.swift
protocol CustomUserControlDelegate {
func didChangeValue(value: Int)
}
class CustomUserControl: UIView {
// Properties
// ...
private var value = 0 {
didSet {
// Possibly do something ...
// Call delegate.
delegate?.didChangeValue(value)
}
}
var delegate: CustomUserControlDelegate?
// ... some methods/actions associated with your user control.
}
Now lets assume an instance of your CustomUserControl is used in a a view controller, say ViewController. Your delegate functions for the custom control can be used in the view controller to observe key changes in the model for CustomUserControl, much like you'd use the inherent delegate functions of the UITextFieldDelegate for UITextField instances (e.g. textFieldDidEndEditing(...)).
For this simple example, use a delegate callback from the didSet of the class property value to tell a view controller that one of it's outlets have had associated model update:
// ViewController.swift
Import UIKit
// ...
class ViewController: UIViewController, CustomUserControlDelegate {
// Properties
// ...
#IBOutlet weak var customUserControl: CustomUserControl!
// Instance of CustomUserControl in this UIViewController
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Custom user control, handle through delegate callbacks.
customUserControl.delegate = self
}
// ...
// CustomUserControlDelegate
func didChangeValue(value: Int) {
// do some stuff with 'value' ...
}
}

Need to update tableView

I'm studying programming in Swift, and I've got this example from a book with a mistake (commented). How can I fix this and update tableView while pressing the Add button? I've read many solutions, but none seem to work. Also, tableView updated when I reloaded simulator. I've been stuck with this for two days, and it's driving me mad! :)
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var managedObjectContext: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
managedObjectContext = appDelegate.managedObjectContext! as NSManagedObjectContext
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadBooks() -> Array<AnyObject> {
var error: NSError? = nil
var fetchRequest = NSFetchRequest(entityName: "Book")
let result: [AnyObject] = managedObjectContext!.executeFetchRequest(fetchRequest, error:&error)!
return result
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return loadBooks().count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
var book: Book = loadBooks()[indexPath.row] as! Book
cell.textLabel!.text = book.title
return cell
}
#IBAction func addNew(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("Book", inManagedObjectContext:managedObjectContext)
var book = Book(entity: entity!,insertIntoManagedObjectContext:managedObjectContext)
book.title = "My Book:" + String(loadBooks().count)
var error: NSError?
managedObjectContext.save(&error)
myTableView.reloadData() //mistake!
}
}
You have to declare your an outlet for your tableview which is probably set up in the according storyboards view controller.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var managedObjectContext: NSManagedObjectContext!
#IBOutlet weak var myTableView: UITableView!
// your code
}
Please don't forget to connect your table view instance from the storyboard with your new created outlet, by opening code and storyboard side-by-side and draw a line from the dot next to the outlet to the tableview.
Or you can use the way apple describes here (https://developer.apple.com/library/ios/recipes/xcode_help-IB_connections/chapters/CreatingOutlet.html) to create your outlet from the table view on the storyboard.
edit:
Nevermind, i realized that if you wouldnt have had connected the dataSource and delegate, the crash would have happend way before the addNew().
Kie's answer is correct, however to see an actual result you also need to connect the dataSource and delegate of the tableView to your class.
You can do this either in storyboard, or in code in the viewDidLoad method.
override func viewDidLoad(){
super.viewDidLoad()
myTableView.dataSource = self
myTableView.delegate = self
// Additional code
}

delegate passing nil in swift

I am trying to get to grips with delegates but the delegate I have set up seems to be nil and I am not sure why. I have a HomeViewController where the game is started from, then a UITableViewController where the player selects a row from a table. The row index is then used to pull data to be used in the game. The UITableViewController segues back to the HomeViewController where the game then starts. I thought I had put the correct protocol and delegate code in place but the delegate seems to be nil.
Any help much appreciated!
import UIKit
import Foundation
class HomeViewController: UIViewController, WordListsTableViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// sets up the game here
}
func wordListSelected(selectedWordList: Int) {
// passes the index path of the table to the AppWordList class to create the wordList for the game.
controller.wordList = AppWordList(wordListNumber: selectedWordList)
}
and in the TableViewController
import UIKit
protocol WordListsTableViewControllerDelegate {
func wordListSelected(selectedWordList: Int)
}
class WordListsTableViewController: UITableViewController {
var delegate: WordListsTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
reloadData()
tableView.reloadData()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedWordList = Int()
if (indexPath.section) == 2 {
selectedWordList = (indexPath.row) // Console shows the row is being selected ok.
delegate?.wordListSelected(selectedWordList) // IS NIL ???
// exit segue back to the HomeVC
performSegueWithIdentifier("startGameSegue", sender: nil)
}
}
You need to inform the HomeViewController class that has to be the delegate receiver for the class WordListsTableViewController, like this:
import UIKit
import Foundation
class HomeViewController: UIViewController, WordListsTableViewControllerDelegate
{
var wordListTableViewController = WordListTableViewController() // You forgot this
override func viewDidLoad() {
super.viewDidLoad()
wordListTableViewController.delegate = self // And this
// sets up the game here
}
func wordListSelected(selectedWordList: Int) {
// passes the index path of the table to the AppWordList class to create the wordList for the game.
controller.wordList = AppWordList(wordListNumber: selectedWordList)
}
You're missing a very important point about the Delegate Pattern, you need to keep a reference to the class that delegate its function and set it delegate in the class that handle the function. So let suppose you present the WordListsTableViewController by a segue from the HomeViewController like in the following example:
class HomeViewController: UIViewController, WordListsTableViewControllerDelegate {
// the reference to the class that delegate
var wordListTableViewController: WordListsTableViewController!
override func viewDidLoad() {
super.viewDidLoad()
// sets up the game here
}
func wordListSelected(selectedWordList: Int) {
// passes the index path of the table to the AppWordList class to create the wordList for the game.
controller.wordList = AppWordList(wordListNumber: selectedWordList)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// get the reference to the WordListsTableViewController
self.wordListTableViewController = segue.destinationViewController as! WordListsTableViewController
// set as it delegate
self.wordListTableViewController.delegate = self
}
}
And then you should be notified from the WordListsTableViewController, in the above example I assume the use of segues, but if you present the WordListsTableViewController you can use the same principle of keep a reference to the delegate class, like I show in the above example.
I do not apply any concept in code regarding the retain-cycles that can be happen in the use of delegates, but you can read more in my answer of this question about how to implement delegates correctly:
"fatal error: unexpectedly found nil while unwrapping an Optional value" while calling a protocol method
I strongly recommend you read more about the Delegate Pattern in this post:
How Delegation Works – A Swift Developer’s Guide
I hope this help you.
In your HomeViewController you have to set delegate to self:
override func viewDidLoad() {
super.viewDidLoad()
// get reference to your word lists table view controller
// if controller is your table view that should work
// controller.delegate = self
let wordLists = WordListsTableViewController....
// set up delegate
wordLists.delegate = self.
}