I have a ViewController(VCA) with a TableView inside. From this ViewController it is possibile to call another ViewController (VCB). In this second VC it is possibile add an item to the plist used to populate the TableView in VCA. The problem is that when I save the new item and dismiss the VCB, I can't reload the TableView in VCA.
I have found a lot of examples:
How can you reload a ViewController after dismissing a modally presented view controller in Swift?
How to call reload table view after you call dismissViewController in swift?
How to reload tableview from another view controller in swift
Update the data of a TableViewController after add item
Update the first view after dismissing Popover
Table view is not getting updated after dismissing the popover?
after reading i tried with this code:
**IN VCB**
import UIKit
protocol AddItemDelegateProtocol {
func didAddItem()
}
class VCB: UIViewController {
var delegate : AddItemDelegateProtocol?
...
}
#IBAction func saveButton(_ sender: Any) {
....
self.delegate?.didAddItem()
dismiss(animated: true, completion: nil)
}
**In VCA**
class VCA: UIViewController, UITableViewDelegate, UITableViewDataSource, AddItemDelegateProtocol {
let addItemVC = VCB()
...
override func viewDidLoad() {
super.viewDidLoad()
addItemVC.delegate = self
...
}
func didAddItem() {
self.tableView.reloadData()
}
but this doesn't work. I don't
understand where I'm wrong. Could
you help me?
EDIT: my Solution
I solved in this way:
I've created a singleton in which I declare:
class DataManager {
static let shared = DataManager()
var firstVC = VCA()
.....
}
then, in viewDidLoad of VCA:
DataManager.shared.firstVC = self
now, in the saveButton of VCB, i can call:
#IBAction func saveButton(_ sender: Any) {
........
DataManager.shared.firstVC.tableView.reloadData()
dismiss(animated: true, completion: nil)
}
you can do this in two way :-
1)
Do One thing in VCA
VCA
override func viewWillAppear(_ animated: Bool){
tableView.reloadData()
}
If this does not work out then try this.
2)
create an instance of VCA in VCB and whenever you move from VCA to VCB pass the value of VCA to the instance of VCB and from there reload the table.
VCB
var instanceOfVCA:VCA! // Create an instance of VCA in VCB
func saveButton(){
instanceOfVCA.tableView.reloadData() // reload the table of VCA from the instance
dismiss(animated: true, completion: nil)
}
VCA
override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
VCB.instanceOfVCA = self // Pass the value of VCA in instance of VCB while navigating through segue
}
Here you are calling table's reload data when the viewcontroller is not yet shown. i.e, Even before you dismissed the viewcontroler VCB and viewcontroller VCA is shown, you are calling reloadData.
Try calling reload data in VCA's viewWillAppear(animated: Bool) function.
Try this: make changes as below
let addItemVC : VCB? = nil
In ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
addItemVC = (storyboard?.instantiateViewController(withIdentifier: "ViewControllerID") as! SelectionViewController?)! // change ViewControllerID with your controller id
addItemVC.delegate = self
}
The code in your question is perfectly good. I use the same approach and it works like a charm.
IMHO the problem in your code is that you only refresh the table view with self.tableView.reloadData(), BUT may be you forget to refresh your data model - the data source for the table view. E.g. if you delete an entity from Core Data then you need to refetch your data and only after that reload the table view.
I managed to do it using delegate/protocol that is usually used to pass data between view controllers but in this instance I just called the function without passing data and inside this function i put ( tableView.reloadData() ) and it worked like a sweet :)
juts google "Passing data between view controllers and use the method as I explained above"
Related
I have a tableview, which is a list of football matches. In my navigation I have a "plus" sign where I can add a new match to. This is presented as a popover and it works very fine. Although when I use
dismiss(animated: true, completion: nil) and apply changes to my database.
The tableview does not update and I have to change page in the app, to make it update. '
Any suggesting for making my tableview updating after a popover has been showed? I have tried updating the tableview in viewDidAppear
When you dismiss your popover controller at that time check which is presenting view controller if it is your VC with tableview you want to reload then reload your tableview in viewWillDisappear method of your popoverVC
class PopOverViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let tableVC = presentingViewController as? TableViewController {
DispatchQueue.main.async {
tableVC.tableView.reloadData()
}
}
}
If you want to reload data from a core data database
I don't know what data base you are using but I assume it's Core Data
If it's the case:
create un Unwind Segue (control drag the button you're using to dismiss the controller to the exit icon on top right of controller, give it an identifier).
add this code to both your tableview controller and popoverController
here is the code:
class TableViewController: UITableViewController {
var matches = [Match](){ //Core Data object
didSet {
OperationQueue.main.addOperation { //reloading after adding a new match (safe)
self.tableView.reloadData()
}
}
}
//...
//this unwind function executes after dismissing your popover controller
#IBAction func unwindToThemeList(sender: UIStoryboardSegue) {
matches = //... retreive your data
//this code works with var theme {didset} to make sure new matches are added
}
}
and
class popoverController: UIViewController {
//this section of code lets you do operations before dismissing the controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let identifier = segue.identifier else { return }
if identifier == TheNameYouGaveToYourIdentifier {
//Do your saves
}
}
So when you press the button to dismiss, it will trigger the unwind segue, going through override prepare first then through unwind for segue.
prepare section in pop viewController lets you do your saves
unwind section in tableviewController lets you retrieve your data
then to reload efficiently use the didst section
SCENARIO
Xcode 11.5, Swift 5
Using Core Data
User wants to update their profile. VC2 is dismissed after user taps save. VC1 area highlighted in yellow should reflect the change.
PROBLEM
Data is being saved correctly. However, VC1 elements highlighted in yellow doesn't automatically update. If I go to another tab then come back, the view elements refresh with the updated changes.
MY CODE
I have a setupUI() method that lays out the elements and have tried adding it to VC1's viewWillAppear method, but no luck.
//VC1:
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
setupUI()
}
viewWillAppear is not called when you dimiss the modal that fill the data you need to use a delegate
1- When you show the modal vc
let vc = SomeVC()
vc.delegate = self // declare property delegate inside the modal of that type / protocol
// present
2- when you dimiss the modal
self.delegate?.setupUI()
// dimiss
You could use a delegate method to perform some changes in VC1 in response to some action in VC2. In this case you will set the delegate in VC1 and call the delegate method in VC2. Ideal place to make this call would be in completion block of dismiss.
//VC1
public protocol MyProtocol: class {
func delegateMethod()
}
In the viewDidLoad method set the delegate for VC2
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
setupUI()
//VC2 is the instance of view controller you are going to push from this page
VC2.delegate = self
}
Make sure VC1 confirms to MyProtocol protocol
extension VC1: MyProtocol {
func delegateMethod() {
// reload view here
}
}
Declare the delegate in VC2
//VC2
var delegate: MyProtocol?
Then call the delegate method in completion of dismiss
self.dismiss(animated: false, completion: {
self.delegate?.delegateMethod()
})
Alternatively you could use observers to respond to any changes as well, but that might be an overkill. Check out this article, they discuss the whole thing in detail.
setting vc2.modalPresentationStyle = .fullScreen will solve it without the need to make any delegates.
I know this is a pretty common question but I've tried the various solutions offered here (that are not too old) and in numerous tutorials and I just can't seem to find out why it's still failing for me. Basically setting sendingViewController.delegate to self ends up being nil in sendingViewController. I understand this is very likely because the reference to the sendingViewController is being disposed of. But here is why I'm asking this again.
First, almost every tutorial and every other StackOverflow post is wiring up the mainViewController and the sendingViewController differently. I'm trying to make this work through a Navigation Controller, what one would think is the most common pattern for this.
In the app I'm building (which is more complex than the sample I'm going to show), the mainViewController calls the Settings viewController through a right navbar button. Then the user can select items from a list, which opens a controller with a searchBar and a tableView of items to select from. I need that third view controller to return the selected item from the table view to the settings screen. I'm using storyboards as well. I'm fairly new to Swift and I'm not ready to do all this "programmatically". Any way in the sending view controller, my delegate which should have been set in the calling view controller is nil and I can't invoke the protocol function in the main view controller to pass the data back.
I did a tutorial directly (not using Nav controllers) and I got that to work, but the moment I deviate away, it starts failing. I then put together a streamlined project with two view controllers: ViewController and SendingViewController. ViewController was embedded in a navigation controller and a right bar button was added to go to the SendingViewController. The SendingViewController has a single UI Button that attempts to call the protocol function and dismiss the SendingViewController. I'm not using Seque's, just a simple buttons and protocol/delegate pattern as I can.
My question is what am I missing to actually set the SendingViewController.delegate correctly?
Here's some code:
//ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var showDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func fetchDataButton(_ sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SendingViewController") as! SendingViewController
controller.delegate = self
print("fetching data")
present(controller, animated: true, completion: nil)
}
}
extension ViewController: SendingViewControllerDelegate {
func sendData(value: String) {
print("got Data \(value)")
self.showDataLabel.text = value
}
}
and
// SendingViewController.swift
import UIKit
protocol SendingViewControllerDelegate {
func sendData(value: String)
}
class SendingViewController: UIViewController {
var delegate: SendingViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.navigationController?.popViewController(animated: true)
}
}
Here is a screenshot of the Storyboard:
The ChildViewController does have a storyboard id name of "ChildViewController". All buttons and labels have their appropriate IBOutlet and IBAction's set up.
Help!
i copy paste your code .. its working perfect .. i make just one change
instead of pop you need to use dismiss as you are presenting from your base viewController
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.dismiss(animated: true, completion: nil)
}
here is the project link we.tl/t-NUxm9D26XN
I managed to get this working. In the receiving/parent view controller that needs the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! sendingViewController
controller.cityDelegate = self
}
Then in the sending view controller in my tableView did select row function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let city = filtered[indexPath.row]
searchBar.resignFirstResponder()
self.navigationController?.popViewController(animated: true)
self.cityDelegate?.addCity(city)
self.dismiss(animated: true, completion: nil)
}
I don't think I should be both popping the view controller and dismissing it, but it works. Also in the view controller I did this:
private var presentingController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
}
}
I don't know if I really need this didMove() or not since it doesn't really do anything.
But some combination of all this got it working.
In my other app I'm not using a navigation bar controller and the standard delegate/protocol method works like a charm.
I m confuse about call back using protocol and delegate.
The problem is. I have two viewcontrollers vcA & vcB
and vcA have a tableView, vcB have a button.
vcA click the cell to vcB.
Then I want to click the button in vcB and do the following two things.
1.vcA tableView reloadData.
2.vcB popViewcontroller To vcA.
I can't understand how to solve this issue.
Have any sample to teach me?
Thanks.
This is the delegate solution , but it's better to put the self.tableView.reloadData() method inside viewDidAppear , as it's being called when you pop VcB
class VcA: UIViewController ,TableRefresh {
func reloadTable()
{
// reload here
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let des = segue.destination as! VcB
des.delegate = self
}
}
protocol TableRefresh {
func reloadTable()
}
class VcB: UIViewController {
var delegate: TableRefresh?
#IBAction func closeClicked(_ sender: UIButton) {
self.delegate?.reloadTable()
// pop here
}
}
I hope this could work for you:
class vcA : UIViewController {
override func viewWillAppear(_ animated: Bool) {
tableView.reloadData()
}
}
class vcB : UIViewController {
#IBAction fun button(bin : UIButton){
self.navigationController.popViewController(true)
}
}
It's not possible to call the methods of any VC objects if it's not in your memory.
For the first time, it vcB will not be in memory. Hence the object of the same is not in existence.
If you really want to call a function of that class and if its feasible crate an object of vcB and call the method.
or you might try a shared object of the VC and keep on using the same if it's feasible. you may post exact scenario so that people can suggest something better
Your vcA has a reference to the vcB, so you are able to write something like this in the vcA
class vcA {
var vcB: vcB?
...
vcB?.doSmth()
....
}
but there is no way for you to call vcA from the vcB since it doesn't have a reference to it. So to let vcA know that something happened in vcB or to call some function from vcB you can do several things.
Delegates, Key-Value Observing, Reactive Programming and some others.
Since you asked for the delegates solution lets stick to it.
The general idea behind delegates is as the name says to delegate someone else to do something. In your case, you want to delegate button click handling to the vcA. To do so, you will need a couple of things.
Next steps are just the implementation of the idea described above.
class VcA {
var vcB: VcB?
...
vcB?.delegate = self
...
vcB?.doSmth()
....
}
extension VcA: VcBDelegate {
func buttonIsClicked() {
// reload data
// pop vcB
}
}
protocol VcBDelegate: class {
func buttonIsClicked()
}
class VcB {
var delegate: VcBDelegate?
...
// when the button is clicked
// delegate handling to someone who is 'listening' for this event
self.delegate?.buttonIsClicked()
...
}
Notice how delegate in VcB is optional, meaning that if no one is signed as a delegate for VcB, the event will simply be ignored.
There are multiple ways to do this.
1: Put tableView.reloadData() in viewDidAppear() and then just pop vcB
2: When you are pushing vcB you create a reference to it and then in vcB you have a listener that you apply to the reference. like so:
class viewControllerB: UIViewController {
var reloadTableView: (() -> ())
#objc func buttonPressed() {
reloadTableView()
self.navigationController?.popViewController(animated: true)
}
}
And then:
let vcB = viewControllerB()
vcB.reloadTableView = {
self.tableView.reloadData()
}
present(vcB, animated: true)
3: Do as Sh_Khan
I have a button on 2nd viewController, after pressing that button, I would like to dismiss the 2nd viewController and go back to the 1st view controller and immediately call a function that coded inside 1st ViewController swift file.
May I know how can I do that? By segue?
There are many way to do this one of the best way is using protocol and delegate.
You can create one protocol and extend that protocol in your ViewController1. Now create the delegate of protocol in ViewController2 and pass reference of that delegate in the ViewController1's prepareForSegue method.
First create one protocol like this
protocol PassdataDelegate {
func passData()
}
Now extend this protocol in ViewController1 like this and pass the reference of delegate in prepareForSegue method
class ViewController1 : UIViewController, PassdataDelegate {
func passData() {
//Here call your function
self.callMyFunction()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "SegueIdentifier") {
let destVC = segue.destinationViewController as! ViewController2
destVC.delegate = self
}
}
}
Now create the delegate object of protocolin ViewController2 like this
class ViewController2 : UIViewController {
var delegate: PassdataDelegate?
//Now call the method of this delegate in Button action
#IBAction func buttonClick(sender: UIButton) {
self.delegate.passData()
//Now dismiss the controller
}
}
Note: - Here i am passing stringbut you can pass any type of object that you have declare in your delegate method.
You can refer unwind segue.
class ViewController1 {
#IBAction func doSomeStuffAfterReload(segue: UIStoryboardSegue) {
// do whatever you need to do here.
}
}
On storyboard, from ViewController2 Ctrl+Drag from the button to the exit outlet and select doSomeStuffAfterReload.
You can see it in action here: https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
Happy coding^^