I'm making an expense tracking app. I have added a table view in the main view controller, and added an "add" button in the navigation controller. On clicking this, it shows a view controller in which you type in the data.
On clicking add at the end, it should save the entered data through coreData, and then be presented in the tableView, but my app crashes saying that a nil value was found, even though I have integrated the "??" safe guard."
You tableview in startingViewController is nil this is the problem. When you call the MainVC.getAllItems() your tabview is not initialized. Probably you are re creating startingViewController on your second controller to reach it getAllItems function but it is a wrong approach. You need to update previous viewController datas with protocols or notifications.
First you need to create a protocol like below
protocol AddViewControllerDelegate {
func updateTableView()
}
After that you need to define a variable in your addViewcontroller with this protocol type and call protocol's function when user adds new expense.
class AddViewController: UIViewController {
var delegate: AddViewControllerDelegate?
func callUpdateTableView() {
delegate?.updateTableView()
}
}
In your StartingViewController must conform this protocol. So you need to add updateTableView function. Also you need to say the delegate of your second class is your first class in where you show your addViewController.
class StartingViewController: UIViewController, AddViewControllerDelegate {
func goToAddViewController() {
let vc = AddViewController()
vc.delegate = self
show(vc, sender: nil)
}
func updateTableView() {
// Reload Tableview
}
}
So basically, when you call the protocol function from your secondViewController, your firstViewController's updateTableView function called and you can reload your tableview in this function.
Related
I have 3 view controller: in the 1st VC I am fetch data by a service call. The fetched data is in an array of objects. When a button in the 1st VC is tapped, I pass that array of objects to the 2nd VC where it will show the array of objects in a table view. In the table view cell there is a like button, which shows the total likes from the object. When I tap on the table view cell it opens the 3rd VC, where it shows details and also the like button.
I pass the object like this:
func openImagesList() {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "ImagesList") as? ImagesListVC {
vc.imageModel = self.imagesModel
self.navigationController?.pushViewController(vc, animated: true)
}
}
Now the object is changing in the 3rd and 2nd VCs, but when it gets changed I want the same changes in the 1st VC too. How can I keep track of this?
A more modern alternative to using a delegate is to use a closure. For example, let's say you want to pass data from SecondVC back to FirstVC:
class FirstVC: UIViewController {
private func presentSecondVC() {
let secondVC = SecondVC()
secondVC.dataChangedHandler = dataChanged(data:)
present(secondVC, animated: true)
}
private func dataChanged(data: SomeModel) {
// do stuff with data
}
}
In SecondVC, you need a variable for the handler:
class SecondVC: UIViewController {
var dataChangedHandler: ((SomeModel) -> Void)?
private var data: SomeModel = ...
private func dataDidChange() {
dataChangedHandler?(data)
}
}
Instead of a delegate, you're using dataChangedHandler, a closure to pass data back and forth. You can easily extend this method to three view controllers by passing around the reference to dataChanged(data:).
Using closures is concise (you don't even need a protocol) and versatile. It is also very modular, as you don't have to directly expose FirstVC.
I have a view controller, lets call it vc1, which passes some data to another (vc2) using prepare for segue, and then calling performSegue.
Is there a way to pass some data back from vc2 to vc1 when vc2 is dismissed by swiping down?
Thanks,
Edit --
Apologies for the lack of information, very new to swift so unsure of the correct question to ask in this situation.
To elaborate, the root of the issue at the moment is that vc2 is not dismissed programatically. ie there is currently no function called, it is simply dismissed by the user swiping down.
Is there some function that I can include to capture this dismissal, and use it to send data back to vc1?
I would prefer not to add any buttons to vc2 if possible.
Apologies again, and I appreciate all the help given already!
Try This
class VCOne: UIViewController {
//Create a shared instance of VCOne
static var sharedInstance:VCOne?
//Let the data to be passed back to VCOne is of type string
var dataToBePassedBack:String?
override func viewDidLoad() {
super.viewDidLoad()
//set the sharedInstance to self
VCOne.sharedInstance = self
}
}
Class VCTwo:UIViewController{
//function in which you are dismissing your current VC you can use the shared
instance to pass the data back
func dismissVC(){
//before dismissing the VCTwo you can set the value for VCOne
VCOne.sharedInstance?.dataToBePassedBack = "data"
}
}
Using Protocol And Delegate You Do or Other Option is NSotificationcenter.
One way yo do it is to create another file that it the controller of everything and then have a delegate that always notifies the view controllers when new changes are available. I will walk it through.
protocol HeadControllerDelegate {
// Create a function that sends out the data to the delegates when it is called
// You can use your custom struct here to pass more data easly
func didReciveNewData(myData: String?)
}
struct HeadController {
// Create a shared instance so that the viewcontroller that conforms to the view as well as when we sends out the data the delegate is correct
static var shared = HeadController()
// Creates the delegate, every view can asign it to
public var delegate: HeadControllerDelegate?
// Add all your values here you want to pass back
var myValue: String? {
// The didSet gets called every time this value is set, and then is it time to call the delegate method
didSet {
// Calls the delegates didReciveMethod to notify the delegates that new data exsists
delegate?.didReciveNewData(myData: myValue)
}
}
}
Now in your viewcontroller class where you would like the data to be avaiable (as you said when you swipe down)
class ViewController: UIViewController {
// Here you create a property of the shared instance
let headController = HeadController.shared
override func viewDidLoad() {
super.viewDidLoad()
// Set yourself as the delegate for the headController delegate to recive data
headController.delegate = self
}
}
extension ViewController: HeadControllerDelegate {
// here will the data be recived
func didReciveNewData(myData: String?) {
// handle the data here, you have now got newData
print(myData)
}
}
In the class where you want to pass data you just do it like this. The beauty of this is that you can have multiple classes or structs that writes to the head controllers data (just make sure you do it thought the shared instance). It is also a good pracice according to we to use the delegate pattern.
class Sender {
var headController = HeadController.shared
func sendData(data: String) {
// Here you change the data of the headcontroller wich will send the data to all the delegates
headController.myValue = data
}
}
Hope this answer helps. If you have any questions please let me know.
UPDATE -- EASIER SOLUTION
Here is an easier solution but is less scalable as the previous one according to me.
In prepareForSegue simply pass over your current viewContorller as a field in the destination view controller. Then when viewDidDissapear in the new view controller you can simply pass back the data. Not to worry, I will show you!
In prepare for Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let dc = segue.destination as? SecondViewController {
dc.viewController = self
}
}
And declare the secondViewContorller as following. The ViewDidDisappear method will be called when the view has dismissed, and therefore can you pass over the data to the view controller you have set before using the prepare for segue method.
class SecondViewController: UIViewController {
var viewController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidDisappear(_ animated: Bool) {
(viewController as? ViewController)?.value = 2
}
}
Then you could update the UI using a didSet, which simply will be called when the property is set, which will be done in the view did disappear method.
var value: Int = 0 {
didSet {
print(value)
text?.text = "\(value)"
}
}
Hope this helps!
I have worked with delegate pattern for passing data in the past but that was one-to-one sort of interaction like say I need to pass data back from ViewController B to ViewController A and I set the delegate property defined in B from inside A. Usually we need this kind of delegation.
But I have certain condition where I need to set the delegate property from inside the third, not a ViewController, but a class
Here's how it is laid out -
protocol DataPassingDelegate {
func reloadData()
}
class ButtonView: UIButton {
// Some function that decide which ViewController is to be displayed
func destinationVCDecider() {
// parentController fetched the ViewController in which the button is laid out
let destinationVCObject = self.parentController.storyboard?.instantiateViewController(withIdentifier: Constants.STORYBOARD_IDENTIFIER.JOB_DETAILS_VIEW_CONTROLLER) as! JobDetailsViewController
// Setup for passing data via delegate
let jobsVCObject = JobsViewController()
destinationVCObject.delegate = jobsVCObject
// Displaying the Details of the job
parentController.navigationController?.pushViewController(destinationVCObject, animated: true)
}
}
class JobsViewController: UIViewController,DataPassingDelegate {
func reloadData() {
// Reload the jobs from the server
}
}
class JobDetailsViewController: UIViewController {
weak var delegate: DataPassingDelegate?
func navigateBack() {
delegate?.reloadData()
}
}
navigateBack() inside JobDetailsViewController will be called when certain event has been triggered
Now, when the navigateBack() is called, the delegate property turns out to be nil
Earlier I used to assign self in cases where there was one-to-one interaction but here there are a few classes between them that I don't want to pass them all around
Your approach here is correct. You need to debug it. Create your JobsViewController's instance like this-
let vc = UIStoryboard(name: "Name", bundle: nil).instantiateViewController(identifier: "ViewID") as JobsViewController.
You can debug whether delegate instance is being passed or not by putting a breakpoint in ViewDidLoad method of JobDetailsViewController.
Another approach you can follow is to use NotificationCenter
In VC#1, I have a UITableView. When I tap on a cell, I am brought to VC#2 where information about that cell is displayed.
I want to be able to press a button in VC#2 which changes the title of the cell it corresponds with in VC#1, but I am confused on how to do this?
Should I create a variable in VC#2 to save the indexPath for the cell that was tapped, and then call a function in VC#1 from VC#2 that uses that indexPath to update the cell? If I did this, wouldn't VC#1 need to be static so I know I'm modifying the right instance of VC#1? I'm using a push segue and a navigation controller to go back, so creating a new instance of VC#1 wouldn't reference the same VC im trying to modify as I believe?
Is there an easier way to do this?
You should use the delegate pattern.
VC1 should know what cell that VC2 is showing. You should have an IndexPath property in VC1 that stores what cell is VC2 currently displaying, right?
Now, create a protocol called VC2Delegate:
protocol VC2Delegate : class {
func titleDidChange(_ vc2: VC2, to title: String)
}
Now, add this property in VC2:
weak var delegate: VC2Delegate?
Now, when you think the title of the cell should change, call the delegate:
delegate?.titleDidChange(self, to: "Some Title")
That's all for VC2.
Make VC1 conform to VC2Delegate:
extension VC1: VC2Delegate {
func titleDidChange(_ vc2: VC2, to title: String) {
// set the text of the table cell here...
}
}
Now, when you are passing data to VC2 from VC1, probably in the prepareForSegue method, do
vc2.delegate = self
Learn more about delegates here.
You can pass every data you want through view controllers using delegates
First create a protocol whatever you want
protocol ViewControllerDelegate {
func getSelected(value:Int)
}
Create a variable from your ViewController you want pass the data
var delegate: ViewControllerDelegate?
On didSelectRowAt method you will do
if delegate != nil {
delegate.getSelected(value: indexPath.row)
}
self.navigationController?.popViewController(animated: true)
On ViewController that will receive data you have to do this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? SecondViewController {
vc.delegate = self
}
}
extension YourViewController: ViewControllerDelegate {
fun getSelected(value:Int) {
// Get value from another view controller and manage it
}
}
This code is in Swift 4
If you don't understand something let me know
It's wrong approach you are pursuing. You must separate your data layer from your presentation layer. So in VC#2 you edit your visualized data, then VC#1 reloads the data to update its view.
Short answer: You should not do that at all.
View controllers should not modify other view controller's views.
You should modify the data model in VC2, then send a message back to VC1 telling it to update the cell.
(In the push segue you can set up VC1 to be VC2's delegate, then define a protocol that VC2 uses to notify VC1 about the indexPath's of the data model that need to be updated.)
Im new to programming and trying to build my own app, I wonder how Im I supposed to link the info I get from the addTask viewcontroller to the tableview cell? At the moment Im just trying to get the text from the textfield and Im going to add the other features later.
What Im trying to do
Refer This :-
Create a global array add remove element from that array on click of your button
You can pass data from one view controller to another using Delegates. Check my ans here. You can set your table view class as delegate of your task view controller. Implement the protocol methods of task view controller get the data and reload table.
Hope it helps.
Happyy Coding!!
You can pass data using Delegation .
In Second ViewController
import UIKit
protocol secondViewDelegate: class {
func passData(arrData : [Any])
}
class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
weak var delegate: secondViewDelegate? = nil
#IBAction func clickOnButton(_ sender: Any) {
self.delegate.passData([]) // replace your array here
}
}
In FirstViewController
class FirstViewController: UIViewController, UITableViewDataSource, secondViewDelegate
let objectSecondVC: SecondViewController? = storyboard?.instantiateViewController(withIdentifier: "secondVCID") as! SecondViewController?
objectSecondVC?.delegate = self
navigationController?.pushViewController(objectSecondVC?, animated: true)
Second ViewController Delegate Method in FirstViewController
func passData(arrData : [Any]){
// append to your main array
}
It seems like your add task view controller is connected with your table view controller though a segue. So when you moving back from add task view controller, you can use unwind to pass data back. Here is a detailed tutorial with simple instructions and pictures.