How to pass data to a previous ViewController in Swift (Cocoa Application) - swift

Passing data to the next viewController is simple and straightforward, and can be done using prepareSegue method. However, I can't understand how to pass data to a previous viewController in Swift(Cocoa Application)
I have a textfield in viewControllerB and when you type something in it and press a button, I want to pass it to a label in viewControllerA and instead of opening the viewControllerA in a new window, I just want the viewController B to be dismissed and the passed data to be visible on the viewControllerA.
That's all there is to it. I have been stuck on it for the past 48 hours. Any help on this will be appreciated.
Thanks!

You do this using delegates. Example:
protocol NextProtocol: class {
func sendBack(value: Int)
}
class Previous: NextProtocol {
func sendBack(value: Int) {
print("I have received \(value)")
}
func prepareSegue(...) {
// get next instance
var next: Next
next.delegate = self
}
}
class Next {
weak var delegate: NextProtocol?
func someMethod() {
delegate?.sendBack(5)
}
}

Related

How can I reload the tableView after dismissal of modal view? [duplicate]

This question already has answers here:
Detecting sheet was dismissed on iOS 13
(13 answers)
Closed 1 year ago.
In StoreViewController, when a button is clicked, a ModalViewController, named AddStoreVC is opened.
Then at AddStoreVC, when I press Save, I want to dismiss itself (which I can) and when StoreViewController is loaded, the tableView to be refreshed.
viewWillAppear, viewDidAppear or none of the alternatives on previous threads are applicable.
I'd appreciate for your support.
You can use a closure, delegate or notifications (and even KVO would also be a solution). Since you have a relation of one-one, I'd go with closure or pattern.
Closure:
Add in the ViewController which will be modally presented (AddStoreVC)
var onWillDismiss: (() -> Void)?
When you call dismiss(animated:completion:) on it, call onWillDismiss?()
In the presenting ViewController, get a reference on the modal one, and do:
modalVC.onWillDismiss = { [weak self] in
self?.myTableView.reloadData()
}
I passed no param (()), but if you also want to retrieve a param, add it. Imagine you want an Int:
var onWillDismiss: ((Int) -> Void)?
onWillDismiss?(theIntIWantToPass)
modalVC.onWillDismiss = { [weak self] theIntPassed in
print(theIntPassed)
self?.myTableView.reloadData()
}
Delegate :
You can also use the delegate pattern:
Create the delegate:
protocol AddStoreVCCustomProtocol {
func modalVCWillDismiss(_ modalVC: AddStoreVC)
func modalVC(_ modalVC, willDimissWithParam param: Int)
}
Make the presenting one compliant to it:
extension StoreViewController: AddStoreVCCustomProtocol {
func modalVCWillDismiss(_ modalVC: AddStoreVC) {
myTableView.reloadData()
}
func modalVC(_ modalVC, willDimissWithParam param: Int) {
print("theIntPassed with delegate: \(param)")
myTableView.reloadData()
}
}
Add a property to the Modal to have a delegate:
weak var customDelegate: AddStoreVCCustomProtocol?
And call it on dismiss(animated:completion:): customDelegate?.modalVCWillDismiss(self) or `customDelegate?.modalVC(self, willDimissWithParam: theIntIWantToPass)

How can I check popularProduct.xib file loaded into HomeViewController

In my HomeViewController have four section:
Banner
Popular Product
New Product
Old Product
All are .xib files and they are working fine.
Now I want to add ActivityIndicator in my HomeViewController
So now I want to show ActivityIndicator on HomeViewController until all the .xib's file not fully loaded in HomeViewController as .xib's ActivityIndicator should hide.
Now, the problem for me is that how can I get the confirmation about .xib's loaded to HomeViewController or not?
As a pretty simple direct solution, you could follow the delegation approach. So, when "Popular Product" View complete the needed process, you should fire a delegate method which will be handled by the view controller.
Example:
consider that in PopularProduct class you are implementing a method called doSomething() which need to get called and finish its work to hide the activity indicator from the view controller and it should send Data instance to the view controller. You could do it like this:
protocol PopularProductDelegate: class {
func popularProduct(popularProduct: PopularProduct, doSomethingDidPerformWith data: Data)
}
class PopularProduct: UIView {
// ...
weak var delegate: PopularProductDelegate?
func doSomething() {
// consider that there is much work to be done here
// which generates a data instance:
let data = Data(base64Encoded: "...")!
// therefore:
delegate?.popularProduct(popularProduct: self, doSomethingDidPerformWith: data)
}
// ...
}
Therefore, in ViewController:
class ViewController: UIViewController {
// ...
var popularProduct: PopularProduct!
override func viewDidLoad() {
super.viewDidLoad()
// show indicator
popularProduct.doSomething()
}
// ...
}
extension ViewController: PopularProductDelegate {
func popularProduct(popularProduct: PopularProduct, doSomethingDidPerformWith data: Data) {
print(data)
// hide indicator
}
}
Furthermore, you could check my answer for better understanding for delegates.

How to access a referencing outlet from a different view controller?

I am new to Swift and Xcode. I am building an Financial Expense ios app.
In my first view controller, I created a referencing outlet for a label called expenseNum.
In my second view controller, I have a function for a button called Add Expense. When it is clicked, I need it to update the expenseNum variable with the amount of the expense.
What is the best way to go about this? I had created an object of the first view controller class and accessed it like "firstviewcontroller.expenseNum" but this will create a new instance of the class and I need it to be all the same instance so it can continuously add to the same variable. Thanks for the help!
You need a delegate
protocol SendManager {
func send(str:String)
}
In first
class FirstVc:UIViewcontroller , SendManager {
func send(str:string) {
self.expenseNum.text = str
}
}
when you present SecondVc
let sec = SecondVc()
sec.delegate = self
// present
In second
class SecondVc:UIViewcontroller {
var delegate:SendManager?
#IBAction func btnClicked(_ sender:UIButton) {
delegate?.send(str:"value")
}
}
// setting delegate
in viewDidLoad of SecondVc
if let first = self.tabBarController.viewControllers[0] as? FirstVc {
self.delegate = first
}
There are several ways you can pass data from ViewController2 to another ViewController1
The best way here is Protocol Delegates
Please follow below steps to pass data
In Your SecondViewController from where you want to send data back declare a protocol at the top of class declaration
protocol SendDataBack: class {
func sendDataFromSecondVCtoFirstVC(myValue: String)
}
Now in the class , declare a object of your protocol in same ViewController
weak var myDelegateObj: SendDataBack?
And now in your Add Expense button action just call the delegate method
myDelegateObj?.sendDataFromSecondVCtoFirstVC(myValue: yourValue)
Now go to your first ViewController
the place from where you have pushed/present to SecondViewController you must have taken the object of SecondVC to push to push from first
if let secondVC = (UIStoryboard.init(name: "Main", bundle: nil)).instantiateViewController(withIdentifier: "secondVCID") as? SecondViewController {
vc?.myDelegateObj = self
self.navigationController?.pushViewController(secondVC, animated: true)
**OR**
self.present(secondVC, animated: true, completion: nil)
}
now in your FirstViewController make an extension of FirstViewVC
extension FirstViewController: SendDataBack {
func sendDataFromSecondVCtoFirstVC(myValue: String) {
}
}
I think you can make a variable in your properties in second ViewController (before viewDidLoad method)
var delegate: FirstViewController? = nil
and use from the properties of the first view controller anywhere of the second view controller.
delegate!.mainTableView.alpha=1.0
//for example access to a tableView in first view controller
The simplest way to achieve this is to use a public var. Add a new Swift file to your project, call it Globals. Declare the public variable in Globals.swift like so:
public var theValue: Int = 0
Set its required value in the first ViewController, and you'll find you can read it in the second with ease.

Swift - How to use a closure to fire a function in a view model?

I am watching the video series
Swift Talk #5
Connecting View Controllers
url: https://talk.objc.io/episodes/S01E05-connecting-view-controllers
In this video series they remove all the prepareForSegue and use an App class to handle the connection between different view controllers.
I want to replicate this, but specifically only in my current view model; but what I don't get is how to connect view controllers through a view model (or even if you're meant to)
In their code, at github: https://github.com/objcio/S01E05-connecting-view-controllers/blob/master/Example/AppDelegate.swift
They use do this within their view controller
var didSelect: (Episode) -> () = { _ in }
This runs;
func showEpisode(episode: Episode) {
let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
detailVC.episode = episode
navigationController.pushViewController(detailVC, animated: true)
}
In the same way, I want to use my ViewController to use my ViewModel for a menu button press (relying on tag).
My code follows;
struct MainMenuViewModel {
enum MainMenuTag: Int {
case newGameTag = 0
}
func menuButtonPressed(tag: Int) {
guard let tagSelected = MainMenuTag.init(rawValue: tag) else {
return
}
switch tagSelected {
case .newGameTag:
print ("Pressed new game btn")
break
}
}
func menuBtnDidPress(tag: Int) {
print ("You pressed: \(tag)")
// Do a switch here
// Go to the next view controller? Should the view model even know about navigation controllers, pushing, etc?
}
}
class MainMenuViewController: UIViewController {
#IBOutlet var mainMenuBtnOutletCollection: [UIButton]!
var didSelect: (Int) -> () = { _ in }
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func mainMenuBtnPressed(_ sender: UIButton) {
let tag = (sender).tag
self.didSelect(tag)
}
}
What I don't understand is how do I connect the command
self.didSelect(tag)
to the function
func menuButtonPressed(tag: Int)
within my ViewModel
As I understand it, according to the swift talk video is that the idea is that the view controller are "plain" and that the view model handles all the major stuff, like menu button presses and then moving to different view controllers as necessary.
How do I connect the didSelect item to my viewModel function?
Thank you.
You should set didSelect property for your controller like here:
func showEpisode(episode: Episode) {
let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
detailVC.episode = episode
detailVC.didSelect = { episode in
// do whatever you need
// for example dismiss detailVC
self.navigationController.popViewController(animated: true)
// or call the model methods
self.model.menuButtonPressed(episode)
}
navigationController.pushViewController(detailVC, animated: true)
}

Pass variables from one ViewController to another in Swift

I have a calculator class, a first ViewController to insert the values and a second ViewController to show the result of the calculation. Unfortunately I get a error called "Can't unwrap Optional.None" if I click the button. I know it's something wrong with the syntax, but I don't know how to improve it.
The button in the first Viewcontroller is set to "Segue: Show (e.g. Push)" in the storyboard to switch to the secondViewController if he gets tapped.
the calculator class is something like:
class Calculator: NSObject {
func calculate (a:Int,b:Int) -> (Int) {
var result = a * b
return (result)
}
}
The Viewcontroller calls the function, inserts a/b and wants to change the label which is located in the secondviewcontroller:
class ViewController: UIViewController {
#IBAction func myButtonPressed(sender : AnyObject) {
showResult()
}
var numberOne = 4
var numberTwo = 7
var myCalc = Calculator()
func showResult () {
var myResult = myCalc.calculate(numberOne, b: numberTwo)
println("myResult is \(String(myResult))")
var myVC = secondViewController()
myVC.setResultLabel(myResult)
}
And here is the code of the secondViewController
class secondViewController: UIViewController {
#IBOutlet var myResultLabel : UILabel = nil
func setResultLabel (resultValue:Int) {
myResultLabel.text = String(resultValue)
}
init(coder aDecoder: NSCoder!)
{
super.init(coder: aDecoder)
}
In Swift, everything is public by default.
Define your variables outside the classes:
import UIKit
var placesArray: NSMutableArray!
class FirstViewController: UIViewController {
//
..
//
}
and then access it
import UIKit
class TableViewController: UITableViewController {
//
placesArray = [1, 2, 3]
//
}
The problem here is that the FirstViewController has no reference to the instance of SecondViewController. Because of this, this line:
secondViewController.setResultLabel(myResult)
does nothing (except probably causing the Can't unwrap Optional.None error). There are a few ways to solve this problem. If you are using storyboard segues you can use the -prepareForSegue method of UIViewController. Here is an example:
In FirstViewController:
override func prepareForSegue(segue: UIStoryboardSegue!,sender: AnyObject!){
//make sure that the segue is going to secondViewController
if segue.destinationViewController is secondViewController{
// now set a var that points to that new viewcontroller so you can call the method correctly
let nextController = (segue.destinationViewController as! secondViewController)
nextController.setResultLabel((String(myResult)))
}
}
Note: this code will not run as is because the function has no access to the result variable. you'll have to figure that out yourself :)
I think the issue here is, you are trying to set the UI component (here, its the label : myResultLabel)
When segue is fired from first view controller, the second view has not yet been initialized. In other words, the UI object "myResultLabel" is still nil.
To solve this, you will need to create a local string variable in second controller. Now, set that string to what you are trying to display, and finally, set the actual label in "viewDidLoad()" of the second controller.
Best Regards,
Gopal Nair.