In the code below, I'm trying to use the variable controller to fill in the type of my destination segue at the bottom. But instead I get an error message that says controller is undeclared. It's declared right there on the second line! I can see it! I realize there are many alternatives to using segue identifiers but I'm more interested in learning why this doesn't work.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var controller: UIViewController
switch segue.identifier {
case "ASegue":
controller = AViewController()
case "BSegue":
controller = BViewController()
case "CSegue":
controller = CViewController()
default:
controller = AViewController()
}
if let vc = segue.destination as? controller { //Use of undeclared type 'controller'
...
}
}
A cast must be to a type (literal), not a variable.
And in any case you cannot conflate all your action into a single controller variable as you seem to want to do, exactly because they are different types.
So you would simply move the cast into each case of the switch, casting to the type that you expect for that segue's destination controller, and doing the appropriate work on that controller in that case:
switch segue.identifier {
case "ASegue":
if let controller = segue.destination as? AViewController {
// do stuff
}
case "BSegue":
if let controller = segue.destination as? BViewController {
// etc.
Perhaps a slightly cleaner way of expressing this is to forget the identifier and just go for the view controller class, if each possible segue has a different view controller class:
switch segue.destination {
case let controller as AViewController:
// populate controller
case let controller as BViewController:
// populate controller
// etc.
Related
I've been looking through a Coordinator tutorial and it brought up a problem with code I've written in the past.
Namely, when reusing a view controller I've used a property to be able to display different elements depending on which view controller the user arrived from. This is described in the above tutorial as a hack.
For example I segue to labelviewcontroller using
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "label" {
let vc = segue.destination as! LabelViewController
vc.originalVC = self
}
}
and then on labelViewController have a property
var originalVC: ViewController?
which I then change the items in viewDidLoad() through
override func viewDidLoad() {
super.viewDidLoad()
if originalVC != nil {
label.text = "came direct"
imageView.isHidden = true
}
else {
label.text = "button"
imageView.isHidden = false
}
}
I've a working example project here: https://github.com/stevencurtis/ReusibilityIssues
Now, I know the answer might be use the Coordinator tutorial, but is there any other method that I can use to simple reuse a viewController for two different circumstances, rather than using a property or is there anyway to clean this up to be acceptable practice?
You can do that without passing originalVC just by checking parent type if you are pushing it inside a navigation controller like this :
if let p = parent {
if p.isKind(of: OriginalViewController.self){
//it pushed in navigation controller stack after OriginalViewController
}
}
but is there any other method that I can use to simple reuse a viewController for two different circumstances
If the "two different circumstances" you describe are very different (by this I mean "require very different lines of code to be run"), then you should create two different view controller classes, because otherwise you would be violating the Single Responsibility Principle.
If your "two different circumstances" are different, but also quite related, then you can just have all the information that the VC needs to know as properties. You certainly don't need a whole ViewController.
For example, if your LabelViewController will show a "foo" button only if it is presented by ViewControllerFoo.
You can add a showFooButton property in LabelViewController:
var showFooButton = false
override func viewDidLoad() {
fooButton.isHidden = !showFooButton
}
And then in ViewControllerFoo.prepareForSegue:
if segue.identifier == "label" {
let vc = segue.destination as! LabelViewController
vc.showFooButton = true
}
I wouldn't call this a hack. This is the recommenced way described in this post and they didn't call it a hack.
This question already has answers here:
Passing data between view controllers
(45 answers)
How do I pass a string or data object between two view controllers?
(2 answers)
Closed 4 years ago.
I want to have 3 View Controllers; A, B, and C. View Controller A will be where the final value gets displayed, or the Main View Controller. View Controller B and View Controller C will have simple Text Fields as inputs. The values you put into the Text Fields in View Controller B and C will be added together and shown in View Controller A. You will also need to have buttons to implement the action. How can this be done?
For instance, if the user inputs the number 2 in View Controller B and the number 3 in View Controller C text fields, then View Controller A will show the number 5.
There are multiple methods to do so
You can use delegate, notification centre etc.
try this reference
Send data back
Send Data Back
and it would be more helpful if you can share your code, what have you done so far.
First create the variable "numberOfCViewController" in ViewController B and create two variables called "numberOfCViewControllerInA" and "numberOfBViewControllerInA" in ViewController A
Then just create segues between all the controllers and simply add this function to pass the TextFields data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "CViewControllerToBViewController"
{
let BViewController = segue.destination as! BViewController
BViewController.numberOfCViewController = numberTextField.text!
}
}
By doing that you pass the numberTextField.text! over to the ViewController B.
All you now have to do is repeat the same techniche in the other ViewControllers until youve reached C.
Then you just simply add the two numbers and assign the result to the Label in ViewController C.
Depends on what your using:
Segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "ViewControllerBToViewControllerC" else {
return
}
guard let viewControllerB = segue.destination as? ViewControllerB else {
return
}
viewControllerB.passedValue = <insert passed value>
}
Navigation Controller:
guard let navigationController = navigationController else {
return
}
guard let viewControllerB = UIStoryboard(name: "\(ViewControllerB.self)", bundle: nil) as? ViewControllerB else {
return
}
viewControllerB.passedValue = <insert passed value>
navigationController.pushViewController(viewControllerB, animated: true)
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.
My app is split into 2 storyboards: a character selection one and a main application one. When the user selects a character the app segues's to the main application, and all the views of that storyboard should now relate to the character the user selected.
I'm trying to find out the best way to share a String that will have the selected character's information between all the main application storyboard's views. Right now I'm using UserDefaults to just set a global variable:
func loadMainApp(sender: UITapGestureRecognizer) {
let currentCharcter = allCharacters[(sender.view?.tag)!]
let defaults: UserDefaults = UserDefaults.standard
defaults.setValue(currentCharacter, forKey: "CurrentCharacter")
performSegue(withIdentifier: "MainAppSegue", sender: self)
}
From there all the view controllers in the Main App storyboard can fetch the string from UserDefaults.
Is this the best way of doing such a thing or is there a better way?
The better way is to pass the character to the viewController you are segueing to, and the easiest way to do that is with prepare(for segue. If you change your performSegue call to pass the sender on by saying performSegue(withIdentifier: "MainAppSegue", sender: sender) you will be able to access that in prepare(for like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//Safely check to make sure this is the correct segue by unwrapping the sender and checking the segue identifier
if let tapGesture = sender as? UITapGestureRecognizer, let tapGestureView = sender.view, let mainViewController = segue.destination as? MainViewController, segue.identifier == "MainAppSegue" {
//Get a reference to the character that was selected
let currentCharacter = allCharacters[tapGestureView.tag]
//Pass the character to the new viewController
mainViewController.character = currentCharacter
}
}
I made a couple assumptions about the name of the viewController you are performing the segue to, and assumed it has a variable called character where you can send your character. Your new viewController now has a reference to the character.
If I understand you well. Personally I am using Singeltons for achieving Global variable between Views rather than UserDefaults
class SomeClass{
static let sharedInstance = SomeClass()
var someString = "This String is same from any class"
}
Usage inside of some function :
SomeClass.sharedInstance.someString = "Changing Global String"
I asked that question before, and I got the solution to what he sought. Now, I need to amplify my question. Using delegates, how can I create a Delegate to the ViewController send data to the ContainerView and ContainerView send data to the ViewController?
Well, I don't know if this is entirely what you're looking for, but what I've always done in this situation is kept a record of each view controller inside the other's class.
For example if your container view has the embed segue with identifier "Embed Segue" then your classes might look like:
Superview Class
Class ViewControllerOne: UIViewController {
var data = "This is my data I may want to change"
var subView: ViewControllerTwo?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Embed Segue" {
let destinationVC = segue.destinationViewController as! ViewControllerTwo
destinationVC.superView = self
self.subView = destinationVC
}
}
}
Embedded Class
Class ViewControllerTwo: UIViewController {
var data = "This is the other view controller's copy of that data"
var superView: ViewControllerOne?
}
Then you can pass data between these View Controllers simply by referencing self.subView.data and self.superView.data respectively.
Edit: For ViewControllerTwo to pass data back to ViewControllerOne, it would then simply have to reference self.superView.data. e.g:
Class ViewControllerTwo: UIViewController {
var data = "This is the other view controller's copy of that data"
var superView: ViewControllerOne?
func passDataBack() {
self.superView.data = self.data
}
}
This would then update the data variable in the first view controller.