I am creating my first simple budgeting app. Basically, I take a few user inputs like monthly income & savings goal. Then they click "start", & the app calculates stuff such as, their daily budget etc.
Here I'm running into trouble. After all the calculations, I display "how much you can spend each day" (e.g. $20 a day), which I pass forward through segues from their original inputs on the original screen.
Now, in this VC (UserInfoVC) I created a button which lets them add how much money they spent today. So when they click this "add money spent" button, I open a new VC (AddSubtractMoney) where I present a calculator where they can enter how much they spent today (i.e. $12) and click submit.
I run their input compared to their daily budget to get a New daily budget.
Now, I'm having trouble passing this updated number backwards, to display it on the previous VC on the label "dailySpendingLimitLabel". I know segues are not the best way to go about passing data backwards.
I've tried closures, but I end up getting lost in the syntax, and protocols and delegates (it's my 2nd month coding).
Is there a simple way to achieve passing this data back to the previous VC and populating the data in that previous display label?
Below is the code.
The First snippet is from the UserInfoVC where I display their originally entered data that I segued through. The Second snippet is from the AddSubtractMoney class where I placed the calculator and created an object "newestUpdate" inside a function that allows me to calculate the number they entered on the calculator minus their old daily budget. To arrive at a new budget which I want to present backwards to the UserInfoVC.
class UserInfoViewController : ViewController {
var userNamePassedOver : String?
var userDailyBudgetPassedOver : Double = 99.0
var userDailySavingsPassedOver : Double = 778.00
var userMonthlyEarningsPassedOver : Double?
var userDesiredSavingsPassedOver : Double?
var newAmountPassedBack : Double = 0.0
#IBOutlet weak var dailySavingsNumberLabel: UILabel!
#IBOutlet weak var userNameLabel: UILabel!
#IBOutlet weak var dailySpendingLimitLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
userNameLabel.text = userNamePassedOver
dailySpendingLimitLabel.text = String(format: "%.2f", userDailyBudgetPassedOver)
dailySavingsNumberLabel.text = String(format: "%.2f", userDailySavingsPassedOver)
}
#IBAction func addSubtractMoneyPressed(_ sender: UIButton) {
performSegue(withIdentifier: "addOrSubtractMoney", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addOrSubtractMoney"{
let addOrSubtractMoneyVC = segue.destination as! AddSubtractMoney
addOrSubtractMoneyVC.dailyBudgetPassedThrough = userDailyBudgetPassedOver
}
}
}
extension UserInfoViewController: AddSubtractMoneyDelegate {
func calculatedValue(value: Double) {
dailySpendingLimitLabel.text = String(userDailyBudgetPassedOver - value)
}
}
import UIKit
protocol AddSubtractMoneyDelegate {
func calculatedValue(value: Double)
}
class AddSubtractMoney: UIViewController {
#IBOutlet weak var outputLabel: UILabel!
var runningNumber = ""
var finalNumberPassedOver : Double?
var amountPassedBackToUserInfo : Double = 0.0
var dailyBudgetPassedThrough : Double = 0.0
var delegate: AddSubtractMoneyDelegate?
override func viewDidLoad() {
super.viewDidLoad()
outputLabel.text = "0"
// Do any additional setup after loading the view.
}
#IBAction func buttonPressed(_ sender: UIButton) {
runningNumber += "\(sender.tag)"
outputLabel.text = runningNumber
}
#IBAction func submitNewInfo(_ sender: UIButton) {
// FIX FIX
AddSubtractMoneyController.addToMoneySpentArray(amountISpent: outputLabel.text!)
sendBackUpdatedNumber()
dismiss(animated: true, completion: nil)
}
#IBAction func allClearedPressed(_ sender: UIButton) {
runningNumber = ""
outputLabel.text = "0"
}
// THIS LINE PRODUCES THE CORRECT INPUT IN OUTPUT CONSOLE WHEN I PRINT- BUT I CANT FIGURE HOW TO TRANSFER IT BACK TO PREVIOUS VC
func sendBackUpdatedNumber(){
let newestUpdate = UserInfo(whatYouSpentToday: runningNumber, oldDailyBudgetPassed: dailyBudgetPassedThrough)
amountPassedBackToUserInfo = dailyBudgetPassedThrough - Double(runningNumber)!
newestUpdate.goalToSaveDaily = amountPassedBackToUserInfo
print(amountPassedBackToUserInfo)
self.delegate?.calculatedValue(value: amountPassedBackToUserInfo)
}
}
My suggestion is to use a callback closure. It's less code and easier to handle than protocol / delegate.
In AddSubtractMoney declare a callback variable and call it in sendBackUpdatedNumber passing the Double value
class AddSubtractMoney: UIViewController {
// ...
var callback : ((Double)->())?
// ...
func sendBackUpdatedNumber(){
let newestUpdate = UserInfo(whatYouSpentToday: runningNumber, oldDailyBudgetPassed: dailyBudgetPassedThrough)
amountPassedBackToUserInfo = dailyBudgetPassedThrough - Double(runningNumber)!
newestUpdate.goalToSaveDaily = amountPassedBackToUserInfo
print(amountPassedBackToUserInfo)
callback?(amountPassedBackToUserInfo)
}
}
In prepare(for segue assign the closure to the callback variable and add the code to be executed on return
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addOrSubtractMoney"{
let addOrSubtractMoneyVC = segue.destination as! AddSubtractMoney
addOrSubtractMoneyVC.callback = { result in
print(result)
// do something with the result
}
addOrSubtractMoneyVC.dailyBudgetPassedThrough = userDailyBudgetPassedOver
}
}
Using delegate
if segue.identifier == "addOrSubtractMoney" {
let addOrSubtractMoneyVC = segue.destination as! AddSubtractMoney
addOrSubtractMoneyVC.dailyBudgetPassedThrough = userDailyBudgetPassedOver
addOrSubtractMoneyVC.delegate = self
}
}
You need to add delegate property in AddSubtractMoney class
var delegate: AddSubtractMoneyDelegate?
Create Protocol in AddSubtractMoney class
protocol AddSubtractMoneyDelegate {
func calculatedValue(value: Double)
}
And respond to delegate
func sendBackUpdatedNumber(){
let newestUpdate = UserInfo(whatYouSpentToday: runningNumber, oldDailyBudgetPassed: dailyBudgetPassedThrough)
amountPassedBackToUserInfo = dailyBudgetPassedThrough - Double(runningNumber)!
newestUpdate.goalToSaveDaily = amountPassedBackToUserInfo
print(amountPassedBackToUserInfo)
self.delegate.calculatedValue(value: amountPassedBackToUserInfo)
}
Now you need to implement this delegate method in class where delegate is set.
Here in UserInfoViewController class delegate is set so you need to implement its delegate method
extension UserInfoViewController: AddSubtractMoneyDelegate {
func calculatedValue(value: Double) {
//set label here
}
}
You could possibly also use an unwind segue to pass back the data.
If you don't under stand flow behind delegate(protocol oriented), you can simply go through below code. it only works if both class
But it is not a good practice
Learn about protocol, closure, or Notification Center broadcasting for mostly used, flexible and reusable coding methods.
UserInfoViewController
class UserInfoViewController : ViewController {
fun receiveBackUpdatedNumber(numberString:String){
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addOrSubtractMoney"{
let addOrSubtractMoneyVC = segue.destination as! AddSubtractMoney
addOrSubtractMoneyVC.userInfoViewController = self
}
}
}
}
AddSubtractMoney
class AddSubtractMoney: UIViewController {
var userInfoViewController: UserInfoViewController!
var updatedNumber = ""
func sendBackUpdatedNumber(){
self.userInfoViewController.receiveBackUpdatedNumber(numberString: updatedNumber)
}
}
If you are confirtable you can go with protocols.. protocols insist a class to compulsory implement a method, which make code more reusable and independent.
In Above method we are passing instance of current viewcontroller(UserInfoViewController) to next viewcontroller(AddSubtractMoney) on performing segue, So by that we can access any properties of function in UserInfoViewController from AddSubtractMoney. So it make easy to pass data from AddSubtractMoney to -> UserInfoViewController
Related
I am trying to make a list of users and their passwords in one view controller, save that information in a dictionary, and send that dictionary to another view controller which asks the user to input their username/password combination to authorize the log in. (the key is the username and the value is the password). Is there a way I can send the dictionary from SecondVC to the FirstVC?
First View Controller
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var Username: UITextField!
#IBOutlet weak var Verification: UILabel!
#IBOutlet weak var Password: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Username.delegate = self
Password.delegate = self
}
var usersDict = [String : String]()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let des = segue.destination as? AccountViewController {
des.usersDict = usersDict
}
}
#IBAction func Authorization(_ sender: Any) {
for ( key , value ) in usersDict{
let v = key.count
var start = 0
if start <= v{
if Username.text == key{
if Password.text == value{
Verification.text = "Looks Good"
}
}
else{
start += 1
}
}
else{
Verification.text = "Yikes"
}
}
}
}
Second View Controller
class AccountViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var CreateUsername: UITextField!
#IBOutlet weak var CreatePassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
CreateUsername.delegate = self
CreatePassword.delegate = self
// Do any additional setup after loading the view.
}
var usersDict = [ String : String ]()
#IBAction func MakeANewAccount(_ sender: Any) {
usersDict[CreateUsername.text!] = CreatePassword.text!
}
}
I have made there dictionary, but it will only send in the beginning and won't update after creating the original account. (dictionary it is sending is empty)
With a segue add this method inside ViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let des = segue.destination as? AccountViewController {
des.usersDict = yourDicHere
}
}
Here's a general pattern for making a controller work with data from some object it creates, in this case a second controller.
Try applying it to your situation and let me know if you run into problems.
protocol Processor {
func process(_ dict: [String : String])
}
class FirstController: UIViewController, Processor {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? SecondController {
controller.delegate = self
} else {
print("Unexpected view controller \(segue.destination)")
}
}
func process(_ dict: [String : String]) {
}
}
class SecondController: UIViewController {
var delegate: Processor?
func someWork() {
if let processor = delegate {
processor.process(["Name" : "Pwd"])
} else {
print("Delegate not assigned")
}
}
}
I'm having a problem passing array data back from one view controller ("VC2") to another ("VC1"). I do everything by the rules. I made a proper protocol in VC1.
But unfortunately I could not get the data back.
This is my code:
VC2
protocol RecivedData {
func dataRecived(nameArray: [String] , priceArray: [String])
}
var popUpdelegate : RecivedData?
#IBAction func nextBtnTapped(_ sender: UIButton) {
print("Hello")
let namedata = itemNameArr
let namePrice = itemPriceArr
self.popUpdelegate?.dataRecived(nameArray: namedata, priceArray: namePrice)
print(namedata)
print(namePrice)
self.view.removeFromSuperview()
}
VC1
class HomeVC: UIViewController , RecivedData {
func dataRecived(nameArray: [String], priceArray: [String]) {
itemNameArr += nameArray
itemPriceArr += priceArray
print(itemNameArr, itemPriceArr)
print ("This is HomeVC")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sendSegue"{
let secondVC: AddOnItemPopUpVC = segue.destination as! AddOnItemPopUpVC
secondVC.popUpdelegate = self
}
}
}
Replace your code with this
protocol RecivedData : class {
func dataRecived(nameArray: [String] , priceArray: [String])
}
And
weak var popUpdelegate : RecivedData?
Now it will start working.
Make sure there will be no typo in segue name.
I'm at my wits end. I've got basically the same thing working (but with a simpler function to generate the variables in a test project) but now my code is passing blank arrays to the next view controller.
I'm just wanting to pass 2 arrays, the values for which are generated through a function, to the next VC - I've triple checked my code and I'm sure I've done it right but obviously not! Any ideas!?
1st VC :
import UIKit
class WorkoutSetupController: UIViewController {
#IBOutlet weak var timeInputField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()}
//Create function to generate a new random workout, with a random exercise array, and random reps array
func generateNewWorkout() -> (randomExerciseArray:[String], randomRepsArray:[Int]) {
let randomKey = Int(arc4random_uniform(4) + 3)
var workoutSet = [String]()
let possibleExercises = masterExerciseArray
var repsSet = [Int]()
while workoutSet.count < (randomKey) {
let randomIndex = Int(arc4random_uniform(UInt32(possibleExercises.count)))
workoutSet.append(possibleExercises[randomIndex])
}
while repsSet.count < (randomKey) {
repsSet.append(Int(arc4random_uniform(30)))
}
//return the values and print them to make sure the function is working (it is!)
let workoutToBePassed = workoutSet
let repsToBePassed = repsSet
print (workoutToBePassed, repsToBePassed)
return (workoutToBePassed, repsToBePassed)
}
//perform a manual segue to the WorkoutController page, using the function above to generate the values to be passed
#IBAction func workoutButtonPressed(_ sender: UIButton) {
performSegue(withIdentifier: "doItNow", sender: generateNewWorkout())
}
//set the variables to be passed to the new VC, accepting the format of the returned arrays from my function for the variables
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "doItNow" {
if let destVC = segue.destination as? WorkoutController, let variablesToBePassed = sender as? (arr1:[String],arr2:[Int]) {
print (variablesToBePassed.arr1, variablesToBePassed.arr2)
destVC.selectedWorkoutExerciseArray = variablesToBePassed.arr1
destVC.selectedWorkoutRepsArray = variablesToBePassed.arr2
}
}
}
}
second VC :
import UIKit
class WorkoutController: UIViewController {
//set the variables we're passing from the first VC with the right data type
var selectedWorkoutExerciseArray = [String]()
var selectedWorkoutRepsArray = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func printButtonPressed(_ sender: UIButton) {
//print the variables out - this is currently printing blank arrays arghhghghghh!
print (selectedWorkoutExerciseArray, selectedWorkoutRepsArray)
}
}
Where am I going wrong!? I can't see it anywhere! Please help!
The parameter labels of the tuple don't match, you have to check
let variablesToBePassed = sender as? (randomExerciseArray:[String], randomRepsArray:[Int]) {
This is a good example of the benefit of force unwrapping objects which must have a value of the expected type
let destVC = segue.destination as! WorkoutController
let variablesToBePassed = sender as! (arr1:[String],arr2:[Int])
If the code crashes it reveals the design error at once.
Sir,
I am trying to implement a form and pass the Data object below
import UIKit
import GRDB
class Staff: Record {
var id: Int64?
var compId: Int64 = 0
var chiName: String = ""
var engName: String = ""
to the table view controller loading the child record. when it comes to implementation, it seems getting null and does not make sense. Would you please tell me how to ensure the second view controller does not receive null objects under this case ?
Below is the
Log :
Here is my code:
First UIView Controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("view salary X ")
print(dummy)
print(dummy.id ?? "0")
if let secondController = segue.destination as? ViewSalaryTableViewController {
secondController.dummyStaff = dummy
}
}
Second UITableView Controller :
public var dummyStaff : Staff?
override func viewDidLoad() {
super.viewDidLoad()
..
print("arrive dummyStaff")
print(dummyStaff ?? "njull")
}
Storyboard partial draft :
Storyboard setting
Make sure the type casting for secondController is working. If you have multiple segues, use segue identifier to distinguish. Below code worked fine for me:
class MyBook {
var name:String!
}
ViewController 1
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Vc1ToVc2" {
let book = MyBook()
book.name = "Harry"
if let destinationVc = segue.destination as? ViewController2 {
destinationVc.book = book
}
}
}
ViewController 2
var book:MyBook?
override func viewDidLoad() {
super.viewDidLoad()
print(book?.name ?? "No name")
}
Prints: Harry
let me explain my code and then state the problem i'm facing
i have two viewControllers classes
1- difficultyViewController : where the user chooses the difficulty of the game
**difficultyViewController has three buttons for the user to click on which difficulty is desired
2- gameViewController : where the game will be presented to the user
**currently in the gameViewController only have a label
in the difficultyViewController i have an enum which represent the three game difficulties
class difficultyViewController: UIViewController {
enum difficulties {
case Easy
case Medium
case Hard
}
var gameDifficulty : difficulties?
// other code is here
}
and in the gameViewController i have a variable correspond to this enum
class gameViewController: UIViewController {
#IBOutlet weak var gameDifficultyLabel: UILabel!
var gameDifficulty : difficultyViewController.difficulties?
// other code is here
}
in the difficultyViewController i'm using code to perform and prepare for the segue
#IBAction func easyButtonPressed(sender: AnyObject) {
gameDifficulty = .Easy
performSegueWithIdentifier("toGame", sender: gameDifficulty as? AnyObject)
}
and here is the prepare for segue code
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toGame" {
if let gameVC = segue.destinationViewController as? gameViewController {
if let difficulty = sender as? difficulties {
print(difficulty)
gameVC.gameDifficulty = difficulty
}
}
}
}
and now the problem i'm facing is
when sending the difficulty as an argument to the perform segue, the conversion from the enum variable to the not valid and i always receive a nil value
what is the reason for that ? is it not possible to convert an enum to anyObject ?
You are setting the game difficulty variable when the user presses the button, so why not just set the difficulty level based on that value?
Also, your class names and enum names should be capitalized to differentiate them from variable names.
class DifficultyViewController: UIViewController {
enum Difficulties {
case Easy
case Medium
case Hard
}
var gameDifficulty : Difficulties?
// other code is here
}
class GameViewController: UIViewController {
#IBOutlet weak var gameDifficultyLabel: UILabel!
var gameDifficulty : DifficultyViewController.Difficulties?
// other code is here
}
#IBAction func easyButtonPressed(sender: AnyObject) {
gameDifficulty = .Easy
performSegueWithIdentifier("toGame", sender: AnyObject)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toGame" {
if let gameVC = segue.destinationViewController as? gameViewController {
gameVC.gameDifficulty = gameDifficulty // You changed this in the IBAction, so simply send it on to the next VC
}
}
}
}