Swift 3 pass data from one ViewController to another ViewController - ios

I am new to IOS programming and don't really know what I'm asking but I will try and explain
I am using Firebase to auth and then I want to take the UID and pass it to a different VC the problem I am getting I cant get the var userID to print out side of the IBAction here is where I am so far, any pointer would get good cheers guys
#IBAction func creatAccountPressed(_ sender: Any) {
if let email = emailTextField.text,
let password = passwordTextField.text,
let name = nameTextField.text {
Auth.auth().createUser(withEmail: email, password: password, completion: { user, error in
if let firebaseError = error {
print(firebaseError.localizedDescription)
}
let userID = user!.uid
let userEmail: String = self.emailTextField.text!
let fullName: String = self.nameTextField.text!
self.ref.child("users").child(userID).setValue(["Email": userEmail, "Name": fullName])
self.userID1 = user!.uid as! String
})
print(self.userID1)
presentLoggedInScreen()
}
}
func presentLoggedInScreen() {
let stroyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loggedInVC: LoggedInVCViewController = stroyboard.instantiateViewController(withIdentifier: "loggedInVC") as! LoggedInVCViewController
self.present(loggedInVC, animated: true, completion: nil)
}

One the simpler way to pass info from one VC to another is either through an initiliazer, or through a variable that you set before presenting the second VC.
Since you are new to this, try the variable approach for now, so if say, you're passing a string:
class LoggedInVCViewController : UIViewController {
var info : String? {
didSet {
if let newInfo = self.info {
//do what ever you need to do here
}
}
}
override viewDidLoad() {
super.viewDidLoad()
}
}
func presentLoggedInScreen(yourInfo: String) {
let stroyboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loggedInVC:LoggedInVCViewController =
storyboard.instantiateViewController(withIdentifier: "loggedInVC") as!
LoggedInVCViewController
loggedInVC.info = yourInfo
self.present(loggedInVC, animated: true, completion: nil)
}
Mind you, you can always use any other king of variable Class for info. You also would program your VC to execute some methods inside of the get bracket of the property, populate some fields based on the content of info, load a specific UI etc.
Another usefull way is to go the initialization route, but unfortunately you cannot use it with Storyboard nibs (sad i know, but this post goes over this nicely), but it still usefull whenever you will feel comfortable enough to initialize and design VC's programmatically (which I would learn ASAP if were you).
You pass a variable in a custom intializer method for your VC:
class LoggedInVCViewController : UIViewController {
var info : Any? {
get {
if let this = self.info {
return this
} else {
return nil
}
} set {
if let new = newValue {
//
}
}
}
init(info: Any?) {
//This first line is key, it also calls viewDidLoad() internally, so don't re-write viewDidLoad() here!!
super.init(nibName: nil, bundle: nil)
if let newInfo = info {
//here we check info for whatever you pass to it
self.info = newInfo
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
So you would use is as:
func presentLoggedInScreen(yourInfo: String) {
let loggedInVC = LoggedInVCViewController(info: yourInfo)
self.present(loggedInVC, animated: true, completion: nil)
}
Obviously, as stated, this method cannot be used with Storyboards, but very usefull and, as I'm sure you can see, is much more compact.
I suggest you get familiar with Swift Properties of the docs, along with some blog tips such as this one. You can get pretty creative after a while.
For learning more programmatic approaches, i strongly recommend this YouTube channel: Let's Build That App, I personnaly haven't found a better reference point for programmatic approach Swift programming.
Don't hesitate to ask questions!
UPDATE
Your IBAction should look like this:
#IBAction func creatAccountPressed(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text, let name = nameTextField.text {
Auth.auth().createUser(withEmail: email, password: password, completion: { user, error in
if let firebaseError = error {
print(firebaseError.localizedDescription)
}
let userID = user!.uid
let userEmail: String = self.emailTextField.text!
let fullName: String = self.nameTextField.text!
self.ref.child("users").child(userID).setValue(["Email": userEmail, "Name": fullName])
self.userID1 = user!.uid as! String
print(self.userID1)
presentLoggedInScreen(yourInfo: self.userID1)
})
}
}

Put
print(self.userID1)
presentLoggedInScreen()
inside the completion block. Currently those lines are almost certain to happen before the completion block is done as the block is executed after the required network calls return.
Be sure to wrap presentLoggedInScreen() in a block that dispatches it to the main thread as it touches the UI and all UI calls must be made from the main thread.
DispatchQueue.main.async {
presentLoggedInScreen()
}
This will make it so that presentLoggedInScreen() executes after the value as been assigned to userID1 allowing you to pass the value over to the incoming view controller (assuming, of course, that the incoming VC has an appropriate variable to pass userID1 into).
Something like:
loggedInVC.userID = self.userID1
before you present the VC.

Related

In Cognito on iOS, handling new password required doesn't ever reach didCompleteNewPasswordStepWithError

I'm trying to implement functionality to respond to FORCE_CHANGE_PASSWORD on my iOS app that uses AWS Cognito. I used this Stack Overflow question which references this sample code. Right now, my code opens a view controller like it's supposed to; however, once on that view controller, I can't get it to do anything. In the sample code, it seems that when you want to submit the password change request you call .set on an instance of AWSTaskCompletionSource<AWSCognitoIdentityNewPasswordRequiredDetails>, yet when I do this, the protocol function didCompleteNewPasswordStepWithError is never called. Interestingly, the other protocol function getNewPasswordDetails is called quickly after viewDidLoad and I can't tell why. I believe this shouldn't be called until the user has entered their new password, etc and should be in response to .set but I could be wrong.
My code is pretty identical to the sample code and that SO post, so I'm not sure what's going wrong here.
My relevant AppDelegate code is here:
extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startNewPasswordRequired() -> AWSCognitoIdentityNewPasswordRequired {
//assume we are presenting from login vc cuz where else would we be presenting that from
DispatchQueue.main.async {
let presentVC = UIApplication.shared.keyWindow?.visibleViewController
TransitionHelperFunctions.presentResetPasswordViewController(viewController: presentVC!)
print(1)
}
var vcToReturn: ResetPasswordViewController?
returnVC { (vc) in
vcToReturn = vc
print(2)
}
print(3)
return vcToReturn!
}
//put this into its own func so we can call it on main thread
func returnVC(completion: #escaping (ResetPasswordViewController) -> () ) {
DispatchQueue.main.sync {
let storyboard = UIStoryboard(name: "ResetPassword", bundle: nil)
let resetVC = storyboard.instantiateViewController(withIdentifier: "ResetPasswordViewController") as? ResetPasswordViewController
completion(resetVC!)
}
}
}
My relevant ResetPasswordViewController code is here:
class ResetPasswordViewController: UIViewController, UITextFieldDelegate {
#IBAction func resetButtonPressed(_ sender: Any) {
var userAttributes: [String:String] = [:]
userAttributes["given_name"] = firstNameField.text!
userAttributes["family_name"] = lastNameField.text!
let details = AWSCognitoIdentityNewPasswordRequiredDetails(proposedPassword: self.passwordTextField.text!, userAttributes: userAttributes)
self.newPasswordCompletion?.set(result: details)
}
}
extension ResetPasswordViewController: AWSCognitoIdentityNewPasswordRequired {
func getNewPasswordDetails(_ newPasswordRequiredInput: AWSCognitoIdentityNewPasswordRequiredInput, newPasswordRequiredCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityNewPasswordRequiredDetails>) {
self.newPasswordCompletion = newPasswordRequiredCompletionSource
}
func didCompleteNewPasswordStepWithError(_ error: Error?) {
DispatchQueue.main.async {
if let error = error as? NSError {
print("error")
print(error)
} else {
// Handle success, in my case simply dismiss the view controller
SCLAlertViewHelperFunctions.displaySuccessAlertView(timeoutValue: 5.0, title: "Success", subTitle: "You can now login with your new passowrd", colorStyle: Constants.UIntColors.emeraldColor, colorTextButton: Constants.UIntColors.whiteColor)
self.dismiss(animated: true, completion: nil)
}
}
}
}
Thank you so much for your help in advance and let me know if you need any more information.

