Protocols and delegation in swift 3 not sending values between viewcontrollers - ios

I have two viewcontrollers(SignInViewcontroller.swift and ProfilePage.swift)
I want to pass the string from SignInViewcontroller to ProfilePage viewcontroller.
I created a protocol in SignInViewcontroller.And I delegate the method in ProfilePage controller.When I send the string through protocols I didn't receive that string in ProfilePage viewcontroller Where I am wrong.please help me to solve.
Here is my code:
SignInViewController.swift
protocol sendTokenDelegate: class {
func sendToken(login:String)
}
class SignInViewController: UIViewController {
weak var delegateToken:sendTokenDelegate?
func loginAzure(email: String, password: String) {
token = "abcdefgh"
self.delegateToken?.sendToken(login: token)
}
}
ProfilePage.swift
class ProfilePage: UIViewController, UITableViewDelegate, UITableViewDataSource, sendTokenDelegate {
override func viewDidLoad() {
let signInVC = SignInViewController()
signInVC.delegateToken = self
}
func sendToken(login: String) {
self.logInToken = login
print("Login Token in Profile Page is \(login)")
}
}

In that case, you need object that will store token from SignInViewController, until ProfilePage is requesting it.
class TokenStorage {
static let shared = TokenStorage()
public var token: String = ""
}
then you receive token call:
TokenStorage.shared.token = receivedToken
and in ProfilePage request it:
print(TokenStorage.shared.token)

If you are coming from SignUpViewController to ProfilePageViewController, you can pass the string values upon navigation after getting the singIn token you want from your logingAzure() I assume:
If you navigate using segues -> self.performSegue(withIdentifier: "signUpToProfile", sender: self)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "signUpToProfile" {
if let profileVC : ProfilePageViewController =
segue.destination as? ProfilePageViewController {
profileVC.loginToken = token
}
}
}
If you are using self.navigationController?.pushViewController
let storyboard = UIStoryboard(name: "Profile", bundle: Bundle.main)
if let profileVC = storyboard.instantiateViewController(withIdentifier:
"ProfilePageViewController") as? ProfilePageViewController {
profileVC.loginToken = token
}
EDIT
If you are not going to profilePage directly from SignUpViewController,
then just save the token in your Keychain OR UserDefaults.
Do this by creating a SessionManager singleton to handle tokens and other resources when logging in or signing up

Just use "prepare for segue"
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ProfileSegue" {
if let vc = segue.destination as? ProfilePage {
vc.token = self.token
}
}
}

When you are going to NextVC that doesn't exist in the navigation controller then you have to bind data with the instance of the class when you are pushing just like that:
let vc = UIStoryboard(name: "ProfilePage", bundle: nil).instantiateInitialViewController() as! ProfilePage
vc. logInToken = "abcdefgh"
self.navigationController?.pushViewController(vc, animated: true)
class ProfilePage: UIViewController {
var logInToken = "" // you will receive the token in this variable.
}
In you case, it seems like ProfilePage doesn't exist.
NOTE:- Delegate will use just opposite case when you want to pass the value from ProfilePage to SignInViewController.
OR
If your all API wants the token so you can declare at the class level or save it to UserDefauls:
1)
var logInToken = "" // your variable visible the entire application classes
class ProfilePage: UIViewController {
}
func loginAzure(email: String, password: String) {
logInToken = "abcdefgh" //Just assign and use it
}
2)
UserDefaults.standard.set("aasdfa", forKey: "token")
let token = UserDefaults.standard.value(forKey: "token"
Also, you are doing the bad coding you have to understand the OOP's
let signInVC = SignInViewController()
signInVC.delegateToken = self
This will reperensent the seprate instance in the memory and every
object has its own properties and behavior.

Try using:
protocol sendTokenDelegate: class {
func sendToken(login:String)
}
class SignInViewController: UIViewController {
weak var delegateToken:sendTokenDelegate?
func loginAzure(email: String, password: String) {
token = "abcdefgh"
if self.delegateToken != nil{
self.delegateToken?.sendToken(login: token)
}
}
}
class ProfilePage: UIViewController, UITableViewDelegate, UITableViewDataSource, sendTokenDelegate {
override func viewDidLoad() {
//get your instantiateViewController from storyboard
let signInVC = self.storyboard?.instantiateViewController(withIdentifier: "SignInViewControllerIdentifire") as! SignInViewController
signInVC.delegateToken = self
}
func sendToken(login: String) {
self.logInToken = login
print("Login Token in Profile Page is \(login)")
}
}

Related

Can not pass Data To ContainerView

I have tried 2 methods to pass the data from ViewController to ContainerView, with and without segue
Here is without segue method
ViewController
class DetailPostBookReviewVC: UIViewController {
var postid: String!
#IBAction func Menubutton(_ sender: Any) {
print(postid!)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "MenuBookReviewVC") as! MenuBookReviewVC
vc.favpostid = postid
}
ContainerView
class MenuBookReviewVC: UIViewController {
var favpostid = String()
#IBAction func Deletepost(_ sender: Any) {
print(favpostid)
}
}
result: favposid has Nill Value
UPDATE this is with segue method
class DetailPostBookReviewVC: UIViewController {
var postid: String!
#IBAction func Menubutton(_ sender: Any) {
print(postid!)
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "toMenuBookReviewVC") { //"toMenuBookReviewVC" is identifier
let vc = segue.destination as! MenuBookReviewVC
vc.favpostid = postid!
}
}
Pass your data like. User prepare(for:sender:)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if
segue.identifier == "MyIdentifierInStorybiard", // Set that
let controller = segue.destination as? MenuBookReviewVC {
controller.favpostid = postid
}
}
I think you postid is not String type so print the null value
In this way, you can't pass data for the container view. if in this way without presenting controller and push controller you can use the global variable then direct pass data and use any controller you want to use.
Example
import UIKit
class ViewController: UIViewController {
var postid: String!
override func viewDidLoad() {
super.viewDidLoad()
postid = "23" // You change your post ID
}
#IBAction func Menubutton(_ sender: Any) {
favpostid = postid
}
}
var favpostid : String!
class MenuBookReviewVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(favpostid)
}
}
For Passing Data To Container View you can use this
UserDefaults.standard.set(value, forKey: "SomeKey")
after your data is used you can clear that default value.
UserDefaults.standard.set("", forKey: "SomeKey")

