Swift 4 How to pass data between ViewControllers and ContainerViews? - ios

I have 3 ViewControllers: ViewController A and ViewController B, and Controller C which is actually a ContainerView consisting of two UIViews
As you can see in the above picture, ViewController C has a clear background such that the "Test Label" can be seen in the UIViews of both ViewController A and B.
When I swipe up from ViewController A to go to ViewController B, I want to be able to perform some animation (fade in/out, translate, change text etc..). Let's say I want to change the text for from "Test Label" to "Some new text", the problem is as soon as I get into ViewController B, I get the "Unexpectedly found nil while unwrapping an Optional value" error.
Why am I getting nil and how can I change the label text properly?
This code seems to make sense but I can't get it right:
let containerViewController = ContainerViewController()
containerViewController.testLabel.text = "Some new text"
I've also tried:
let containerViewController = storyboard?.instantiateViewController(withIdentifier: "containerViewController") as! containerViewController
containerViewController.testLabel.text = "Some new text"
Do I have to add something in ViewController A's override func prepare?

You can give the embed segue from the container to the viewController C an identifier let say embedSegueFromBToC then catch the actual ViewController C in the prepare for segue in the parent controller let say B.
So in B viewController add this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "embedSegueFromBToC" {
if let viewControllerC = segue.destination as? ViewControllerC {
viewContrllerC.loadViewIfNeeded() // you might need this since the need to access the testLabel which a UI component that need to be loaded
viewControllerC.testLabel.text = "Some new text"
}
}
}

Correct, you have to use the prepare(for segue:..) override in your originating view controller, it will be passed the instance of ViewController B as the segue.destination.
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if let viewControllerB = segue.destination as? ViewControllerB
{
viewControllerB.testLabel.text = "Some new text"
}
}
Check out this tutorial for more information: https://learnappmaking.com/pass-data-between-view-controllers-swift-how-to/

Related

How to pass variable to a ViewController that is embedded in a Navigation Controller?

I want to pass data from ViewController1 to ViewController2. ViewController2 is embedded within a Navigation Controller (because I have a Segmented Control that corresponds to 3 Child View Controllers). My segue is directly from ViewController1 to the Navigation Controller.
I tried the following in ViewController1:
ViewController2().testID = "test value"
With the following in ViewController2:
var testID = ""
However testID does not update when I run print(testID) in ViewController2.
What is recommended for passing data from ViewController1 to ViewController2? Any help/advice is much appreciated!
You can pass the value by overriding the prepare(for segue:) function in ViewController1
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination
guard let destination = segue.destination as? UINavigationController else {
return
}
guard let finalDestination = destination.viewControllers.first as? ViewController2 else {
return
}
finalDestination.testID = "Test Value"
}
NSUserDefaults will always work.

Passing a string through 2 modal segues

I have been struggling to implement an example of protocol and delegate.
My MainVC has a Modal Segue to a CategoryVC but the CategoryVC sends the user to a DetailVC.
I need to set the MainVC as the delegate for the DetailVC, but all of the examples I can find set the MainVC as the delegate of the DetailVC when the MainVC instantiates the DetailVC.
My DetailVC is not instantiated by the MainVC.
I want to accomplish
detailVC.delegate = self
But because I'm using Interface Builder Modal Segues, I don't know how to refer to the object.
Edit:
MainVC has an image being edited and the functionality to add overlays
CategoryVC has categories of overlays
DetailVC has a UICollectionView showing all of the overlays to choose from
I want DetailVC to pass to MainVC which overlay to add to the image
So the sequence goes MainVC (modal segue) -> CategoryVC (Modal segue) -> DetailVC
It sounds like CategoryVC needs to relay the info about mainVC to DetailVC.
You could give CategoryVC a property theMainVC that it would use to remember the MainVC.
So in MainVC's prepareForSegue:
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let theCategoryVC = segue.destination as? CategoryVC {
theCategoryVC.theMainVC = self
}
}
Then in your CategoryVC:
var theMainVC: MainVC?
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let theDetailVC = segue.destination as? DetailVC {
theDetailVC.delegate = theMainVC
}
}
There are many solutions to your current problem
1- Chain delegate
protocol MYDataSender {
func myDele(str:String)
}
class MainVC:UIViewController,MYDataSender{
func myDele(str:String) { print(str) }
}
when present CategoryVC
let cat = ///
cat.delegate = self // if you don't use self.present(vc ,,, then it would be inside prepareForSegue
class CategoryVC:UIViewController{
var delegate:MYDataSender!
}
when present DetailsVC
let det = //
det.delegete = delegate // rhs is CategoryVC'sdelegete = MainVC , if you don't use self.present(vc ,,, then it would be inside prepareForSegue
class DetailsVC:UIViewController{
var delegate:MYDataSender!
func send() {
delegate.myDele(str:"sendToMain")
}
}
2- NotificationCenter // the easiest
3- Shared singleton
4- Store Data

Passing data from Pop-over ViewController UITextfield back to Master ViewController