String passed through sender parameter cannot be used by the ViewController

I am passing a String (postID) between two view controllers (respectively DiscoverVC and DetailVC) using the sender parameter of the method performSegue.
When the destination VC is presented, I can actually see that the String I passed is correctly assigned to a local variable “postID” (since I can print it). But when I try to perform some operations on it, such as querying in the Firebase Database, it seems like the String is no longer present. I don’t know how to solve this problem even though it seems stupid to solve.
This is the code of the viewDidLoad() method of the destinationVC
var postID = “”
override func viewDidLoad() {
print("this is the postID that I get from previous VC \(postID)")
super.viewDidLoad()
updateViewDetail()
}
This is the method updateViewDetail() which makes some operations with the Firebase database. It is trying to retrieve the post using the PostID String.
func updateViewDetail(){
Api.Post.observePost(with: postID) { (post) in
print("post id inside \(post.id)")
guard let postUid = post.uid else{
return
}
self.fetchUsers(uid: postUid) {
self.post = post
self.tableView.reloadData()
}
}
}
When I try to print the post.id inside the closure it is nil. Do you know how I can solve this problem?
Just for the sake of completion, this is the code of the observePost method that retrieves the post object from the FIRDatabase:
func observePost(with id : String, completion: #escaping (Post)-> Void) {
REF_Posts.child(id).observeSingleEvent(of: .value) { (snaphsot) in
if let dict = snaphsot.value as? [String : Any]{
let post = Post.transformPost(dict: dict, key: snaphsot.key)
completion(post)
}
}
}
EDIT 1:
This is the code of prepare method set in the previous VC :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let detailVC = segue.destination as! DetailViewController
let postid = sender as! String
detailVC.postID = postid
}

