Xcode 10, Swift 4 - How do I transfer data across multiple view controllers? [duplicate] - ios

This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 3 years ago.
I am currently working on an app and have problems with posting events.
I collect data in a sequence of several views (7 screens) and would like to store the data finally at once (or show all the data in the final view).
These data are, for example, location info, user info, event image, comments, event category, ...
I know how to store the data in the database/storage (firebase) if I collect the data in one or two views.
But in my use case I have seven views and I could not find any elegant method.
What's the best way to do that with Xcode 10?

You can use struct as below code. Make all required variable for all screen in this struct (like string, image etc..). And you can access this from any ViewController.
struct InputDetails {
static var details: InputDetails = InputDetails()
var city: String = ""
var lat: String = ""
var long: String = ""
}
Now to add value in this
InputDetails.details.city = textfiels.text
Now to access first screen value in last screen
print(InputDetails.details.city)
And once your API call or above struct usage is over, make sure to reset all details like below.
InputDetails.details = InputDetails()

There are several ways for passing data between View Controllers. For example you could use an instance property or a segue or the delegation method.
I recommend you study this article which paints a complete picture of the different methods and how to apply them:
How To: Pass Data Between View Controllers In Swift
Edit:
Upon examining the picture in your question I figured that using a segue would be the most appropriate solution here. As it seems from the picture you enter data in one View Controller, pass that onto the second View Controller and finally you upload all the data to Firebase.
I assume that you use storyboards (if not then consult the link above for other methods.) In this example below you will pass a string from one VC to another.
Step 1:
Add a segue between two view controllers. Storyboard -> press ctrl and click on VC one and drag your mouse -> you will see a blue arrow, drag that to VC two and release -> select manual segue: show -> click on the segue and give it an identifier
Step 2:
In VC two, make a string variable:
class SecondViewController: UIViewController {
var stringToPass: String = ""
override func viewDidLoad() {
super.viewDidLoad()
print(stringToPass)
}
Step 3:
In VC one, enter the following:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? SecondViewController {
vc.stringToPass = "This is the string we pass between two VC"
}
}
Step 4:
Then whenever you want to go to the SecondViewController perform the segue like this:
performSegue(withIdentifier: "identifierYouEntered", sender: self)

Related

Problem with transfer of values ​in controllers in Swift

I have login few screens and controllers in my app. First screen is screen with button and moves user to next login view with username, password field and login button. On the controller i have function onClickButton and when i have good data i request to the server with this data.
When server give me callback i have many params about user to set in label in next view.
My structure is like this
Login View -> SecondLogin View and LoginViewController -> TabBarController -> NavigationController -> Table View with TableViewController
My code is
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "afterLoginView" {
if let secondVC = segue.destination as? TabBarViewController {
secondVC.finalName = self.username
}
}
}
When i want transfer my data directly to tableViewController i have error
Thread 1: signal SIGABRT
I do not understand what I'm doing wrong
You'll need these values in almost all view controllers. Create a singleton class to store the logged in user values like this
class UserDetails: NSObject, Codable {
static let shared = UserDetails()
private override init() {
super.init()
}
var finalName: String?
var otherDetails: String?
}
Now when you receive the response from the login api, assign the values in this singleton class.
UserDetails.shared.finalName = "something"//Name received from server callback
Now you can access these values from any view controller.
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(UserDetails.shared.finalName)
}
}
You have some work to do to get to the right view controller. Since your segue is only pointing at the UITabBarViewController, you should put in another guard or if/let statement to get you to the UINavigationController, and then another to finally get you to the UITableViewController, where you can actually refer to your finalName variable.
That would look something like:
if let secondVC = segue.destination as? TabBarViewController {
if let navCon = secondVC.viewController[0] as? UINavigationController {
if let tableVC = navCon.topViewController as? nameOfYourTableVC {
tableVC.finalName = self.username
}
The code is untested, just typed off the top of my head, so please proceed with due caution. Issues such as which tab is the correct NavController would also need to be addressed.
You need to use the actual name of your tableView class in that last if/let. A generic UITableViewController will not include your custom variables.
When server give me callback i have many params about user to set in label in next view.
This is a great example of why you should keep the M in MVC. When you get a response back from the server, store the returned data in your data model. (If you don't have a data model, you should make one.) When a view controller gets some data from the user, such as a user name, it should store that in the model. There's little reason to pass raw data back and forth between view controllers directly... just make sure that all your view controllers have a reference to the model, and have them get and set values there as needed.
This kind of approach will make your code a lot more flexible. It allows view controllers to worry about what they need to do their job, and it gets them out of the business of caring what other view controllers need.
My structure is like this
Login View -> SecondLogin View and LoginViewController -> TabBarController -> NavigationController -> Table View with TableViewController
It might make more sense to load the tab bar controller and then present the login view controller(s) modally. The view controllers that are managed by the tab bar controller can all be set up to refuse to do anything useful until the data they need is present in the data model, and that lets the tab bar controller be the root view controller. That will make it easy to set the model for each of it's child view controllers when the app starts up, and the app can then present the modal login view controllers, also set up with references to the model.

Data is lost after switching views

In Screen A I collect data from the user and then display it on Screen B using a prepare for segue function. Then I noticed if the user goes to Screen C and then back to Screen B the data is gone. How do I keep the data there.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textInput: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let a : BViewController = segue.destination as! BViewController
a.honey = textInput.text!
}
}
import UIKit
class BViewController: UIViewController {
#IBOutlet weak var x: UILabel!
var honey:String = "default"
override func viewDidLoad() {
super.viewDidLoad()
x.text = honey
}
}
Then I noticed if the user goes to Screen C and then back to Screen B the data is gone. How do I keep the data there.
I see two problems:
It looks like you're not actually going back to the "screen B" that you left. The storyboard shows a push segue from B to C, and another from C to B. So I think what's happening is that you're creating another Screen B, separate from the first one. You should look into unwinding the push segue instead of adding another one. Remember: the screens you create in a storyboard aren't individual objects -- they're more like patterns or prototypes of objects, and they can be instantiated multiple times.
More generally, it's a good idea to avoid storing your program's data in views or even in view controllers. The M in MVC stands for model, which is meant to be a separate object (or group of objects) that manage the data and business logic for your app. Using a model wouldn't solve problem #1, but it'll still help you avoid a lot of "hey, where did my data go?" issues.
This is a common problem.
You need to pass the data into each new VC when you instantiate it. The data you want to save can be an instance variable in each VC.
Or you need to save the data you want to persist into NSUserDefaults or use Core Data. If you go this route, once you save the data into one of these places, you can access it anytime from anywhere.

IOS - I want to recycle the server data on Viewcontroller

When we move the a segue from A view to B view or from b view to A view, viewdidload get data from server all the time
So, can we keep the data that viewdidload get at first excute, without starting viewdidload each page?
It's waste of time to get data all the time, when we open each page.
From now we using pageviewcontroller, I think it is inappropriate it.
I using swift language.
If you have good idea, please let me know.
There are very many tutorials on the internet that show how to do what you want. Try a google search on swift pass data between view controllers.
At its very simplest, the second controller needs to have one or properties that can hold data from the first controller. The first controller must implement the following function:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
let secondVC = segue.destinationViewController as! ViewControllerClass
// Pass data to the second view controller.
secondVC.dataFromServer = dataFromServer
secondVC.otherData = otherData // etc.
}
The data will then be passed from the first controller to the second.

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
}
}