How to set a delegate in Swift

I want to send my UserModel with all user informations from a ViewController (ShowUserViewController) to another ViewController (ChatViewController) with a delegate but its not working.
In my ShowUserViewControllers user are all informations I want to send to the ChatViewController.
var user: UserModel?
In my ChatViewController I have the following declaration where I want to send my datas:
var currentUser: UserModel?
Here my protocol:
protocol UserInfoToChatID {
func observeUserID(user: UserModel)
}
Here I prepare the segue and set delegate by tapping the button:
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
var delegate: UserInfoToChatID?
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
delegate?.observeUserID(user: user!)
}
At last I call the delegate in my ChatViewController:
extension ChatViewController: UserInfoToChatID {
func observeUserID(user: UserModel) {
self.currentUser = user
performSegue(withIdentifier: "UserInfoToChatVC", sender: self)
}
}
If you need to pass data from one ViewController to another, you don't have to use delegates for this. You can just pass this data as sender parameter of performSegue method:
performSegue(withIdentifier: "UserInfoToChatVC", sender: user!)
then in prepare for segue just downcast sender as UserModel and assign destination's currentUser variable
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = sender as! UserModel
}
}
But in your case you actually don't have to pass user as sender. You can just assign destination's currentUser variable as ShowUserViewController's global variable user
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = user!
}
}
2 things:
first, if you just want to pass data from one viewController to other viewController you don't need to use delegate pattern, just pass the object to the next viewController on prepare form segue.
second, if you want to implement the delegate pattern you should have one viewController than call to the delegate and the other implement the functions.
example:
protocol ExampleDelegate: class {
func delegateFunction()
}
class A {
//have delegate var
weak var delegate: ExampleDelegate?
// someWhere in the code when needed call to the delegate function...
delegate?.delegateFunction()
}
Class B: ExampleDelegate {
func delegateFunction() {
// do some code....
}
//when you move to the next viewControoler(to A in that case)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AClass" {
if let vc = segue.destination as? A {
vc.delegate = self
}
}
}
To pass the UserModel object forward, from ShowUserViewController to ChatViewController, you should use something called Dependency Injection:
So you'll do something like this inside ShowUserViewController:
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "UserInfoToChatVC", sender: nil)
}
Note: The sender parameter should be the object that initiated the segue. It could be self, i.e. the ShowUserViewController object, but I'd advise against passing the UserModel object, because that object did not initiate the segue, and has nothing to do with navigation at all. It should be injected inside the Destination Controller later on.
In the same file, override the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
I believe you've mostly done this part right, but you may need to communicate back from ChatViewController to ShowUserViewController.
In that case, you can and should use Delegation.
Create something like this inside ShowUserViewController:
protocol ChatViewControllerDelegate: class {
func didUpdateUser(_ model: UserModel)
}
class ChatViewController: UIViewControler {
var user: UserModel?
weak var delegate: ChatViewControllerDelegate?
/* more code */
func someEventHappened() {
delegate?.didUpdateUser(self.user!)
}
}
Finally, there is an additional line to be added to the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
// Add this line...
chatVC.delegate = self
}
}
And specify that the ShowUserViewController implements the ChatViewControllerDelegate protocol, then override the didUpdateUser(_:) method:
func didUpdateUser(_ model: UserModel) {
// Some code here
}

