Swift 3 - Get data back from segue - swift

I have 4 views which contains a segue(it sends data from a json and store it in a variable called email) which i'm passing it like this:
A -> B(email) -> C(email)
... -> B(email) -> D(email)
So what i want to do is send back the information of the segue to:
C-> B
D-> B
Any idea how can i achieve this?

I think you're after an unwind segue.
To quote the docs:
...use the segue object to fetch the view controller being dismissed so that you can retrieve data from it.
Please see Creating an Unwind Segue on the View Controller Programming Guide for iOS.

The common way for passing data back is to use delegation. Delegation in Swift is done using protocols. So, for example, you declare the following protocol (please, use meaningful names for protocol and properties, the following is just an example):
protocol COrDDelegate {
func willReturn(someString: String)
}
Then, you add corresponding delegate properties to C and D view controllers:
class C: UIViewController {
weak var delegate: COrDDelegate?
...
}
(the same goes for D). You'll have to use weak modifier to avoid retain cycles. After that, call delegate's method right before dismissing the view controller:
func dismissMe() {
self.delegate?.willReturn(someString: "Meaningful data")
self.dismiss(animated: true, completion: nil)
}
In B, you implement the protocol:
extension B: COrDDelegate {
func willReturn(someString: String) {
print(someString)
}
}
and assign the delegate property to C or D in prepare(for:sender:) segue preparation method (just like you are probably doing for email):
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let c = segue.destination as? C {
c.email = self.email
c.delegate = self
}
}
Hopefully, I was able to convey the general idea. Good luck!

Related

Sending data using protocols

I have issues with using protocols to send data back to previous controller. I have studied SO questions and guides, but for some reason my data doesn't get transferred back.
In my second class I create data, that is later being sent back to first class:
protocol ImageEditorDelegate {
func sendImage(image: UIImage, id: String)
}
class PhotoEditorViewController: UIViewController {
var delegate: ImageEditorDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func didPressSave(_ sender: UIButton) {
delegate?.sendImage(image: finalImage, id: imageThatWasSelected)
self.dismiss(animated: true, completion: nil)
}
}
And in my receiving class I have:
class NewProductViewController: UIViewController, ImageEditorDelegate {
var imageEditor: PhotoEditorViewController?
override func viewDidLoad() {
super.viewDidLoad()
imageEditor?.delegate = self
}
func sendImage(image: UIImage, id: String) {
print("Receiving images", image, id)
switch id {
case "1":
selectedImages[1] = image
productImage1.image = image
case "2":
selectedImages[2] = image
productImage2.image = image
case "3":
selectedImages[3] = image
productImage3.image = image
default:
break
}
}
}
But nothing happens, this func never gets called. I think my delegate is nil, or so, but how could I fix this issue? I have Also, I'm using VIPER as architecture with slightly customized segues, may this be the issue? I have tried simple segues, but had same issue.
I understand that this is rather simple question, but I couldn't understand what I doing wrong after I have read articles about protocols.
Thanks for your help!
What you're doing is very wrong. You have two view controllers with property references to one another:
class PhotoEditorViewController: UIViewController {
var delegate: ImageEditorDelegate?
}
class NewProductViewController: UIViewController, ImageEditorDelegate {
var imageEditor: PhotoEditorViewController?
}
Those are not weak references, so if you ever do get this to work — that is, if you ever arrange things so that the NewProductViewController's imageEditor is a PhotoEditorViewController whose delegate is that NewProductViewController — you will have a nasty retain cycle and a memory leak.
This suggests that you have not understood the protocol-and-delegate pattern. Only the presented view controller should have a delegate property pointing back to the presenter, and it should be weak. The presenter does not need any property pointing to the presented view controller, because it presents it.
you need to instantiate your photoEditor, like
photoEditor = PhotoEditorViewController()
before attempting to set its delegate.
you dont' have to do this next part, but I'd suggest making the delegate variable a weak variable to avoid any retain issues, like so
weak var delegate: ImageEditorDelegate?
and you'll need to mark the protocol as class like so
protocol ImageEditorDelegate : class {

Checking Segue after it initiated

I have two UIViewController: A, B
Lets say there are two segues connecting them: C, D
Once a segue has been activated and I am in view B, can I know which segue got me here? C or D?
I don't know of any built-in mechanism for this, but you could have all your destination view controllers conform to a protocol SourceSegueProtocol that has a var to contain the invoking segue.
Then in the source view controller's prepare(for:sender:) method you could set that variable for destination view controllers that conform to the SourceSegueProtocol.
There is a prepare(for: segue) function that allows you to set a property in the new ViewController.
class OriginViewController : UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? SegueProtocol {
destination.transitionSegue = segue.identifier
}
}
}
class DestinationViewController : UIViewController, SegueProtocol {
var transitionSegue: String = ""
override func viewDidLoad() {
print("Segue: ", transitionSegue)
}
}
protocol SegueProtocol {
var transitionSegue : String { get set }
}
Edit: As per comment suggestion, it's better to expect a destination that conforms to a protocol rather than one of a specific type.

how to properly pass data from one viewcontroller's selected cell to previous viewcontroller?

