Swift Protocol Third VC to First VC - ios

I need to pass a String and Array from my Third ViewController to my First ViewController directly using protocol/delegate, I have no problem doing it from VC 2 to VC 1 but I'm having a hard time with this. Also after clicking a button in my VC3 I need to go back to VC 1 and update the VC UI how would I do that? Would that have to be in viewdidload?
This in Swift UIKit and Storyboard

You need two protocols, and your firstVC and SecondVC have to conform those. When pushing new ViewController you need to give the delegate of that ViewController to self. On your third VC, when you click the button you need to call your delegate and pass your data to that delegate method, then repeat the same for other.
For FirstVC
protocol FirstProtocol: AnyObject {
func firstFunction(data: String)
}
class FirstVC: UIViewController, FirstProtocol {
weak var delegate: FirstProtocol?
#IBAction func buttonClicked(_ sender: Any) {
let secondVC = SecondVC()
secondVC.delegate = self
navigationController?.pushViewController(secondVC, animated: true)
}
func firstFunction(data: String) {
navigationController?.popToRootViewController(animated: true)
print(data)
}
}
You handle your navigation from your root. For better experience you can use something like coordinator pattern to handle it.
protocol SecondProtocol: AnyObject {
func secondFunction(data: String)
}
class SecondVC: UIViewController, SecondProtocol {
weak var delegate: FirstProtocol?
#objc func buttonClicked() {
let thirdVC = ThirdVC()
thirdVC.delegate = self
navigationController?.pushViewController(thirdVC, animated: true)
}
func secondFunction(data: String) {
delegate?.firstFunction(data: data)
}
}
Second VC is something that you just need to pass parameters.
class ThirdVC: UIViewController {
weak var delegate: SecondProtocol?
#objc func buttonClicked() {
delegate?.secondFunction(data: "data") // PASS YOUR ARRAY AND STRING HERE
}
}

What you need is unwind segue. Unwind segue will act like segue, only backward, popping, in this case, VC2. You can read here for more information.
Updating data code would be put in a function similar to prepareToSegue() but for unwind segue in your VC1.
Example of the function inside VC1:
#IBAction func unwindToDestination(_ unwindSegue: UIStoryboardSegue) {
switch unwindSegue.identifier {
case SegueIdentifier.yourSegueIdentifier:
let sourceVC = unwindSegue.source as! SourceVC
dataToPass = sourceVC.dataToPass
reloadData()
default:
break
}
}