Swift 3 - Passing data between a View Controller and after that to another 2

I'm trying to perform a segue which it doesn't work.
What i'm trying to do is send the data which i have in a textfield in my View Controller(Main), after that i want to send it to a ViewController called OperationsController and after that send it to another Views (CreateController & ListController) so i can use that same data and send it to a php file and get data to populate a table view in ListController. And for CreateController to get the email (which is in short words the data) and perform a query based on the email and insert into the database.
Anyways i tried sending the data to Operations into a label and doesn't work.
This is my code
ViewController: .
import UIKit
class ViewController: UIViewController {
var datas:[Usuario]?
struct Usuario : Codable {
let correo: String?
let contrasena: String?
}
#IBOutlet weak var txtError: UILabel!
#IBOutlet weak var txtCorreo: UITextField!
#IBOutlet weak var txtContra: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnLogear(_ sender: Any) {
let urlString = "http://localhost:8080/swiftdb/logear.php"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
//Implement JSON decoding and parsing
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let articlesData = try JSONDecoder().decode([Usuario].self, from: data)
//Get back to the main queue
DispatchQueue.main.async {
self.datas = articlesData
let aarti = self.datas
for item in aarti! {
let correos = item.correo
let contras = item.contrasena
if(item.correo == self.txtCorreo.text && item.contrasena == self.txtContra.text){
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "OP") as! OpcionesController
self.present(nextViewController, animated:true, completion:nil)
self.performSegue(withIdentifier: "segue", sender: self)
self.txtError.text = " "
} else {
self.txtError.text = "Datos Incorrectos"
}
}
}
} catch let jsonError {
print(jsonError)
}
}.resume()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? OpcionesController{
destination.name = txtCorreo.text
}
}
}
OperationsController: .
import UIKit
class OpcionesController: UIViewController {
var name: String?
#IBOutlet weak var displayLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let nametoDisplay = name {
displayLbl.text = name
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Before calling presentViewController add :
nextViewController.name = yourTextField.text
You could also delete the segue call. That is redundant.
Here is an example that I've used in the past :
#IBAction func doSegue(_ sender: UIButton) {
buttonTag = sender.tag
let storyboard = UIStoryboard (name: "Main", bundle: nil)
let resultVC = storyboard.instantiateViewController(withIdentifier: "ResultViewController")as! ResultViewController
// Communicate with new VC - These values are stored in the destination
// you can set any value stored in the destination VC here
resultVC.firstValue = buttonTag
resultVC.secondValue = randomOpponentValue()
self.navigationController?.pushViewController(resultVC, animated: true)
}
1.So get rid of this code, because if you are calling performSegue you don’t need that one.
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "OP") as! OpcionesController
self.present(nextViewController, animated:true, completion:nil)
2.Then in the prepareForSegue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == “YourSegueIdentifier" {
let destination: OpcionesController = segue.destination as! OpcionesController
destination.name = txtCorreo.text
}
}
3.Replace this code:
if let nametoDisplay = name {
displayLbl.text = name
}
with:
displayLbl.text = name

How to transfer data between view controller using segues

