iOS Swift doesn't pass data back via delegation - ios

I'm segueing from one view controller to another like so:
switch popIndex {
case 301:
let destVC = segue.destination as! IDPopoverViewController
destVC.popIndex = popIndex
destVC.titleString = "Issuer or Category"
...
In the second VC, the user enters data (String). I'm passing this data back to the originating VC via delegation:
case 301:
newIssuerDelegate?.appendName(name: dataToReturn)
print("In delegate call, dataToReturn = \(dataToReturn)")
...
The delegate function:
func appendName(name:String) {
newIssuer.name = name
issuerNameLabel.text = name
print("In appendName, name = \(name)")
saveIt()
}
The Protocol
protocol NewIssuerProtocol {
func appendName(name:String)
...
The Problem:
The data seemingly never arrives at the delegate function. The data doesn't appear on issuerNameLabel, nor in the print statement. Doesn't crash, just returns to the originating VC and idles, awaiting another command.
I've placed breakpoints in the delegate call (stops, as expected), and in the delegate function. the receipt of the data isn't acknowledged by the breakpoint, nor by the print statement. I should mention that none of the (6) other delegate functions in the same protocol appear to receive or display the relevant data, either. However, several similarly constructed delegation schemes throughout the app are working just fine.
My Plea
Can someone spot what I'm doing wrong on this one?
All help much appreciated!
Fix
I neglected to set the delegate when preparing for the segue, as was generously pointed out below by #vadian!
case 301:
let destVC = segue.destination as! IDPopoverViewController
destVC.popIndex = popIndex
destVC.titleString = "Issuer or Category"
destVC.newIssuerDelegate = self

Related

How to make Http request load on time before table view cell

I may be missing a design or simple detail here as I'm new to iOS, but can't figure this one out:
I've got a func called getWeatherData(), that based on the location received from another view controller, gets weather conditions from a http request and assigns it to HikeModel. This function is called in method of ViewDidLoad().
After func gets the weather conditions, it passes this to a child view controller. Which Child VC is a Table View Controller.
Problem is my TableViewCell loads faster than the http request takes to be done, in the parent view controller. And when the cell labels and images try to retrieve information that should have values assigned, theirs is nothing, since the http request hasn't finished on time.
I also attempted using DispatchQueue.main.async but no difference was shown.
I'm doing a native http request with Swift4.
For more detail on the structure of my project, I'm doing based on this work: https://medium.com/#phillfarrugia/re-creating-the-siri-shortcuts-drawer-interaction-9b2bc94e0b05
In ViewDidLoad
let group = DispatchGroup()
group.enter()
DispatchQueue.main.sync {
self.getWeatherData(hikeLocation: self.startHikeLocationString)
group.leave()
}
Method sending information fillDrawer, with what should contain weather vars assigned to HikeModel.
private func configureDrawerViewController() {
let compressedHeight = ExpansionState.height(forState: .compressed, inContainer: view.bounds)
let compressedTopConstraint = view.bounds.height - compressedHeight
containerViewTopConstraint.constant = compressedTopConstraint
previousContainerViewTopConstraint = containerViewTopConstraint.constant
// NB: Handle this in a more clean and production ready fashion.
if let drawerViewController = children.first as? DrawerViewController {
//send distnace too
drawerViewController.delegate = self
drawerViewController.fillDrawer(hike: self.hikeModel, userLocation: self.userLocation)
}
TableView CellsForRowAt
cell.temperature.text = hikeModel.temperature
cell.weather.text = hikeModel.weather
cell.weatherIcon.text = hikeModel.weatherIcon
cell.humidity.text = hikeModel.humidity
cell.barometer.text = hikeModel.barometer
cell.sunrise.text = hikeModel.sunrise
cell.sunset.text = hikeModel.sunset
I expect the tableViewCell in child controller to load after the http request is done in parent view controller.
Your code is incomplete to identify issue but you should have to do like:
1) load data from webservice then,
2) reload tableView
In your ViewDidLoad
let group = DispatchGroup()
group.enter()
DispatchQueue.main.sync {
self.getWeatherData(hikeLocation: self.startHikeLocationString)
group.leave()
}
add line after complete process of data modeling and it should be in mainQueue
yourtable.reloadData()

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

Persist data and pass to multiple views

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)

UINotification selecting from handleActionWithIdentifier display specific viewController

I am new to iOS programming and I only understand swift language for now. For my app, I have set up user, local and remote notifications. The local notifications have 2 actions, 1 to dismiss and the other is to direct the user to a specific viewController.
My viewController hierarchy to the specific viewController I want to display is tabBarController -> (2nd tab) tableViewController -> (one of the many tablecells) viewController
After Richard's suggestion, I have simplified my code with the above hierarchy. I have performed a background fetch to my server in response to a remote push notification and save content locally before creating a local notification to inform the user. I have managed to set up to the point where I guide the user to the correct Tab on the tabViewController using self.window?.rootViewController.selectedIndex = 1 within application:handleActionWithIdentifier in App delegate.
How do I proceed with selecting the correct tablecell assuming that the local notification holds an index that can be used?
Edit:
After some tinkering, I managed to get the viewController I wanted to be displayed.
if var tabb = self.window?.rootViewController as? UITabBarController {
tabb.selectedIndex = 1
var controllers = tabb.childViewControllers
for controller in controllers {
if let navcon = controller as? UINavigationController {
if let tblvc = navcon.childViewControllers.first as? QuestionnaireTableViewController {
tblvc.becomeFirstResponder()
tblvc.moveUserToQuestionnaire(questionID)
}
}
}
}
func moveUserToQuestionnaire(questionID : String) {
var list = questionnaireObj.getQuestionnaireListData()
for item in list {
var selected = item["questionnaire_id"] as! NSString as String
if selected == questionID {
selectedUserID = item["study_id"] as! String
selectedReplyBatchID = item["reply_batch_id"] as! NSNumber
selectedQuestionnaireID = questionID
performSegueWithIdentifier("SelectQuestionnaire",sender:self)
break
}
}
}
If this is a correct method to use, please inform me. Thanks! Hope it also helps someone in the process. =)

Running a segue after selecting a table once a Asynchronous request is finished

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.

Resources