Here is a different approach that accomplishes what you described by performing a Present Modally segue directly from View Controller 3 to View Controller 1, and sharing the string and array values by way of override func prepare(for segue....
In Main.storyboard, I set up 3 View Controllers, and have segues from 1 to 2, 2 to 3, and 3 to 1. These are Action Segues directly from the buttons on each VC, which is why you won't see self.performSegue used inside any of the View Controller files. Here is the picture:
In the first view controller, variables are initialized (with nil values) that will hold a String and an Array (of type Int in the example, but it could be anything):
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var updatableTextLabel: UILabel!
var string: String?
var array: [Int]?
override func viewDidLoad() {
super.viewDidLoad()
// These will only not be nil if we came here from the third view controller after pressing the "Update First VC" button there.
// The values of these variables are set within the third View Controller's .prepare(for segue ...) method.
// As the segue is performed directly from VC 3 to VC 1, the second view controller is not involved at all, and no unwinding of segues is necessary.
if string != nil {
updatableTextLabel.text = string
}
if let a = array {
updatableTextLabel.text? += "\n\n\(a)"
}
}
}
The second view controller doesn't do anything except separate the first and third view controllers, so I didn't include its code.
The third view controller assigns the new values of the string and array inside prepare (this won't be done unless you press the middle button first, to demonstrate both possible outcomes in VC 1). This is where your string and array get passed directly from 3 to 1 (skipping 2 entirely).
import UIKit
class ThirdViewController: UIViewController {
var theString = "abcdefg"
var theArray = [1, 2, 3]
var passValuesToFirstVC = false
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func updateFirstVC(_ sender: UIButton) {
passValuesToFirstVC = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if passValuesToFirstVC && segue.identifier == "toFirst" {
// Cast segue.destination (which on its own has type UIViewController, the superclass to all your custom View Controllers) to the specific subclass that your first View Controller belongs to
let destinationVC = segue.destination as! FirstViewController
// When your first view controller loads, it will receive these values for the 'string' and 'array' variables. They replace the original 'nil' values these had in the FirstViewController definition.
destinationVC.string = theString
destinationVC.array = theArray
}
}
}
Note that there is an IBOutlet to the label on the first View Controller which contains the text to be updated.
After visiting the third view controller, pressing the "Update First VC Text" button, and then performing the segue back to the first, here is how it will look:
This doesn't address the part about protocols and delegates in your question (as I'm not sure how they're being used in your program, and other answers have already addressed that), but it illustrates the method of transferring variables directly from one View Controller to another without unwinding segues or using the UINavigationController.

Related

Where to set delegate in swift?

I've set up a simple Swift project to try and wrap my head around delegates & protocols. The goal is to pass data between two classes (SendingClass & ReceivingClass). Two buttons in the SendingClass are linked to the delegate which should trigger the Protocol conforming function in the ReceivingClass to execute. This doesn't work unfortunately, I suspect it has to do with where and how I am declaring the ReceivingClass as the delegate.
Appreciate your insights, i'm just starting out!
I've tried setting the delegate in various locations (presently within viewDidLoad, but cant get it to work).
let vc = SendingClass()
vc.statusDelegate = self
SendingClass.swift
import UIKit
protocol StatusDelegate {
func statusChanged(state: Bool, sender: String)
}
class SendingClass: UIViewController {
var statusDelegate : StatusDelegate?
#IBAction func button1Pressed(_ sender: UIButton) {
statusDelegate?.statusChanged(state: true, sender: "Button 1")
}
#IBAction func button2Pressed(_ sender: UIButton) {
statusDelegate?.statusChanged(state: false, sender: "Button 2")
}
}
ReceivingClass.swift
import Foundation
import UIKit
class ReceivingClass: UIViewController, StatusDelegate {
override func viewDidLoad() {
let vc = SendingClass()
vc.statusDelegate = self
}
func statusChanged(state: Bool, sender: String) {
print("Sender = \(sender) , State = \(state)")
}
}
Expected: the ReceivingClass protocol conforming function (func statusChanged) should execute each time the buttons are pressed within the SendingClass.
Actual: Nothing happens
I am using this..
// create extension in your receiving class
extension ReceivingClass: PopUpVCDelegate {
func statusChanged(state: Bool, sender: String) {
print("Sender = \(sender) , State = \(state)")
}
}
// on sending class, when you present your receiving class on any button click
eg.
let resultController = self.storyboard?.instantiateViewController(withIdentifier: "PopUpVCID") as? PopUpVC
resultController?.delegate = self
self.present(resultController!, animated: true, completion: nil)
//or if not have button add on viewdidload in receiving class
// here is full eg
How to get data from popup view controller to custom table view cell?
For protocol and delegate, you use it when u want to bring a value from 2nd VC (presented by 1st or pushed by 1st VC) to 1st VC, which is the original.
From your code, I dont see you presenting or pushing your 2nd VC. that's why it's not working. Hopefully I answered your doubt.
However if you still want to bring a value over from 1st VC to 2nd VC. In second VC, create a variable to receive it
var ReceivedData = String()
then from your first VC, when u are going to push it,
let vc = SendingClass()
vc.ReceivedData = "Whatever you want it to receive"
If you're using storyboard segues, maybe the view controller is instantiated from there so probably you have to use the prepareForSegue and get the destination view controller (which is already instantiated for you) in the ReceivingClass view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let destination = segue.destination as? SendingClass {
destination.delegate = self
}
}
Also be careful with delegate patter: the delegate property should be declared as a weak property to avoid retain-cycle
weak var delegate: MyDelegate?

How would you save(append) data from one View Controller to another View Controller when you segue from a different view controller?

