Swift - Passing data coming from an API to another view controller - ios

Please check the below code:
#IBAction func sendActivationCode(_ sender: UIButton) {
service.Register(phoneNumber: self.mobileNumberTxt.text!, callback: { (response) in
self.setCustomerValues(response: response)
})
}
func setCustomerValues(response: [String:Any]) {
registrationToken = (response["token"]! as! String)
code = response["code"] as! Int
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toStep2" {
let vc = segue.destination as! Step2ViewController
vc.registrationToken = registrationToken
}
}
The problem is: prepare function is executed before setCustomerValues and I cannot use registrationToken variable in Step2ViewController.swift because it's nil.

Instead of connecting your segue from the button to Step2ViewController, connect it from the view controller. This way the segue will not automatically be performed when the button is touched.
Then call performSegue from within your setCustomerValues callback to perform the segue explicitly after getting the registration token. Note that if the callback is not on the main thread, you will need to dispatch_async to the main thread before calling performSegue.

You should push viewcontroller after self.setCustomerValues(response: response). Don't push viewcontroller when sendActivationCode

The best way to come out of this problem is to create an IBAction method from your button on a Touch Up Inside Event and not create any Segues on 'action' of your button.
Use the following code:
#IBAction func sendActivationCode(_ sender: UIButton) {
service.Register(phoneNumber: self.mobileNumberTxt.text!, callback: {
(response) in
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("Step2ViewController") as! Step2ViewController
vc.registrationToken = (response["token"]! as! String)
vc.code = response["code"] as! Int
self.navigationController?.pushViewController(vc!, animated: true)
})
}

Related

Reloading TableView when a UIViewController is being dismissed?

The problem here is that I'm presenting EditCommentVC modally, over the current context of the CommentVC because I want to set the background of the UIView to semi-transparent. Now, on the EditCommentVC I have a UITextView that allows the user to edit their comment, along with 2 buttons - cancel (dismisses the EditCommentVC) and update that updates the new comment and push it to the database.
In term of code, everything is working, except that once the new comment is being pushed and EditCommentVC is being dismissed, the UITableView on CommentsVC with all the comments is not being reloaded to show the updated comments. Tried calling it from viewWillAppear() but it doesn't work.
How can I reload the data in the UITableView in this case?
#IBAction func updateTapped(_ sender: UIButton) {
guard let id = commentId else { return }
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
let commentVC = CommentVC()
commentVC.tableView.reloadData()
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})
}
The code in the CommentVC where it transitions (and passes the id of the comment). CommentVC conforms to a CommentActionProtocol that passes the id of that comment:
extension CommentVC: CommentActionProtocol {
func presentActionSheet(for commentId: String) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let editAction = UIAlertAction(title: "Edit", style: .default) { _ in
self.performSegue(withIdentifier: "CommentVCToEditComment", sender: commentId)
}
actionSheet.addAction(editAction)
present(actionSheet, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
}
}
}
I see atleast 2 problems here:
You are creating a new CommentVC which you should not do if you want to update the tableView in the existing view controller.
Since you have mentioned that Api.Comment.updateComment is a an asynchronous call, you need to write the UI code to run on the main thread.
So first you need to have the instance of the commentVC in a variable inside this viewController. You can store the instance of the view controller from where you are presenting this view controller.
class EditCommentVC {
var commentVCdelegate: CommentVC!
// Rest of your code
}
Now you need to pass the reference commentVC in this variable when you are presenting the edit view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
editCommentVC.commentVCdelegate = self
}
}
Now you need to use this reference to reload your tableView.
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
commentVCdelegate.tableView.reloadData() // - this commentVC must be an instance that you store of the your commentVC that you created the first time
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})
Well, i had this problem too, and the solution i found was to use Protocol. I would recommend you to search how to send data back to previous ViewController. That way, when you dismiss the EditCommentVC, you then send back a value(in my case i send true) to the previous ViewController(in your case, CommentVC), and then you'll have a function in CommentVC checking if the value is true and if it is, reload the TableView.
Here, let me show you an example of how i used (those are the names of my ViewControllers, functions and protocols, you can use whatever you want and send whatever data you want back):
In your CommentVC, you'll have something like this:
protocol esconderBlurProtocol {
func isEsconder(value: Bool)
}
class PalestranteVC: UIViewController,esconderBlurProtocol {
func isEsconder(value: Bool) {
if(value){
//here is where you can call your api again if you want and reload the data
tableView.reloadData()
}
}
}
Also, dont forget that you have to set the delegate of EditCommentVC, so do it when you're presenting EditCommentVC, like this:
let viewController = (self.storyboard?.instantiateViewController(withIdentifier: "DetalhePalestranteVC")) as! DetalhePalestranteVC
viewController.modalPresentationStyle = .overFullScreen
viewController.delegate = self
self.present(viewController, animated: true, completion: nil)
//replace **DetalhePalestranteVC** with your **EditCommentVC**
And in your EditCommentVC you'll have something like this:
class DetalhePalestranteVC: UIViewController {
var delegate: esconderBlurProtocol?
override func viewWillDisappear(_ animated: Bool) {
delegate?.isEsconder(value: true)
}
}
That way, everything you dismiss EditCommentVC, you'll send back True and reload the tableView.