suppose I have two viewcontroller called A and B. In my viewcontroller B I have a tableview in it. When I selected a cell in my tableview, I want to pass that information back to A.
I have a dictionary that is of the following:
myData = [String: DataModel]
where DataModel takes the form of
struct DataModel{
var address = ""
var name = ""
}
I want to send the selected cell's key in B back to A. How should I go about doing that?
thanks for your help
Add this before class BViewController:
protocol ClassBViewControllerDelegate: class {
func didSelectTableViewCell(onRow row: Int)
}
Create a delegate property in BViewController:
weak var delegate: ClassBViewControllerDelegate?
Implement tableView delegate method tableView(_:didSelectRowAt:)
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath:NSIndexPath) {
let row = indexPath.row
delegate?.didSelectTableViewCell(onRow: row)
}
Tell ClassAViewController that its conforms to ClassBViewControllerDelegate as such:
class ClassAViewController: UIViewController, ClassBViewControllerDelegate {
Bind ClassAViewController and ClassBViewController at an appropriate place in ClassAViewController such a, for instance, prepareForSegue:sender:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SegueIdentifierXYZ" {
if let vc = segue.destination as? ClassBViewController {
vc.delegate = self
}
}
}
Use delegate method didSelectTableViewCell(onRow row: Int) of delegate contract ClassBViewControllerDelegate in ClassAViewController:
func didSelectTableViewCell(onRow row: Int) {
print("Selected table view row is:", row)
}
Using completion block or by using delegates you can achieve this.
Using blocks you can code like this:
In your B VC create one property for completion block.
var cellSelectionCallBackHandler: ((DataModel, Int) -> Void)?
Set the cellSelectionCallBackHandler property from VC A.
VCObjectB.cellSelectionCallBackHandler = { (data, index) in
// Use your data here
}
From VC B call the completion handler on selecting cell like this
cellSelectionCallBackHandler?(yourData, index)
Any doubt plz comment.
A delegate method can be used to achieve it. The place/method where you call to dismiss B call the delegate. You can implement this delegate in A.
You can also have a singleton dataHandler class, where you can set and get required properties and can access it from anywhere within your project.

Access the presenting view controller from the presented view controller?

I have a view controller (containing my menu) presented on top of another view controller (my application).
I would need to access the presenting view controller (below my menu) from the presented view controller (my menu), for example to access some variables or make the presenting view controller perform one of its segues.
However, I just can't figure out how to do it.
I'm aware of the "presentingViewController" and "presentedViewController" variables but I didn't manage to use them successfully.
Any Idea ?
Code (from the presented VC, which as a reference to the AppDelegate in which the window is referenced) :
if let presentingViewController = self.appDelegate.window?.rootViewController?.presentingViewController {
presentingViewController.performSegue(withIdentifier: "nameOfMySegue", sender: self)
}
Here is a use of the delegation Design pattern to talk back to the presenting view controller.
First Declare a protocol, that list out all the variables and methods a delegate is expected to respond to.
protocol SomeProtocol {
var someVariable : String {get set}
func doSomething()
}
Next : Make your presenting view controller conform to the protocol.
Set your presenting VC as the delegate
class MainVC: UIViewController, SomeProtocol {
var someVariable: String = ""
func doSomething() {
// Implementation of do
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Your code goes here.
if let destVC = segue.destination as? SubVC{
destVC.delegate = self
}
}
}
Finally, when you are ready to call a method on the presenting VC (Delegate).
class SubVC: UIViewController {
var delegate : SomeProtocol?
func whenSomeEventHappens() {
// For eg : When a menu item is selected
// Set some Variable
delegate?.someVariable = "Some Value"
// Call a method on the deleate
delegate?.doSomething()
}
}
Assuming that VCApplication is presenting VCMenu, in VCMenu you can access VCApplication with:
weak let vcApplication = self.presentingViewController as? VCApplicationType
Your example self.appDelegate.window?.rootViewController?.presentingViewController is looking for the ViewController that presented the rootViewController - it will be nil.
EDIT
Per TheAppMentor I've added weak so there are no retain cycles.

Data between view controllers not passing

I am creating an application in which there are 6 view controller in storyboard. The thing is that data is shared between the default view controller and the first one ( say A and B) which i added. i am using the prepareforseque method for passing data. the problem started when i added two more view controller. lets say C and D i created two new swift files and changed the two view controller class name. i created a textbox and button in C and label in D. when i pressed the button, the value of the text field is not passing into the D view controller although i used the same methods and code which i used for A and B. do i have to do anything else when i want to pass data between two newly added view controller.
first viewcontroller in which when a button is pressed value 1 needed to be passed:
class PlaySelectMenu: UIViewController {
var value = Int()
#IBAction func twotofive(sender: AnyObject) {
value = 1
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextView : PlayGameView = segue.destinationViewController as! PlayGameView
nextView.x = value
}
}
the second view controller which receive the value and print it
import Foundation
import UIKit
class PlayGameView: UIViewController{
var x = Int()
override func viewDidLoad() {
super.viewDidLoad()
print(x)
}
}
here i have added both the view controller from the object library and not working with the default one which is present in storyboard by default. i dont know why these two viewcontroller are not working. please help.
Regards Dev
One solution would be to write the data out to NSUserDefaults and then read it back from NSUserDefaults in the other view controller. Probably not the proper or correct way to share data between two view controllers, but it's been a reliable work around for me.
Other than that, you'd need to share your code so that we can see what's occurring.
Can you post also the code in your controllers C & D. And also if you have copy/paste the code inside your first two controllers into the two others, are you sure that in your prepareForSegue method you have changed the name of the destination segue ?
Assuming you have created the segue in Storyboard:
All you need is to do is put all of needed updates in prepareForSegue because twotofive is called after prepareForSegue.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
value = 1
let nextView : PlayGameView = segue.destinationViewController as! PlayGameView
nextView.x = value
}
Since you have connected your segue from button click to view controller, when you press button segue is automatically called. Instead of connecting segue from button to VC, connect VC to VC. Then in button click method at the last add below line:
#IBAction func twotofive(sender: AnyObject) {
value = 1
self.performSegueWithIdentifier("<Name of the segue identifier>", sender: self)
}
This will call your prepareForSegue. If you are calling more then one VC using segue from a VC then you can use segue.identifier to check which VC was called as below
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "CVC" {
}