Programmatically Passing Variables Through View Controllers in Swift Without StoryBoards - ios

I am creating an iOS application and my question should be simple.
If in View Controller 1 I have a simple string variable called "name" how do I then pass the value of "name" to View Controller 2.
I realize this question has been asked many times before, however all the answers I can find have had responses pertaining to storyboards and segues. I am not using Story Boards so I am wondering if there is a way to do this without them.
Many Thanks

You do this the same way in Swift as you do in Objective-C.
Write VC2 with a property to hold the value you want to pass it.
Create an instance of VC2 from VC1
Set the property on VC2 with the value from VC1.
Display the view of VC2.

So simply you have to do something like:
class ViewController: UIViewController {
var secondVC: VC2!
override func viewDidLoad() {
secondVC = VC()
secondVC.someProperty = "Some Value"
}
}
So your VC2 should have such property as someProperty

Related

swift; xcode 9.2 - Passing arguments over TabBar & navigation controller

In my storyboard I got:
UIView -> UITabBarController -> UINavigationController -> UITableView
Now I want to pass an object from UIView into UITableview. I do get the object to the TabBarController from the prepare for segue func, but from there I kind of get lost.
How to identify what segue you have on the itemlist from the TabBarController?
Could somebody give some example code for the UITabBar and Navigation controller to pass the data?
Phillip is right.
You can do it as following:
class Model {
static let shared = Model()
var data: String // or anything else
}
in UIView:
Model.shared.data = "some data"
in UITableView
let data = Model.shared.data
//do smth with data...
Anton is suggesting the Singleton pattern. It is important to understand what it is when you decide to use it has both its benefits and potential pitfalls. https://thatthinginswift.com/singletons/ is a place to start reading up.
There are ways to just pass an object from one view to the other and that is useful knowledge to know. Both TabBarVC's and NavigationVC's have their viewControllers property which allows you to access an array of their child vc's. You can use this to pass information to specific child vc's. Depending on your needs this may be more appropriate than creating a singleton.
For example:
let childVC = tabBarVC.viewControllers[0] as! MyCustomVCClass
childVC.inheretedObject = objectIWantToSend
This would pass an object to the vc that ocupies the first tab of a tab bar vc.

Detect that from which Page I come to the current Page

I want to know how to do this:
I have 3 view controllers and the first and second view controller are connected to the third one !
I want to know how can I write a code that detect from which one I came to this view controller
I have searched here for my answer But all of the similar questions asked about navigation !!!
The Important thing is that I don't have navigation in my app!!
I don't know if my answer will help you in your specific case, but here is the implementation I see from what you are asking. Maybe it will inspire you.
So imagine your are in your homePage or whatever viewController and you want to navigate throw other, but you want to know from which viewController you came from.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:segue_VC1]) {
CustomViewController1* destinationVC = segue.destinationViewController;
destinationVC.fromSegue = #"I AM VC 1";
}
if ([segue.identifier isEqualToString:segue_VC2]) {
CustomViewController2* destinationVC = segue.destinationViewController;
destinationVC.fromSegue = #"I AM VC 2";
}
}
The more important thing you have to know is that you can access attribute from the destination view controller you will access with your segue.
I know this is in Obj C, but the implementation is still the same.
So that when you navigate from a ViewController to an other one, you can set the attribute of the destinationViewController.
Then when you are in the view controller you wanted to navigate you can check :
if ([_fromSegue isEqualToString: "I AM VC 1"])
// do specific stuff when you come from VC 1
else if ([_fromSegue isEqualToString: "I AM VC 2"])
// do specific stuff when you come from VC 2
else
// other case
There are many ways to do that like simply passing a view controller as a property to the new instance. But in your case it might make more sense to create a static variable which holds the stack of the view controllers the same way the navigation controller does that.
If you are doing this only between the UIViewController subclasses I suggest you to create another subclass of it form which all other view controllers inherit. Let us call it TrackedViewController.
class TrackedViewController : UIViewController {
static var currentViewController: TrackedViewController?
static var previousViewController: TrackedViewController?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
TrackedViewController.previousViewController = TrackedViewController.currentViewController
TrackedViewController.currentViewController = self
}
}
Now you need to change all the view controllers you want to track so that they all inherit from TrackedViewController as class MyViewController : TrackedViewController {. And that is pretty much it. Now at any point anywhere in your project you can find your current view controller via TrackedViewController.currentViewController and the previous view controller via TrackedViewController.previousViewController. So you can say something like:
if let myController = TrackedViewController.previousViewController as? MyViewController {
// Code here if the screen was reached from MyViewController instance
}
Now the way I did it was through the instance of the view controller which may have some side effects.
The biggest problem you may have is that the previous controller is being retained along with the current view controller. That means you may have 2 controllers in memory you do not need.
If you go from controller A to B to C and back to B then the previous view controller is C, not A. This might be desired result or not.
The system will ignore all other view controllers. So if you use one that is not a subclass of TrackedViewController the call will be ignored: A to B to UITableViewController to C will report that the C was presented by B even though there was another screen in between. Again this might be expected result.
So if the point 2 and 3 are good to you then you should only decide weather to fix the point 1. You may use weak to remove the retaining on the two properties but then you lose the information of the previous view controller if the controller is deallocated. So another choice is to use some identifiers:
class TrackedViewController : UIViewController {
static var currentViewControllerID: String?
static var previousViewControllerID: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
TrackedViewController.previousViewControllerID = TrackedViewController.currentViewControllerID
TrackedViewController.currentViewControllerID = self.screenIdentifier
}
var screenIdentifier: String {
return "Default Screen" // TODO: every view controller must override this method and have unique identifier
}
}
Also you may replace strings with some enumeration or something. Combining them with some associated values could then create quite a powerful tool.