I'm trying to pass data using a prepare(for segue:) function but it's showing nil in the second VC. Am I doing anything wrong?
class ViewController: UIViewController {
var first : [String] = []
#IBOutlet weak var passField: UITextField!
#IBOutlet weak var userID: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func login(_ sender: Any) {
let user : String = self.userID.text!
let password : String = self.passField.text!
if user != "" && password != "" {
let postString = ["username":user, “password”: password]
var request = URLRequest(url:URL(string:"http://mydomainhere.com/api/login")!)
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: postString, options:.prettyPrinted)
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
print("error=\(error)")
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] {
let firstName = json["first_name"] as? String
let lastName = json["last_name"] as? String
self.first.append(firstName!) //putting into Array
self.performSegue(withIdentifier: "loginSegue", sender: self)
}
} catch {
print(error)
}
}
}
}
// data transfer to another controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "loginSegue" {
let secondController = segue.destination as? SecondVC
secondController?.name = first //passing to next VC //* here having the issue its not passing the data to next VC
print(first) // here first is printing perfectly
}
}
}
// second View Controller
class SecondVC: UIViewController {
var menu_vc : MenuViewController!
var name : [String]? // passing to this Array
override func viewDidLoad() {
super.viewDidLoad()
print(name) // here printing nil
}
}
As suggested by #Sweeper, it could very well be that your destination view controller is embedded in a UINavigationViewController, hence your segue.destination is in fact a UINavigationViewController, not a SecondVC.
You can try this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var destinationViewController = segue.destination
if let navigationController = destinationViewController as? UINavigationController {
destinationViewController = navigationController.visibleViewController ?? destinationViewController
}
if let secondController = destinationViewController as? SecondVC {
secondController?.name = first
}
}
Of course the first four lines of code could be refactored in an appropriate function (even better if in an extension of UIViewController).
If that solves the problem, you can watch cs193p Stanford iOS course for further details.
In particular watch https://www.youtube.com/watch?v=HQrXM2zUPvY&index=6&list=PLPA-ayBrweUz32NSgNZdl0_QISw-f12Ai starting from the 30:20 mark.
Everything seems perfect with the below snippet
var first : [String] = []
#IBAction func btnTapped(_ sender: Any) {
let firstName = "iOS Geek"
self.first.append(firstName)
self.performSegue(withIdentifier: "MovetoSecVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MovetoSecVC"{
let secVC = segue.destination as! SecondVC
secVC.name = first
print(first) // it'll print here ["iOS Geek"]
}
}
// Your Second View Controller
class SecondVC: UIViewController {
var menu_vc : MenuViewController!
var name : [String]? // passing to this Array
override func viewDidLoad() {
super.viewDidLoad()
print(name!) // it'll print here ["iOS Geek"]
}
}

Could not Cast Value problems Swift iOS

I have an app where I ask the user to login first. After a successful login I want the username to follow to all the other view controllers in the app. I have tried declaring this as a public variable, but I am not sure how to do that properly (ie. it is not working in any instance) so I am now trying a prepareForSegue approach. However when I do i get a "Could not cast value of type 'SwiftLoginScreen.LoginVC' (0x10cdd33a0) to 'SwiftLoginScreen.ActionVC' (0x10cdd3130). (lldb)" - error.
I want to be able to call up the username from any and all view controllers in the app, but I am not able to solve this. I have the username from my HomeVC and I now want it to pass on to my ActionVC.
Home VC:
import UIKit
class HomeVC: UIViewController {
#IBOutlet var usernameLabel : UILabel!
var currentUser : NSString!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
let isLoggedIn:Int = prefs.integerForKey("ISLOGGEDIN") as Int
if (isLoggedIn != 1) {
self.performSegueWithIdentifier("goto_login", sender: self)
} else {
self.usernameLabel.text = prefs.valueForKey("USERNAME") as? String
currentUser = prefs.valueForKey("USERNAME") as? String
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as! ActionVC
destinationVC.operatingUser = self.currentUser;
}
#IBAction func gotoAction(sender : UIButton) {
let appDomain = NSBundle.mainBundle().bundleIdentifier
NSUserDefaults.standardUserDefaults().removePersistentDomainForName(appDomain!)
self.performSegueWithIdentifier("goto_action", sender: self)
}
}
Action VC:
import Foundation
import UIKit
class ActionVC : UIViewController, UIImagePickerControllerDelegate{
#IBOutlet var usernameLable : UILabel!
var operatingUser : NSString!
override func viewDidLoad() {
super.viewDidLoad()
self.usernameLable.text = operatingUser as? String
}
prepareForSegue will get called for all segues involving the View Controller. In your case it is being called for a segue where the destination view controller is an instance of LoginVC, but you have a forced downcast (as!) to ActionVC - so you get an exception at runtime.
You can use an optional binding to make sure that you are handling the correct segue -
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destinationVC = segue.destinationViewController as? ActionVC {
destinationVC.operatingUser = self.currentUser;
}
}
Your problem can be solved by adding
a username variable in AppDelegate class.
If you have a variable named userName
in AppDelegate you can access it anywhere in your app without passing it.
class AppDelegate
{
var userName = "Admin"
}
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
In your first controller you can set
the value:
appDelegate.userName = "Value"
and use the same to access it from
another controller:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.userName

Resources