Sorry, the question is a bit long. Please bear with me.
Basically, I'm trying to write a simple count up/count down ios app using swift. I have three main view controllers. One is an "Initial View Controller" (which is the root view controller) that contain only two buttons - one that presents modally to the actual counting page (second view controller) and another present modally to a tableViewController page (third view controller). so those are the three view controllers.
So, if the user chooses to save the counter they have been counting I want to append the data on that counter view controller to an array I have created to be displayed on the tableViewController. So I'm making the tableViewController a delegate of the Counter View Controller to append the data to the array.
And as to my understanding, you need to implement prepareSegue in tableViewController to connect the tableViewController to the Counter View Controller. However, because the segue to the Counter View Controller doesn't originate from the tableViewController and instead from the Initial View Controller, the prepareSegue function is not working, and thus the delegate doesn't work. So to simplify my question- How would you save(append) data from one View Controller to another View Controller when you segue from a different view controller?
I hope my question was clear. I'm completely new to software development and not sure if I'm making any sense. Thanks so much for the help!
If you have three controllers in the storyboard path, One -> Two -> Three, and you want One to know about data changes in Three, then you need to propagate the changes via Two.
A rough version might look like this:
protocol CountDelegate: class {
func updateCount()
}
class One: UIViewController, CountDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? Two else { return }
destination.delegate = self
}
func updateCount() {
// whatever
}
}
class Two: UIViewController {
weak var delegate: CountDelegate?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? Three else { return }
destination.delegate = self.delegate // just pass the delegate down
}
}
class Three: UIViewController {
weak var delegate: CountDelegate?
func doWork() {
self.delegate?.updateCount()
}
}
So the delegate is One, and both Two and Three (via Two) point back to it as the delegate.
Solution 1 (Using Notifications):
Each time the counter is updated in CounterViewController, the following code needs to be executed:
let notification = Notification.init(name: NSNotification.Name(rawValue: "UpdateCounter"), object: NSNumber.init(value: 1), userInfo: nil)
NotificationCenter.default.post(notification)
This code is implemented in the Initial View Controller (to observe for the changes in counter):
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(parseNotification(_:)), name: NSNotification.Name.init(rawValue: "UpdateCounter"), object: nil)
}
func parseNotification(_ notification: Notification?) {
if let number = notification?.object as? NSNumber {
/** here counter is a class variable in Initial ViewController **/
counter = number.intValue
}
}
And add the following code in prepareforSegue in the Initial View Controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//Condition to check if TableViewController is being opened
tableViewController.counter = counter
}
Don't forget to remove the observer in the Initial View Controller:
deinit {
NotificationCenter.default.removeObserver(self)
}
Solution 2 (Using Global/Shared variables):
Create a global/shared variable such as
class Shared {
var value = 0
let instance = Shared()
}
In the CounterViewController, you set the value every time the counter is updated
Shared.instance.value = counterValue
You can read the value in TableViewController using the following code:
Shared.instance.value

Delegate/Protocols Passing data from one view controller to another