Multiple unwind in the same button - Swift

I would like to know if it is possible to assign two different unwind method at the same button. For example:
I have this views navigations:
Home->A->B
Home->C->B
A and C views navigates to B, but then I want to return to previous views using the same B view and controller.
It is possible?
I have been thinking about write assign unwind method to the button programmatically depending what view comes.
Thanks in advance
I'm sorry about my english, is not good.
Here's a Swift solution that worked well for me. The code below only works if you hookup your segues correctly in the storyboard and in code. Checkout this page for great explanations on setting up unwind segues.
In summary:
You're accessing the same view from multiple other views. So, when you segue to a view, you can pass the source view controller (the view that you're currently in) to a property in the view that you're going to.
In your view that you will unwind out of, you can check the property holding the info (the class) on where you came from, and then perform a segue based on what view it is.
The code: (using ex: Home -> A -> B or... Home -> C -> B)
Note: B is the view that will unwind to multiple different views.
In A or C: (code works the same way in both views)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segueIdentifierInViewAthatGoesToViewB" {
let controller:B = segue.destinationViewController as! B
//the code below passes our current view controller class to a property in view B.
//So, view B will know what view we came from.
//In our example, we're coming from view A or C
controller.viewControllerNavigatedFrom = segue.sourceViewController
}
}
In B:
//setup an IBAction that will check the source view controller (that we passed when we segued to this view) and perform a segue based on where we came from. You can hook this up to a button or anything you want really.
//isKindOfClass(A) - "A" would be the name of your class
//setup a property to receive the view controller class where we are coming from
var viewControllerNavigatedFrom:AnyObject?
#IBAction func myButtonPressed(sender: AnyObject) {
if self.viewControllerNavigatedFrom!.isKindOfClass(A) {
//Unwind to view A
performSegueWithIdentifier("unwindFromBbackToA", sender: sender)
}
else if self.viewControllerNavigatedFrom!.isKindOfClass(C) {
//Unwind to view C
performSegueWithIdentifier("unwindFromBbackToC", sender: sender)
}
}
Although, question isn't very clear. But what I could understand is that you want to navigate back to the previous view i.e. B>C or B>A depending upon where user came from.
If so, then check the UINavigationController. It keeps track of the navigation history and automatically adds a back button. Kind of like the back button in our browsers.
Here is a tutorial, although a bit old: Link

Resources