I have an app where a user logs in by entering their details and that sends a HTTP GET request to my API which authenticates the user and sends the users data/user object back from the database.
Once this is done, within my HTTP request which is triggered on button tap I send the users data onto the next view and perform a transition to the view by using the following code:
if let parseJSON = json
{
// parseJSON contains the return data. We are programmatically creating a segue between two views
// passing data between them.
dispatch_async(dispatch_get_main_queue())
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("mainView") as! MainViewController
var fullname = parseJSON["employeeName"] as! String
var job = parseJSON["jobTitle"] as! String
var email = parseJSON["email"] as! String
vc.username = fullname
vc.jobTitle = job
vc.userEmail = email
self.presentViewController(vc, animated: true, completion: nil)
}
The above works perfectly.The from my second view I create a prepare for segue for another view where I am trying to pass the user data that was just passed to the 2nd view when the user logged in by using the code below:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "settings") {
// pass data to next view
let viewController = segue.destinationViewController as! SettingsTableViewController
viewController.username = username
viewController.jobTitle = jobTitle
}
}
This again works properly. When I tap on the button to go to view 3 the prepareforsegue function passes the data and shows the following view (ignore email field):
But when I click on the back button and try to access to the same view all the data thats coming from the API and is being passed from View 1 to View 2 to View 3 disappears. See below:
I DO understand the reason why this happening, I am passing data in 1 direction and it is not being saved only being passed from one view to another and when I go back a view that process of passing between breaks and hence the data is lost.
My question is how can I preserve data when the user first logs in and the API sends the data back. Can I save it? and keep passing it through multiple views and still keep it no matter what?
Any help will be greatly appreciated.
Thanks in advance.
In short : Store the data you get in an instance variable in your view and use that variable for the segues.
Long Explanation:
Best practice is to create a model class for the stuff you're getting from your API, put all data in a variable of that class when retrieving the info, and like a said have a variable of that type in your view classes.
Tip: read a bit about the MVC paradigma's (lotsa stuff online, if you have some time read the book Design Patterns by the gang of four)
Related
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.
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.
In my app each user sets up a profile page and all this info is stored on Parse. When the current user hits a button it segues to a tableViewController that displays in cells all the other users on the app. In each cell there is a button which the user presses to view the full profile page of the user that they have selected. I have a VC set up to hold all the info I just don't know how to display the particular user that the current user has selected into it. How do I single out the chosen users information? I have read the Parse documentation on relational queries but I can't seem to get my head around it. https://parse.com/docs/ios/guide#queries-relational-queries
So in finer detail basically all my users are displaying on a VC. It has name and profile picture, when the user clicks on the info button beside a certain user it should open a new screen and display all of this users information stored on Parse.
I have tried creating a static outside the class
struct Data { var OtherName:String! var id:String! }
And then in my ViewDidLoad:
let query = PFQuery(className: "_User")
// otherQuery.orderByDescending("createdAt")
query.whereKey("username", notEqualTo:PFUser.currentUser()!.username!)
query.findObjectsInBackgroundWithBlock { (users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// success
print(users!.count)
for user in users! {
self.imageFiles.append(user["image"] as! PFFile)
self.instrumentText.append(user["instrument"] as! String)
self.nameText.append(user["name"] as! String)
self.ageText.append(user["age"] as! String)
// self.locationText.append(user["location"] as! PFGeoPoint)
var singleData = Data()
singleData.id = user.objectId
singleData.OtherName = user["name"] as! String
print("\(singleData)")
} // users
Then on the VC that the info button segues to I am trying to call it and upload each label with just the user that was selected.
self.userName.text = "" as String
But the "" is staying blank.
Then I was thinking maybe if I add that when the more info button is pressed that the user it was pressed on gets chosen and is added to a chosen string on Parse and then I just have to call the user from the chosen string when I am on the chosen users profile page and call the info that way and then when the user clicks the back button the user is no longer chosen. It just seems like I would be adding and taking away a lot of users to "chosen" in Parse is there a better way?
I am able to display all the users info but how do I just display a chosen user from Parse without using the objectId because at the top of each cell it will return a different user here each time depending on settings previously set on a different VC such as only show users in a certain age bracket etc. I know I must set an Id of some sort on button touched but I am at a complete loss. The only online help out there that I can find is set for other languages eg. https://parse.com/docs/js/guide#users-querying>
So I guess my question is :
1.Does anyone know a tutorial they could point me towards.
2.Does anyone know how I even get started and I can go from there myself.
3.Can anyone help?
*Swift and Parse newbie little to no experience in other languages.
New code in firstVC:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "userProfileDetailsSegue" {
if let indexPath = resultsPageTableView.indexPathForSelectedRow {
let controller = (segue.destinationViewController) as! UserProfileDetailsViewController
***error here: UserProfileDetailsViewController.userToShowDetail = indexPath.row
//line above is the one I can't get to work it needs to equal to the cell selected?
}
}
}
New Code in destinationVC:
var userToShowDetail: PFUser? {
didSet {
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let userToShowDetail: PFUser = self.userToShowDetail {
if let label = self.userName {
nameText.append(userToShowDetail["name"] as! String)
// self.nameText.append(user["name"] as! String)
}
}
}
You're going to need to do a couple of things for this to work. First, in the view controller that you're using to show another user's information, you need to set a property like var userToShowDetail: PFUser?. Then in your view with all of the users of your app, you need to create a segue to bring them from the current view to the detail view. Assuming you're using a TableView, you can connect just one prototype cell to the detail view you're trying to navigate. Then you'll need to override prepareForSegue and set the new view controller's user equal to the user at the row that was selected. I don't know how you're storing your objects of user's of your app, but you should probably be able to do something like this.
override fun prepareForSegue(segue: UIStoryboardSegue) {
if segue.identifer == "segueToShowDetailInfo" {
//get the index path for the row that was selected
let indexPath = self.tableView.indexPathForSelectedRow()
//get the PFUser object for that particular row
let userToShow = self.appUsers[indexPath.row]
//create your new view controller
let newVc = segue.destinationViewController as! DestinationVC
//assign the new vc's property to your object
newVc.userToShowDetail = userToShow
}
}
But in your query, you probably shouldn't have a separate array of the the different parts of the user's data. You should just make one array, call it var appUsers = [PFUser]() and then when you get a user back in your query, just say:
for user in users! {
self.appUsers.append(user) as! PFUser
}
There, now you have a whole array of objects that holds a reference to every user. When someone is using your app, and selects a cell, then you can perform the prepare for segue method as above.
Note that I also noticed you have a didSet on your variable in your new view controller. Calling a function to update the UI after this variable is set will not work, and may crash, because the view controller doesn't have a window yet.
I have two view controllers, first view containing "user form" for inputs and second to display the final results. Everything seems to be working fine, i.e. I can send the final results using "prepareForSegue" function (see below)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var SecondVC: SecondViewController = segue.destinationViewController as! SecondViewController
SecondVC.receivedDP_Summary = summaryDP
SecondVC.receivedDC_Summary = summaryDC
SecondVC.receivedDP_CA_Summary = summaryDrillPipe_CsgAnnulus
SecondVC.receivedDC_CA_Summary = summaryDrillCollar_CsgAnnulus
}
However, as soon as I return to first control view after initialising segue, all the user inputs disappear. Is there any way to store the user inputs within the respective text field so they can be manipulated to see varying results?
I am still very new to programming, so I will really appreciate any help
I'm trying to run a url request to get a JSON file after a certain table row is selected, based on the row a unique ID is sent with the URL request and a different JSON is generated. Here is my prepareforSegue
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
var divisionScene = segue.destinationViewController as! DivisionViewController
// Pass the selected object to the new view controller.
if let indexPath = tableView.indexPathForSelectedRow() {
let arrayIndex = indexPath.row
//println("Index: \(arrayIndex)")
torneoIDTransfer = torneos[arrayIndex].torneoID
//println("\(torneoIDTransfer)")
//check second url with second request type same token
//sets url to string using token
let tercerURL = NSURL(string: "http://api.zione.mx/get.json.asp?tr=3&tkn=\(tkn)&tor=\(torneoIDTransfer)")
//initializes request
let request = NSURLRequest(URL: tercerURL!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.currentQueue()) { response, jsonDataRequest3, error in
let dataRequest3 = jsonDataRequest3
//takes data, saves it as json
let tercerJSON = JSON(data: jsonDataRequest3)
//checks to see that contents != nil, meaning the JSON file was found
if tercerJSON != nil {
//checks amount of tournaments available in order to generate table.
let divisionCount = tercerJSON["lista-divisiones"].count
//sets global variable numero de torneos
numeroDeDivisiones = divisionCount
//for loop to go insert into Torneo nuevo each ID and name by using count from above
for var index = 0; index < divisionCount; ++index {
var divisionID = Int(tercerJSON["lista-divisiones" ][index]["DivisionID"].number!)
var nomDivision = tercerJSON["lista-divisiones"][index]["nomDivision"].string
//println("\(divisionID)")
//println("\(nomDivision)")
var divisionNuevo = listaDivisiones(divisionID: divisionID, nomDivision: nomDivision!)
divisiones.append(divisionNuevo)
numeroDeDivisiones = 10
print("WHO IS FIRST")
}
}
print("\(divisiones[0].nomDivision)")
}
}
}
And I created my segway by dragging from the table cell to the new view Controller. However when I click the table cell the transition occurs instantly, before the request has a chance to finish and as a result no data is displayed.
It would be ideal to fetch and process the data in the background, long before the user ever selects a table row. If this is not possible, then I would suggest having your destination view controller do the URL request. The URL request happens asynchronously, so it will never have a chance to finish before your source view controller is deallocated.
In your source view controller, modify prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var divisionScene = segue.destinationViewController as! DivisionViewController
if let indexPath = tableView.indexPathForSelectedRow() {
let arrayIndex = indexPath.row
torneoIDTransfer = torneos[arrayIndex].torneoID
let tercerURL = NSURL(string: "http://api.zione.mx/get.json.asp?tr=3&tkn=\(tkn)&tor=\(torneoIDTransfer)")
divisionScene.fetchDataAtURL(tercerURL)
}
}
You'll need to define a method inside your destination view controller to handle the fetching.
func fetchDataAtURL(URL: NSURL) {
let request = NSURLRequest(URL: tercerURL!)
NSURLConnection.sendAsynchronousRequest( // ... your fetching logic here
}
You'll also need some logic to display the data once it arrives. I would suggest putting it into your request's completion callback (or rather, having the callback trigger a display update). If your divisionScene is a tableView, you might be able to call reloadData (after you update the data source). If not, you'll need some other way to update the UI. If you are updating the UI, make sure to dispatch to the main queue for that part.
Doing this way at least passes the URL loading responsibility to the destination view controller, which will at least be around when the data finally gets there.
" the transition occurs instantly, before the request has a chance to finish".
Of course it does, that is exactly what asynchronous means. You make a point of mentioning it is asynchronous but you must have a misunderstanding about what that means. Explain what you think it means and what you expected your code to so so that you can be better educated by us.
When you call sendAsynchronousRequest() think of your program as branching into two (actually that is what does effectively happen). One part is your original code which will continue to execute i.e your prepareFoSegue code will continue to execute.
Meanwhile, in parallel, the OS will execute the request, and when the request has finished the code in the block that you passed to sendAsynchronousRequest() will be executed. Therefore your prepareForSeque() function will finish before the Json has been received.
But apart from all that, you should not be attempting or hoping or wanting the JSon to be fetched before the segue transition - to do this would halt your ui. Suppose sendAsynchronousRequest() was instead sendSynchronousRequest() and it took 10 seconds to complete, what do you think the consequence would be on your app when it runs?
You should either fetch your data a long time before you GUI is ready to display it, or if that is not possible, display your GUI immediately with no data and then update it as the data arrives.