Sending data to another view controller

This is a learning app that I am making for fun, I have been stuck here for 2 days.
I have two views setup that I use to send data that the user will pick to the other one (they are named AddCoinVC and MainVC).
In AddCoinVC, the sending is performed when the user clicks on the button
let vc = MainViewController()
vc.coinArray.append(CoinWallet(coinName: "Test", coinSymbol: "Test", coinAmount: "0"))
performSegue(withIdentifier: "backToMain", sender: self)
I have setup a breakpoint at this point and printing vc.coinArray prints me the correct value =
($R0 = 1 value { (coinName = "Test", coinSymbol = "Test", coinAmount = "0")
}
But when I go to my other breakpoint at MainVC, it displays 0 value.
var coinArray = [CoinWallet]()
This is the var that I use, the default in MainVC is CoinWallet which is empty when first loading the app. This is the custom Class.
class CoinWallet {
var coinName:String = ""
var coinSymbol:String = ""
var coinAmount:String = ""
init(coinName: String, coinSymbol:String, coinAmount: String) {
self.coinName = coinName
self.coinSymbol = coinSymbol
self.coinAmount = coinAmount
}
}
When the segue and the sending is performed from AddCoinVC to MainVC 'coinArray' should have this value sent to it.
Why would be the value empty if vc.coinArray has 1 value?
The error occurs because MainViewController() does not return the view controller you expect. It's a new blank instance which is not the instance in the storyboard.
Since you are performing a segue anyway, pass the CoinWallet instance as sender parameter in performSegue
let coin = CoinWallet(coinName: "Test", coinSymbol: "Test", coinAmount: "0")
performSegue(withIdentifier: "backToMain", sender: coin)
Then implement prepare(for segue and use the destination property as reference to the main view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "backToMain" {
let coin = sender as! CoinWallet
let mainViewController = segue.destination as! MainViewController
mainViewController.coinArray.append(coin)
}
}
You should add this method in your AddCoinVC and send data as below,
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let mainVC = segue.destination as? MainViewController {
mainVC.coinArray.append(CoinWallet(coinName: "Test", coinSymbol: "Test", coinAmount: "0"))
}
}
In the below lines of code, you are just creating a new instance that is not the viewController being segued.
let vc = MainViewController()
vc.coinArray.append(CoinWallet(coinName: "Test", coinSymbol: "Test", coinAmount: "0"))
You should only perform the segue on button click as below and set any data inside the above method.
performSegue(withIdentifier: "backToMain", sender: self)

String not being passed to next view controller from prepareForSegue