Value of type 'UIViewController' has no member 'newExerciseDelegate'

I've searched for a solution to this problem, even tried following a few tutorials to try to solve this, but for some reason I'm ending up with the same issue. I'm attempting to pass a custom object to a different view controller, but every time I try I get the error "Value of type 'UIViewController' has no member 'newExerciseDelegate'"
My delegate:
protocol exerciseDelegate {
func savedExercise(newExercise: Exercise)
}
my sending VC uses the following code:
var newExerciseDelegate: exerciseDelegate!
.
.
.
#IBAction func saveExerciseWasPressed(_ sender: UIButton) {
if (checkFields() == true){
newExercise = Exercise(name: exerciseNameField.text!, weight: Int32(weightField.text!)!, reps: Int32(numberOfRepsField.text!)!, sets: Int32(numberOfSetsField.text!)!, muscleGroupFocus: .cardio)
newExerciseDelegate.savedExercise(newExercise: newExercise)
dismiss(animated: false, completion: nil)
}
}
My receiving VC uses the following code:
#IBAction func addExerciseBtnWasPressed(_ sender: Any) {
guard let newExerciseVC = storyboard?.instantiateViewController(withIdentifier: "NewExerciseVC") else { return }
newExerciseVC.newExerciseDelegate = self // error present on this line
presentDetail(newExerciseVC)
}
I'm sure it's a stupid mistake, but I'm not seeing it. Any help is appreciated, thank you.
You should specify which class it is.After the code know which class actually it is, then you can access it's public objects, methods, variables etc.
#IBAction func addExerciseBtnWasPressed(_ sender: Any) {
guard let newExerciseVC = storyboard?.instantiateViewController(withIdentifier: "NewExerciseVC") as? NewExerciseViewController else { return }
newExerciseVC.newExerciseDelegate = self
presentDetail(newExerciseVC)
}
If you are accessing that delegate which is declared in your ViewController then you should call in the below way.
let childOne = self.storyboard?.instantiateViewController(withIdentifier:"WhatsNewViewController") as? WhatsNewViewController
You have to downcast the instantiated view controller to your custom view controller class:
guard let newExerciseVC = storyboard?.instantiateViewController(withIdentifier: "NewExerciseVC") as? YourViewControllerClass else { return }
newExerciseVC.newExerciseDelegate = self
Also you should use a capital E for your protocol's name.

Cannot take values from other view controller Swift

