Swift - Passing data between view controllers - ios

I am new to mobile development, and I am building an iOS application with Swift that involves a process where a user answers a series of questions in the form of multiple view controllers in a navigation controller stack.
Basically, I need to capture the text from all the buttons that the user clicks on in his process from controller A to controller D. (One button click leads to the next view controller) By the time, the user reaches controller D, I want to have a array of data that represents what he pressed from controllers A to C.
On controller D, I ask the user if they would like to repeat the process. For example, I would ask the user about a car they own, and, if they own another car, D would take them back to controller A and repeat the process again where the new responses would be saved as another array.
After the user has no more cars, the program moves on to the next section where I would need to load these arrays from before and display different view controllers based on what they entered.
What would be the best way to handle this? I have considered using SQlite to temporarily save the data, but other people have told me to use NSUserDefaults since it takes less time. I will NOT need persistence for this application since I want the user to restart the process from the beginning after they quit the application.

to store a value in one of the viewcontrollers:
let userDefaults = NSUserDefaults.standardUserDefaults()
// user tapped on button 3 (value 2, because 4 buttons => indices 0-3)
userDefaults.setInteger(2, forKey: "Answer1")
userDefaults.synchronize()
to load the values after answering all the questions:
let userDefaults = NSUserDefaults.standardUserDefaults()
let answer1 = userDefaults.integerForKey("Answer1")
let answer2 = userDefaults.integerForKey("Answer2")
...
userDefaults.synchronize()
to delete the saved stuff on application launch (in applicationdidfinishlaunching):
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.removeObjectForKey("Answer1")
userDefaults.removeObjectForKey("Answer2")
...
userDefaults.synchronize()
good luck! :)

I think the path from controller A to controller D can be called a Session for your user.
What you need is a way to store information about this session outside any controller, and this session to be accessible from any controller.
You should create a class Session (sorry, example in objective-C not in Swift):
Session.h
#interface Session : NSObject
- (void)addValue:(id)value;
- (NSArray *)allValues;
- (NSUInteger)valueCount;
- (void)reset;
#end
Session.m
#interface Session ()
{
NSMutableArray *_values;
}
#end
#implementation Session
- (instancetype)init
{
self = [super init];
if( !self ) return nil;
_values = [[NSMutableArray alloc] init];
return self;
}
- (void)addValue:(id)value
{
[_values addObject:value];
}
- (NSUInteger)valueCount
{
return _values.count;
}
- (NSArray *)allValues
{
// NSArray to have a immutable array of values
return [NSArray arrayWithArray:_values];
}
- (void)reset
{
[_values removeAllObjects];
}
#end
This way, you can create a globally accessible Session object (Session *s = [[Session alloc] init];), and use it throughout all your controllers.

Edit: Learned a lot since my original answer.
What I do (this may not be the only way, but works for me), is to instantiate a class of the kind I need on the main View Controller, as well as on each View Controller that modifies the data. If I need to update information that refers to that class, I pass the object in the segue (Swift 3):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case constants.aSegue: // not the real name of my segue :-P
// grab inside the navcon
if let navCon = segue.destination as? UINavigationController {
if let destinationVC = navCon.viewControllers.first as? CGSettingsPopupViewController {
destinationVC.remoteObject = localObject
// localObject is a kind of the class I need, as is remoteObject
// now, changes to destinationVC.remoteObject will be accessible locally, when the destinationVC returns control
}
}
default:
break
}
}
}
Since they are passed by reference, changing data members in 'remoteObject' also changes the ones in 'localObject' since they refer to the same object.

Related

Passing Data from SettingsView Controller to Mainstoryboard - Swift

I am a newbie to iOS app development trying to build a tip calculator. The basic functioning of the app is completed. My Mainstoryboard has a segment control which shows three % values such as 10, 20, 30. There is a Settings button which on click takes me to a new page and shows the similar % values in a segment control.
What I want to do is that, when a number is clicked (a segment) it should be saved as the default tip% value. How should I pass this value to the Mainstoryboard function where I have written a function to calculate the tip amount?
I suppose you are using a segue for moving to the other view, so use prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "tipSegue"
{
if let destinationVC = segue.destinationViewController as? OtherViewController {
destinationVC.tipPercent = someValue
}
}
}
You can write a protocol for setting the default percentage, and have your MainViewController conform to that protocol. When a user taps a button in your SettingsViewController you can call a function (defined within the protocol) to let the "delegate" (MainViewController) know what has happened, and set your default variable accordingly. If this is a permanent setting it might be better to use UserDefaults, as this is exactly what that was designed for. Then, upon loading MainViewController you can access that UserDefaults variable, if it exists.
A little protocol help:
Your protocol could be defined very simply - something like this (and this has to be declared outside of your viewControllers - I often do it above the class declaration of a related viewController):
protocol SettingsDelegate {
func didUpdateDefaultTipPercent(to percent: Float)
}
That's it for the protocol declaration, unless you anticipate needing other functions. Note that you don't actually define the functionality of that function - each viewController that conforms to the protocol may have a different definition of that.
Then, in your settingsViewController you might have an optional variable for protocol conformer like this:
weak var settingsDelegate: SettingsDelegate?
and after the user chooses a default percentage you can safely check to see if the delegate exists and pass that new number to the delegate like so:
if let delegate = self.settingsDelegate {
delegate.didUpdateDefaultTipPercent(to: 15.0) //this "15.0" will come from your segmented control action or whatever
}
In your MainViewController, in your prepareFor(segue... you will need to check to see if you are going to settings, and set Main as the delegate:
...
if let settings = segue.destination as? SettingsViewController {
settings.settingsDelegate = self
}
And, finally, you'll need to make your MainViewController conform to the protocol. I usually do this as an extension just to make it easier to find, and to keep it separated from other things:
extension MainViewController: SettingsDelegate {
func didUpdateDefaultTipPercent(to percent: Float) {
self.defaultPercentage = percent
}
}

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.

unwind segue pass data to different viewcontrollers

I have an app which have 4 different forms. these forms can be completed by clicking the different questions and being lead to a viewcontroller which holds the options. lets call this the OptionViewController. Now I have 4 different forms with different options but all using OptionViewController to pull data from the database, I need to unwind the segue and pass data.
Since there might be 4 different view controllers it might be coming from, I need to make sure that the information is passed properly, i.e. identify if the destinationviewcontroller the unwindsegue is performing the first, second, third or fourth viewcontroller.
I thought I might do something like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "optionSelected" {
if segue.destinationViewController == FirstViewController {
//pass data
} else if segue.destinationViewController == SecondViewController {
//pass data
}
}
}
But obviously I cannot perform segue.destinationViewController == FirstViewController
What should I actually be doing? Or should i just create one OptionViewController for every form, which would solve my problem, but I am not sure if overall the app performance will drop due to the increase of view controllers
Thanks for any help in advance
To test if the destination view controller is of a specific class, use the Swift keyword is:
if segue.destinationViewController is FirstViewController {
Alternatively, you can assign the viewController to a variable using optional binding with an optional cast:
if let dvc = segue.destinationViewController as? FirstViewController {
// dvc will have type FirstViewController so you can access specific
// properties of FirstViewController using dvc
}

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. =)

Resources