I have a push segue on my StoryBoard which is named toGuestVC.
I use that to segue to the next ViewController in my didSelectRowAtIndexPath method like so:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let username = followUsernameArray[indexPath.row]
performSegue(withIdentifier: SG_TO_GUEST_VIEW_CONTROLLER, sender: username)
}
Then in my prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == SG_TO_GUEST_VIEW_CONTROLLER {
if let nextVC = segue.destination as? GuestCollectionVC, let sender = sender as? String {
print("PRINTING NEXT VC: \(nextVC)") //This prints out the memory address. Not sure if this is what you meant by print nextVC.
nextVC.guestUser = sender
}
}
}
For some reason this line in my prepareForSegue is not running:
nextVC.guestUser = sender.username
When I try to print out the value guestUser in my nextViewController the value of guestUser is nil. But when I print out the value of sender in my prepareForSegue method it is not nil.
So is my sender value not being passed to the next ViewController? I can't find a solution to this problem any ideas?
GuestCollectionVC Implementation:
import UIKit
import Parse
private let reuseIdentifier = "Cell"
class GuestCollectionVC: UICollectionViewController {
var guestUser: String!
override func viewDidLoad() {
super.viewDidLoad()
print("PRINTING SELF IN GuestCollectionVC: \(self)")
loadPosts()
}
func loadPosts() {
//Load posts query
let query = PFQuery(className: PF_POSTS_CLASS)
query.limit = postCount
//Getting error here below this comment when I use guestUser since it is nil
query.whereKey(PF_POSTS_USERNAME_COLUMN, equalTo: guestUser)
query.findObjectsInBackground { (result: [PFObject]?, error: Error?) -> Void in
if error == nil {
if let result = result {
self.uuidOfPosts.removeAll(keepingCapacity: false)
self.imageArrayOfPFFIle.removeAll(keepingCapacity: false)
for postObject in result {
if let uuid = postObject[PF_POSTS_UUID_COLUMN] as? String, let pic = postObject[PF_POSTS_PIC_COLUMN] as? PFFile {
self.uuidOfPosts.append(uuid)
self.imageArrayOfPFFIle.append(pic)
}
}
self.collectionView?.reloadData()
}
}else if error != nil {
print("ERROR FROM GUEST COLLECTION VC FROM loadPosts FUNCTION: \(error?.localizedDescription)")
}
}
}
}
So this is my implementation in the GuestViewController. In my loadPosts method where I used the variable guestUser I am getting the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
From console printing
PRINTING NEXT VC: "InstagramClone.GuestCollectionVC: 0x7a6c5cc0"
PRINTING SELF IN GuestCollectionVC: "InstagramClone.GuestCollectionVC: 0x7a6c5100"
it's now obvious that hidden unexpected instance of GuestCollectionVC was created. So, there are different errors occurs depending on order of this two objects invoke their viewDidLoad method (can be any order). Also there are can be other errors in nextVC viewDidLoad method, but this is other story for other question.
You got this problems because you created action segue, that works automatically on cell click (hidden view controller created), and at the same time you are perform this segue in code, creating second controller nextVC.
To solve issue, you should find and remove that segue and add new one, not action segue from some element of your view controller, but "empty" segue between view controllers. To create segue of this type you should select first view controller, hold "control" key and start dragging to next view controller from yellow square on top of first view controller (that symbol controller itself), choose "show", and set identifier.
Since I don't have the full context of the values within your tableView method I can only speculate. That said, the sender you're passing in should be the view controller:
performSegue(withIdentifier: "toGuestVC", sender: username)
You're passing in a value called username which looks to be a string value? It should be something like:
performSegue(withIdentifier: "toGuestVC", sender: self)
where self is your view controller. If you're passing in a string value to sender then in your prepareForSegue method then sender does not have a property called username sender actually is username. Therefore you should pass the value elsewhere:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let indexPath = self.tableView.indexPathForSelectedRow
if let nextVC = segue.destination as? GuestCollectionVC {
nextVC.guestUser = followUsernameArray[indexPath.row]
}
}

How to Transfer Data when dismissing controller view in Swift