How to pass data between Swift files without prepareForSegue?

I currently have three Swift files, one for the main view in a ViewController, and two more which are used for the two views within the first view, which are used for a Segmented Control.
As these don't use segues between each other, I can't use the prepareForSegue method to transfer data between them, so how do transfer the variables and such from one file to another?
This doesn't seem to be a duplicate as other cases such as the one commented are using segues, mine is not.
Are all three Swift classes view controller subclasses?
You have your main view controller with your segmented control. For each segmented, I would create a new view controller subclass.
On your main view controller, for each segment, use a 'Container View' instead of a UIView object.
This will create two new 'screens' in the storyboard, attached to your main view controller with a segue. These new screens will be UIViewControllers, you can change them to be your subclass's as normal.
You can now use your prepareForSegue function as normal to set data in your segmented control view controllers.
So you have something like viewControllerMain, which contains viewSegmentedOne and viewSegmentedTwo, and you want to be able to access `viewControllerMain.myProperty' ?
You can always navigate through the hierarchy to get parent views - but the easiest option could be to include a reference to viewControllerMain in each of the segmented controls
var myParentVC : ViewControllerMain?
then when you create the subviews
mySubView.myParentVC = self
If you are using storyboard for view controllers, then try like this:
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Your_VC_Identifier");
viewController.Your_var = Your_value_to_assign
NOTE: Define Your_var in your ViewController class
You just need to create an instance of the view controller you want to display, this is easy such as calling on the storyboard instance (usually the presenting view controller has one) instantiateViewController(withIdentifier:), by using an identifier that you provide in the storyboard file.
One created you can pass the data you want by casting it to your view controller class and present it as you prefer.
One method would be using singleton class . https://cocoacasts.com/what-is-a-singleton-and-how-to-create-one-in-swift/ this is how you can make singleton class.
other method could be using nsuserdefaults.
You need to decide which approach is best according to your requirement.
try this:
let defaults = UserDefaults.standard()
defaults.set(yourdata, forKey: "someObject")
print(defaults.object(forKey: "someObject"))
You can try use Extensions for UIViewController
private var storedDataKey: UInt8 = 0
extension UIViewController {
var storedViewControllerData: UIViewController? {
get {
return objc_getAssociatedObject(self, &storedDataKey) as? UIViewController
}
set(newValue) {
objc_setAssociatedObject(self, &storedDataKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN)
}
}
}
This very useful you can send data with chain like:
viewControllerB.storedViewControllerData = viewControllerA.storedViewControllerData
or
func viewDidLoad() {
doSomething(self.storedViewControllerData)
}

Passing data between two View Controllers which aren't connected by a segue [duplicate]

This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 7 years ago.
I know that you can pass information between two view controllers if they are connected by a segue using
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let destinationViewController = segue.destinationViewController as? searchTermViewController else { return }
destinationViewController.courseCodes = selectedCourses
}
}
The above code gives an error if there is no segue because of the .destinationViewController. How do i pass information between to arbitrary view controllers without having to set up a global variable?
You can set up a delegate pattern in order to do this.
Here are the steps for setting up the delegate pattern between two objects, where object A is the delegate for object B, and object B will send messages back to A. The steps are:
Define a delegate protocol for object B.
Give object B an optional delegate variable. This variable should be weak.
Make object B send messages to its delegate when something interesting happens, such as when it needs a piece of information. You write delegate?.methodName(self, . . .)
Make object A conform to the delegate protocol. It should put the name of the protocol in its class line and implement the methods from the protocol.
Tell object B that object A is now its delegate.
Here is a tutorial to give you a working example https://www.youtube.com/watch?v=9LHDsSWc680
Go to your storyboard, select the second view controller, go to the Identity inspector tab and give a StoryBoard ID value. This should be a unique value to identify your view controller.
Now in your first view controller', you can run this code. This will basically create an object of the second view controller, set the property value (for transferring data) and push it (same as the segue does)
let ctrl = self.storyboard?.instantiateViewControllerWithIdentifier("detailsView")
as? SecondViewController
ctrl?.userId = 250 // data to pass.
self.navigationController?.pushViewController(ctrl!, animated: true)
provided userId is a variable in your SecondViewController class. Replace
detailsView with the storyboard id value you gave earlier.
class SecondViewController: UIViewController {
var userId : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// do something with self.userId
}
}

Present subclassed view controller from another view controller in Swift

I have some problems to use subclasses in Swift, hope someone can help me.
What I have
Two view controllers:
VC1 with just some UIButtons
EffectVC that do some animation depending on the button pressed on VC1
import UIKit
protocol viewAnimation {
func initialStateSet()
func finalStateSet()
}
class EffectVC: UIViewController {
#IBOutlet weak var mainImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.initialStateSet()
}
override func viewDidAppear(animated: Bool) {
self.finalStateSet()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initialStateSet() {
}
func finalStateSet() {
}
}
class GrowingEffect : EffectVC, viewAnimation {
override func initialStateSet() {
// some stuff
}
override func finalStateSet() {
// other stuff
}
}
The problem
Maybe a simple question but I can't do what I want in Swift: I need to set a subclass according to the button that is pressed.
In other words I need to present subclassed view controller from my VC1 according to which button is pressed on VC1.
If I press the first button for example I want to show the VC 2 with the class GrowingEffect for use some custom stuff (this stuff must change according to the selected button).
What I tried
use IBAction for create my subclassed VC2 and show it
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController : UIViewController = storyboard.instantiateViewControllerWithIdentifier("EffectVC") as! GrowingEffect
self.presentViewController(destinationViewController, animated: true, completion: nil)
but I got
Could not cast value of type 'ViewAnimationDemo.EffectVC'
(0x109948570) to 'ViewAnimationDemo.GrowingEffect' (0x109948650).
use PrepareForSegue
but I can't set any subclass
What I really want to do
I know there are some other solution, like not using storyboard, but now I describe exactly what I want to do, hoping this is possibile:
have only one view controller in IB (EffectVC) associate with the class EffectVC. The class EffectVC has some subclasses like GrowingEffect.
In my code I want to instantiate the view controller EffectVC with the subclass that I need: for example instantiate the view controller in IB EffectVC with the class GrowingEffect.
I know that if I have one view controller for every subclass of EffectVC I can do what I want but I don't want so many view controller in IB because they are equal, the only things that I want to change are 2 methods.
I think there are some things mixed up in your setup. You should have 2 view controllers, each set up in its file, and each present in the storyboard with its identifier. It is ok if GrowingEffect inherits from EffectVC.
What you currently do with as! GrowingEffect is actually trying to cast the UIViewController instance you get from calling instantiateViewControllerWithIdentifier("EffectVC") to GrowingEffect. This will not work, because it is of type EffectVC.
Rather, you need to call instantiateViewControllerWithIdentifier("EffectVC") if button X is pressed, and instantiateViewControllerWithIdentifier("GrowingEffect") if button Y is pressed.
EDIT
If you use storyboard, you have to instantiate view controllers using instantiateViewControllerWithIdentifier. But you can only get an instance of GrowingEffect, if it is present on the storyboard.
It is not possible to "cast" an instance of EffectVC to GrowingEffect once created.
So, you have two possibilities here:
Use storyboard and put both view controllers on it. Use instantiateViewControllerWithIdentifier to instantiate the view controller you need, depending on the button pressed.
Do not use storyboard. Then you can create the needed view controller manually and use your UINavigationController's pushViewController method to present it.
You can't cast from parent class to child class, parent class just doesn't have the capacity to know what the child is doing. You can however cast from a child to parent, so you would want to set your view controller as GrowingEffect, then cast it to Effect, but again there is no strong suit to doing this either unless some method needs the parent class and you are using the child class. It looks like you need a redesign of how you want your view controllers laid out. Now I am assuming you have 2 children, lets call GrowingEffect and ShrinkingEffect. In your designer, you set your 1 to GrowingEffect and the other to ShrinkingEffect and make sure they have unique identifiers. Then you can use your view to present an Effect, and pass in either of those objects.

Resources