Trying to pass data from one view controller MainScreenVC to Another RatesVC with protocol and extension, but that's not working, app crashing everytime . I'm clearly see that problem with code on second VC(because print showing correct data after action on first VC) but not sure where is error.
StoryBoard and 1st VC Example
Second VC
1st View controller
import UIKit
protocol transferNameOfCurrency {
func currencySelected(nameOfCurrency: String)
}
class MainScreenVC: UIViewController {
var transferCurrencyDelegate: transferNameOfCurrency?
var nameOfTheCurrency: String?
#IBAction func updateRates(_ sender: Any) {
nameOfTheCurrency = "EUR"
transferCurrencyDelegate?.currencySelected(nameOfCurrency:
nameOfTheCurrency)
print(nameOfTheCurrency)
}
}
2nd ViewController
import UIKit
class RatesVC: UIViewController {
var currencySelected: String?
override func viewDidLoad() {
super.viewDidLoad()
if let push = self.storyboard?.instantiateViewController(withIdentifier: "MainScreenVC") as? MainScreenVC
{
push.transferCurrencyDelegate = self
}
// Do any additional setup after loading the view.
}
}
extension RatesVC: transferNameOfCurrency {
func currencySelected(nameOfCurrency: String) {
currencySelected = nameOfCurrency
print(currencySelected)
}
}
The most obvious problem lies here:
if let push = self.storyboard?.instantiateViewController(withIdentifier: "MainScreenVC") as? MainScreenVC {
push.transferCurrencyDelegate = self
}
You have to realize that instantiateViewController creates a new view controller - it's not the reference to the view controller presented at the screen. In that code you just created a completely new view controller and then set its delegate to self, but otherwise nothing else.
Without knowing the context it is really hard to suggest anything - prepare(for:) segue might be the place where you want to set the delegate. Anyway, the problem is that you have to obtain a reference to the controller that is presented on the screen, the one that is supposed to be reacting to those events.
Moreover, from the memory management aspect, you should really consider making the delegate property a weak one to prevent memory leaks.
EDIT
So after seeing the minimal working example you provided at link, I think I can provide the solution on how to get that string to the SecondVC.
Your first view controller with comments:
import UIKit
class ViewController: UIViewController {
var newLine: String = "EUR"
#IBAction func push(_ sender: Any) {
// here the secondVC does not exist yet, calling delegate.transferWord() here would have no sense
// performSegue will create that secondVC, but now it does not exist, nor it is set up as the delegate
self.performSegue(withIdentifier: "ViewController", sender: navigationController)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let secondVC = segue.destination as? SecondVC, segue.identifier == "ViewController" {
// at this moment secondVC did not load its view yet, trying to access it would cause crash
// because transferWord tries to set label.text directly, we need to make sure that label
// is already set (for experiment you can try comment out next line)
secondVC.loadViewIfNeeded()
// but here secondVC exist, so lets call transferWord on it
secondVC.transferWord(word: newLine)
}
}
}
No need for delegates here, because your ViewController is the one pushing the SecondVC to the Navigation controller - that means that you can access it directly in prepare(for:), as you can see above.
Now the SecondVC is super simple (I omitted unnecessary code):
import UIKit
class SecondVC: UIViewController {
#IBOutlet weak var label: UILabel!
func transferWord(word: String) {
label.text = word
}
}
Storyboards can stay as they are.

Pass data between ViewController without Segues

I am pretty new to swift development and have some problems understanding how to pass data between ViewController.
I want to build a simple music player app which has three views (Player, Playlists, Tracks).
At start the Player is shown to the user. From there the user can press a button and the Playlists view come up. Now he can select a playlist and the next view Tracks is displayed.
If he press on a track he gets back to the Player view and the track is playing. So I need to pass my track to my PlayerViewController.
Currently I'm using Segues to display each ViewController.
Player -> Playlists -> Tracks -> Player
But this will initialise the Player again which means that values/variables get reset. How can I avoid this?
If you are getting from view controller B to view controller C by saying present, then view controller C can speak to view controller B as its presentingViewController.
Try using Unwind segues to pass data i guess they can help you out.
An unwind segue (sometimes called exit segue) can be used to navigate
back through push, modal or popover segues (as if you popped the navigation
item from the navigation bar, closed the popover or dismissed the modally
presented view controller). On top of that you can actually unwind through
not only one but a series of push/modal/popover segues, e.g. "go back"multiple steps in your navigation hierarchy with a single unwind action.When you perform an unwind segue, you need to specify an action, which is an action method of the view controller you want to unwind to.
//ViewControllerA:
import UIKit
class ViewControllerA: UIViewController {
var dataRecieved: String? {
willSet {
labelOutlet.text = newValue
}
}
#IBOutlet weak var labelOutlet:UILabel!
#IBOutlet weak var nextButtonOutlet: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func nextButtonAction(_ sender:UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "ViewControllerB") as! ViewControllerB
controller.dataPassed = labelOutlet.text
self.present(controller, animated: true, completion: nil)
}
// segue ViewControllerB -> ViewControllerA
#IBAction func unwindToThisView(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? ViewControllerB {
dataRecieved = sourceViewController.dataPassed
}
}
}
//ViewControllerB
import UIKit
class ViewControllerB: UIViewController , UITextFieldDelegate{
#IBOutlet weak var textFieldOutlet: UITextField!
var dataPassed : String?
override func viewDidLoad() {
super.viewDidLoad()
textFieldOutlet.text = dataPassed
textFieldOutlet.delegate = self
}
// UITextFieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// User finished typing (hit return): hide the keyboard.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
dataPassed = textField.text
}
}
//From the Return Button click control and drag to exit of the viewcontrollerB as show in the image.
To pass data between View Controllers, have this block in your code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let feed = segue.destination as! SecondViewControllerName
feed.variableinsecondviewcontroller = variableincurrentviewcontroller
}
If that didn't help, you might need to elaborate on what exactly you want with your code...
Use struct class and access your object any where using the stuct object
Like this
struct SomeStruct { var name: String init(name: String) { self.name = name } } var aStruct = SomeStruct(name: "Bob") var bStruct = aStruct // aStruct and bStruct are two structs with the same value! bStruct.name = "Sue" println(aStruct.name) // "Bob" println(bStruct.name) // "Sue"