I am working on an iOS app that logs the user in by a Login View.
There are two controllers: LognViewController and SignUpViewController
If a new user signs up instead, then the Sign Up View Controller makes an API call to retrieve a new user account. Then, the Sign Up page should transfer the new User object back to the Login page, which in turn logs the user in to the main app.
Based on a previous post I like the idea of a closure, and I'm trying to implement it here; however, I'm getting a nil on the closure function variable. My code is something like this:
In the First Controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "userSignUp" {
if !self.userTextField.text!.isEmpty {
let nav = segue.destinationViewController as! UINavigationController
let vc = nav.topViewController as! SignUpViewController
vc.email = self.userTextField.text!
vc.submitUser = signUpToLogIn
}
}
}
// asynchronous get user back from sign up
func signUpToLogIn(currentUser: User) {
self.currentUser = currentUser
self.checkCurrentUser()
}
In the Second Controller:
var submitUser: ((currentUser: User) -> ())!
#IBAction func signUpButtonTapped(sender: UIButton) {
doSignUp({(u: User?) -> () in
if u != nil {
self.submitUser(currentUser: u!)
self.dismissViewControllerAnimated(false, completion: nil)
}
})
}
I'm looking at the debugger, and it says fatal error: unexpectedly found nil while unwrapping an Optional value When I work with a breakpoint, I can see in the variables section that the submitUser variable is always nil.
Is there a different way of doing this now?
Instead of passing a closure, can you use delegation instead? Your SignUpViewController can notify your LoginViewController when a new user has signed up and pass the new User object back through a delegate method like so
Signup View Controller:
protocol SignUpDelegate {
func userDidSignUp(u: User)
}
class SignUpViewController: UIViewController {
var delegate: SignUpDelegate?
#IBAction func signUpButtonTapped(sender: UIButton) {
// Make async call to sign new user up here
// Once you get a User back from your API call your delegate
// method in your completion or at the end of your network call
self.delegate?.userDidSignUp(newUserObject)
self.dismissViewControllerAnimated(false, completion: nil)
}
}
Then in your login view controller you can implement this delegate method
Login view controller:
class LoginViewController: UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "userSignUp" {
if !self.userTextField.text!.isEmpty {
let nav = segue.destinationViewController as! UINavigationController
let vc = nav.topViewController as! SignUpViewController
vc.email = self.userTextField.text!
vc.delegate = self
}
}
}
// MARK: SignUpDelegate
func userDidSignUp(u: User) {
// Log user in with new user
}
}
Inside the #IBAction func signUpButtonTapped(sender: UIButton):
Try using this function to do the transition to another view controller:
[self performSegueWithIdentifier:#"segueIdentifierHere" sender:self];
Don't forget to remove the self.dismissViewController function you are currently using before you try the performSegueWithIdentifier

How to send Parse object field from one class to another?

I want to make such thing:
On one ViewControleer I'm making a query to Parse.com, where I'm sending objects fields to Label.Text. By clicking one button objects randomly changes, by clicking another one- next ViewController is opening. Just imagine Tinder - on the first VC I swiping girls, on the new one chat is opening, with the girl's name in the head of the NavigatorItem
So I want to send Object Field "Name" that I'm using in that view to another without other query.
I don't know, whether I can do it via segue, or protocol. Can U somehow help me with implementation?
here is the code of my random function
func retriveJobData() {
var query: PFQuery = PFQuery(className: "Jobs")
query.getObjectInBackgroundWithId("AUeuvj0zk2") {
(newJobObject: PFObject?, error: NSError?) -> Void in
if error == nil && newJobObject != nil {
println(newJobObject)
if let newJobObject = newJobObject {
self.PrcieTextField.text = newJobObject["jobPrice"] as? String
self.DateTextField.text = newJobObject["jobDate"] as? String
self.DescriptionTextField.text = newJobObject["jobDescription"] as? String
}
} else {
println(error)
}
}
}
I want to send newJobObject["jobName"] to NavigatorItemName of another ViewController
you can override prepareForSegue for this purpose:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "yourSegueIdentifier") {
// pass data to next view
}
}
Assuming you have some method that triggers a push to the new viewController and that you're using the storyboard, call performSegue using the identifier you set up in the storyboard
#IBAction func buttonPressed(sender: UIButton!) {
performSegueWithIdentifier("identifier", sender: nil)
}
Then override prepareForSegue and pass in the string
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "identifier" {
let controller = segue.destinationViewController as! ViewController
controller.jobName = someWayThatYouRetrieveNewJobObjectName
}
Then in ViewController ensure you have a property for jobName
var jobName:String! //declare this as an optional instead if needed
And set the navigation title
navigationItem.title = jobName

Resources