I have an app setup to have a Master root viewController with a Navigation bar that has a "Settings button". When the user taps the settings button, it brings up a 'settingsTableViewController' that is not dynamic, but rather static so i can format the tableviews with settings like style.
in this settings view, i have a UITextField that takes a Person's name.
When the user clicks "Done" in the navigation bar, i want to pass that name back to the Master Root ViewController so i can use it in the title to display the Person's name. (I want to pass the name upon dismissing the view)
I have tried to use segue's but no luck. Here is a bit of what i tried.
SettingsViewController (pop's over the MasterVC)
class SettingsTableViewController: UITableViewController {
#IBOutlet weak var nameTextfield: UITextField!
#IBAction func done(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
override viewDidLoad(){
self.title = "Settings"
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Trying to pass what was typed in the textfield to the Root ViewController and store this name in a variable of type String called 'name'
if segue.identifier == "masterView"{
let masterViewController = segue.destinationViewController as! MasterTableViewController
masterViewController.name = nameTextField.text
print("Segue was used")
}
}
}
MasterTableViewController.swift
class MasterTableViewController: UITableViewController {
var name: String!
// I am trying to then display the name entered in the pop-over view in this root view controller's title
override viewDidLoad(){
self.title = name!
}
}
My question is, what did i do wrong? I have tried to use delegation but i get nil and the data doesn't seem to get passed either way so any leads will greatly help. Thanks
There is a different kind of segue called Unwind Segue to do that. It doesn't create a new mvc and its used to pass data back to the vc that presented the current one. here is how to do it
First, go to your storyboard and control drag from settingVc to the exit button on top of it (to itself). it will give you 'Selection Segue' and choose IBAction goBack. This means any vc that presented the current one will get to prepare if they implement this method. In other words, you're putting out a protocol and the presenter vc will conform by implementing goBack IBAction. here is how to implement that. In your Mater vc
//You must have this function before you do the segue in storyboard
#IBAction func goBack(segue: UIStoryboardSegue){
/* this is optional
if let stv = segue.sourceViewController as? SettingsTableViewController{
self.name = stv.nameTextfield?.text
}
*/
}
In your setting vc (current vc)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//make sure you set the id of your segue to "Your Go back Unwind segue". ofc u can name it whatever
if segue.identifier == "Your Go back Unwind segue"{
if let mtvc = segue.destinationViewController as? MasterTableViewController{
mtvc.name = nameTextField.text
print("Segue was used")
}
}
}
and done method should be something like this
#IBAction func doneEditing(sender: UIButton) {
presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}

How do I access an array from one viewcontroller in another viewcontroller

I am very new to the swift language and am trying to make an app that would do some basic calculations. The problem I am running into is that I want to use the values from an array that is declared in one viewcontroller in another viewcontroller to do calculations.
I declare the array in the first viewcontroller like this:
var array2 = [Double]()
And then I have no idea how to access this in the second view controller. I have tried looking at previous questions as well as online tutorials and have had no success.
Any help is greatly appreciated. Thank you in advance.
in VC2 create global var array2 = [Double]()
add following override func to body of VC1
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let DVC = segue.destinationViewController as! ViewController2 //replace ViewController2 with the name of your 2nd VC
DVC.array2 = array2
}
Overall, what you are doing is telling VC1 to copy VC1's array2 to VC2's array2, before the segue happens.
As your app becomes more complex, if your VC1 has more than 1 segue...meaning can go to multiple different VC's...you'll need to change your prepareForSegue so that it takes into account for this.
One way to do this is to change it to
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SegueIdentifier you give to that particular segue in identityInspector" {
let DVC = segue.destinationViewController as! ViewController2 //replace ViewController2 with the name of your 2nd VC
DVC.array2 = array2
}
}

Swift: Segue(modal) to a UITableViewController which embeded in antoher navigation controller

Background: I want to display a modal segue from a UITableViewController(A) to a UITableViewController(B), but I want to show a NavigationBar to "Cancel" and "Save".
What I've done:
In storyboard:
I ctrl drag the cell from A to B, and set segue identifer "selectItem"
I choose B and select "Editor - Embed in - Navigation Controller"
In A's ViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifer == "selectItem" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let destinationViewController = segue.destinationViewController as B
// Pass value from A to B
}
}
}
Error:The app crashed at let destinationViewController = segue.destinationViewController as B with the error swift_dynamicCastClassUnconditional. If I didn't embed a navigation controller to B, the program would not crash. But I really need a navigation bar.
Is there any solution or other way to achieve this? Thank you!
PS: I tried drag a NavigationBar from object library in storyboard, but it's miss a part of background to cover statusbar...
Create both of the UITableViewController in your storyboard.
Select your Second UITableViewController (the one you want to present modally), and embed it in an UINavigationController.
Add the "Cancel" and "Save" UIBarButtonItem into UINavitionItem of the Second UITableViewController.
Select the UITablViewCell of your First UITableViewController
Control+Drag into your UINavigationController.
Select "Present Modally" under the "Selecteion Segue" option from the dropdown list.
To pass data in your First UITableViewController override the method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "identifier" {
let destination = segue.destinationViewController as UINavigationController
let bViewController = destination.topViewController as BViewController
// pass data
}
}
Here are the screenshots:
This should do the job, Cheers!
I solved it!
I added a BreakPoint at the line let dest = segue.destinationViewController as B, and I found that segue.destinationViewController is NavigationController type. So I fixed it by replacing this line to:
let dest = segue.destinationViewController as UINavigationController
let bVC = dest.topViewController as B
and do some passing value stuff.
Hope this will help other people facing this problem.
I use
if let b = segue.destinationViewController.childViewControllers.first as? B {
//do something with b
}
for this scenario. It is really just a syntax difference though.

Resources