I want to take user settings details from this view controller and read these details to the previous view controller. I have tried many different ways, but I cannot take values until I visit this view controller
I have tried first method from this page Pass Data Tutorial
This method is also not working. I think it is very simple, but I cannot figure out the right way to do it.
class SetConvViewController: UIViewController {
var engS = "engS"
#IBOutlet weak var swithEnglish: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let eng2 = defaults.value(forKey: engS)
{
swithEnglish.isOn = eng2 as! Bool
}
}
let defaults = UserDefaults.standard
#IBAction func switchEng(_ sender: UISwitch) {
defaults.set(sender.isOn, forKey: engS)
}
}
If I understand you correctly from this part - „but I cannot take values until I visit this view controller” - your problem lies with the fact, that until you visit your settings, there is no value for them in UserDefaults.
If you are reading them using getObject(forKey:) method, I’d recommend you to switch to using getBool(forKey:), since it will return false even if the value has not been set yet for that key ( docs )
Anyhow, if you want to set some default/initial values you can do so in your didFinishLaunching method in AppDelegate :
if UserDefaults.standard.object(forKey: „engS”) == nil {
// the value has not been set yet, assign a default value
}
I’ve also noticed in your code that you used value(forKey:) - you should not do that on UserDefaults - this is an excellent answer as to why - What is the difference between object(forKey:) and value(forKey:) in UserDefaults?.
On a side note, if you are using a class from iOS SDK for the first time, I highly recommend looking through its docs - they are well written and will provide you with general understanding as to what is possible.
I would recommend you to store this kind of data as a static field in some object to be able to read it from any place. e.g.
class AppController{
static var userDefaults = UserDefaults.standard
}
and then you can save it in your SetConvViewController like
#IBAction func switchEng(_ sender: UISwitch) {
AppController.userDefaults.set(sender.isOn, forKey: engS)
}
and after that you can just read it from any other view controller just by calling
AppController.userDefaults
Using segues you can set to any destination whether it be next vc or previous:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PreviousVC" {
if let prevVC = segue.destination as? PreviousViewController {
//Your previous vc should have your storage variable.
prevVC.value = self.value
}
}
If you're presenting the view controller:
Destination vc:
//If using storyboard...
let destVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DestinationViewController") as! DestinationViewController
destVC.value = self.value
self.present(destVC, animated: true, completion: nil)
Previous vc:
weak var prevVC = self.presentingViewController as? PreviousViewController
if let prevVC = prevVC {
prevVC.value = self.value
}

Refresh UITableViewController data

I have 2 controllers, DashboardController and LocateVehicleController. LocateVehicleController has UITableViewController.
In DashboardController, On button press I am doing API call and getting data. And sending array to LocateVehicleController.
let locateVehicleStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let locateVehicleController = locateVehicleStoryboard.instantiateViewController(withIdentifier: "tempID") as? LocateVehicle
self.present(locateVehicleController!, animated: true, completion: nil)
locateVehicleController?.dataArray = self.locateVehicleDataArr
locateVehicleController?.tableView.reloadData()
In LocateVehicleController I have refresh button, If I press refresh button I need to update the tableview controller data which I have used API call data from DashboardController.
As per my understanding when I press refresh button, same API call will invoke. Please help to solve this. Thanks in advance.
I would move the logic of calling of this API to another class. Inject this class into DashboardController and LocateVehicleController and use it to fetch you data. Something like this:
struct Vehicle {
}
protocol VehicleDataFetcherProtocol {
func getVehicleData(completionHandler:([Vehicle])->()) // assuming the data is in form of an Array of a class/struct Vehicle
}
class VehicleDataFetcher: VehicleDataFetcherProtocol {
// Hit API, get data and call the completion hanlder
func getVehicleData(completionHandler:([Vehicle])->()) {
}
}
class DashboardController:UIViewController {
private var vehicleDataFetcher: VehicleDataFetcher!
func injectVehicleDataFetcher(dataFetcher:VehicleDataFetcher) {
self.vehicleDataFetcher = dataFetcher
}
func presentLocateVehicle() {
let locateVehicleStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let locateVehicleController = locateVehicleStoryboard.instantiateViewController(withIdentifier: "tempID") as? LocateVehicleController
self.present(locateVehicleController!, animated: true, completion: nil)
locateVehicleController?.injectVehicleDataFetcher(dataFetcher: self.vehicleDataFetcher)
locateVehicleController?.dataArray = self.locateVehicleDataArr
locateVehicleController?.tableView.reloadData()
}
}
class LocateVehicleController:UIViewController {
private var vehicleDataFetcher: VehicleDataFetcher!
func injectVehicleDataFetcher(dataFetcher:VehicleDataFetcher) {
self.vehicleDataFetcher = dataFetcher
}
}

Resources