Xcode 8/Swift 3: how to make ViewController save state when segue occurs?

App has two View Controllers: ViewController (this is the main View Controller that displays the majority of the app's content) and SecondViewController (accessible via a UIButton on ViewController; SecondViewController is only used to display a user's inventory, and a UIButton within SecondViewController allows the user to return to the original view, ViewController). Currently, the app uses the "Show" action segue to switch between View Controllers when the user presses the appropriate UIButton. However, after switching from ViewController to SecondViewController, and then pressing the UIButton to return to ViewController, the properties of ViewController have been reverted to the properties that occur when the app launches (background color is changed, certain text fields appear that shouldn't).
So, how do I "save the state" of ViewController when the user moves to SecondViewController, so that the user resumes where they left off when they return to ViewController?
What you are looking for is an unwind segue. Here's the simplest way of how to create it:
In your ViewController (or, basically any other view controller you are willing to pop to) create an IBAction that accepts an instance of a segue (function name doesn't really matter):
#IBAction func unwindToThisVC(segue: UIStoryboardSegue) { }
In the storyboard, go to SecondViewController, and control + drag from your UIButton to the Exit outlet of ViewController and then select the IBAction you've created in step 1:
More on Unwind Segues
The way you are doing it now (using Show from the second to get back to the first) actually brings up a third VC.
What you want to do is dismiss the second view controller.
The normal way is to implement a protocol for the second one that the first one implements and then to have a function in that protocol for the second one to let the first one know it is done.
When the function is called, the first one dismisses the second and then it will be shown again with its state intact.
Here is a simple example of segue and unwind that you can adapt to your problem... Assume that you have ViewController with label and a button and a SecondViewController with label and a button.
For the first ViewController...
import UIKit
//steps to receive data back from SecondViewController...
//1. create protocol in the SecondViewController (see SecondViewController code)
//2. conform to the protocol
class ViewController: UIViewController, UnwindSegue {
//3. method that gets triggred.
func dataReceived(dataSegued: String) {
labelOne.text = dataSegued
}
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var labelOne: UILabel!
var textReceived : String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btPressed(_ sender: Any) {
performSegue(withIdentifier: "goToSecondController", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSecondController" {
let destinationVC = segue.destination as! SecondViewController
destinationVC.textSegued = textField.text!
//4. create delegate in the SecondViewController (see SecondViewController code)
//5. set ourselves up as delegate of SecondViewController
destinationVC.delegate = self
//6. then dismiss the SecondViewController (see SecondViewController code)
}
}
}
Then for your SecondViewController...
import UIKit
//1. create protocols and delegates to transfer data back
protocol UnwindSegue {
//single required method with a single parameter
func dataReceived(data:String)
}
class SecondViewController: UIViewController {
var textSegued : String?
//4. create delegate of the protocol of type CanReceive that can be a nil. If it is nil, it doesn't go anywhere when BT is pressed
var delegate : UnwindSegue?
#IBOutlet weak var label: UILabel!
#IBOutlet weak var secondTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
label.text = textSegued
}
#IBAction func btTwoPressed(_ sender: Any) {
//this is not triggered if var delegate is nil (as defined as optional)
delegate?.dataReceived(data: secondTextField.text!)
//6. dismiss the 2nd VC so you can see the fist VC
dismiss(animated: true, completion: nil)
}
}

Resources