Can't send data between to view controllers Swift - ios

I have got a tabbed project in my Xcode.But my Entry Point is a separate view controller that is not connected to tabs.So when user clicks Login button i send value of input to one of view controllers in tabbed part of my project.I have a segue between separated VC and VC that I send data to.Here is my code In part where I send the data
protocol SendDel {
func userDidEnterData(data: String)
}
LogInViewController: UIViewController {
var delegate:SendDel!=nil
#IBAction func SendB(_ sender: Any) {
if(delegate != nil){
if(self.Usn != nil){
let data = self.Usn.text
self.delegate?.userDidEnterData(data: data!)
}
}
}
}
And here is code in part where I receive data
class FirstViewController: UIViewController,SendDel {
func userDidEnterData(data: String) {
UsernameLabel.text="\(data)"
dismiss(animated: false, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "First"){
let sendingVc=segue.destination as! LogInViewController
sendingVc.delegate = self
}
}
}
But unfortunately it is not working.

Actually you setting the delegate property from FirstViewController and then when you present the LogInViewController you have write the code var delegate:SendDel!=nil which makes delegate nil every time you tapped the button.
So try below code :
var delegate: SendDel?
Hope it works for you.

Related

Swift - Pass data from Base ViewController to two Container ViewControllers after loading from Firestore

I know this may be a simple solution but I can't figure it out. I'm trying to load data from Firestore in my Base ViewController to then populate two Container Viewcontrollers at the same time.(I want to do it this way to save on Read Cost) I've been trying to go the segue route but the segue is called before my data is finished loading from Firestore. I need the data to be present to popular the two different Container Viewcontrollers(One container is a chart. The other container is a line graph). Any suggestions would be greatly appreciated.
import UIKit
import Firebase
class BaseViewController: UIViewController {
var db: Firestore!
override func viewDidLoad() {
super.viewDidLoad()
let settings = FirestoreSettings()
Firestore.firestore().settings = settings
db = Firestore.firestore()
if Reachability.isConnectedToNetwork(){
print("Internet Connection Available!")
loadFirestoreData()
} else {
print("Internet Connection not Available!")
}
}
// Load Firestore Data
func loadFirestoreData() {
db.collection("chartGraph").document("companyX")
.getDocument{ (document, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.performSegue(withIdentifier: "ChartSegue", sender: document!.data()!)
self.performSegue(withIdentifier: "LineGraphSegue", sender: document!.data()!)
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if identifier == "ChartSegue" {
let vc1 = segue.destinationViewController as? ChartViewController
vc1.dataLoaded(data: (sender as? [String: Any])!)
}
if identifier == "LineGraphSegue" {
let vc2 = segue.destinationViewController as? LineGraphViewController
vc2.dataLoaded(data: (sender as? [String: Any])!)
}
}
}
You need to keep a reference to Container in your MainViewController.
For that you should add instance variables to MainViewController that will hold a reference to the container controllers, not just the view. You'll need to set it in prepareForSegue.
So the beginning of MainViewController look something like this:
class MainViewController: UIViewController {
var containerViewChartController: ChartViewController?
var containerViewLineGraphController: LineGraphViewController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let controller = segue.destination as? ChartViewController {
containerViewChartController = controller
} else if let controller = segue.destination as? LineGraphViewController {
containerViewLineGraphController = controller
}
}
then you can call container methods like this
func button_Container() {
containerViewChartController?.changeData(yourData)
}

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
}

prepareForSegue called before performSegue

I am trying to perform a segue that passes a number of variables to the next view including one variable, currentID, which is retrieved from a parse database. performSegue should not be called until after currentID has been set to the currentID downloaded from the database. However, when I run the code, currentID ends up being an empty string when it is passed to the next view.
Here is my code called by the Button:
#IBAction func submitButtonPressed(_ sender: Any) {
let point = PFGeoPoint(latitude:0.0, longitude:0.0)
let testObject = PFObject(className: "Person")
testObject["inputAmount"] = inputAmount
testObject["outputAmount"] = outputAmount
testObject["inputCurrency"] = inputCurrency
testObject["outputCurrency"] = outputCurrency
testObject["location"] = point
testObject.saveInBackground { (success, error) -> Void in
// added test for success 11th July 2016
if success {
print("Object has been saved.")
self.currentID = String(describing: testObject.objectId!)
if(self.currentID != ""){
self.performSegue(withIdentifier: "mainToListSegue", sender: self)
}
} else {
if error != nil {
print (error)
} else {
print ("Error")
}
}
}
}
And here is the prepareForSegue method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let listViewController = (segue.destination as! UINavigationController).viewControllers[0] as! ListViewController
listViewController.inputCurrency = inputCurrency
listViewController.outputCurrency = outputCurrency
listViewController.inputAmount = inputAmount
listViewController.outputAmount = outputAmount
listViewController.currentID = currentID
listViewController.cellContent = cellContent
}
To achieve your needs, you MUST connect your segue between viewcontrollers, and not from UIButton to viewcontroller.
Every time you need to prepare your segue before calling it, this is the procedure:
Then, name it and use delegate method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
}
}
For navigating from one controller to another, connect your segue from view controller instead of from the button and it will work.

How can I call a method that is inside a UIViewController embedded in a container from a parent UIViewController?

I have an ios app in swift and I have a UIViewController (let's call it parentController) with a container. This container embeds another UIViewController called embedController.
embedController contains a method that prints a message to a console.
How can I call this method from my parentController?
I tried to use protocols, my current code is as follows:
class ParentController: UIViewController {
var handleEmbedController:HandleEmbedController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "embedViewSegue"){
if let embed = segue.destinationViewController as? EmbedController {
embed.value1 = value1
}
}
#IBAction func sendMsgButtonAction(sender: AnyObject) {
handleEmbedController?.printMsg() //this so far does nothing
}
}
and my embedController:
protocol HandleEmbedController: class {
func printMsg()
}
class EmbedController: UITableViewController, HandleEmbedController{
var value1 = ""
func printMsg(){
print("printing some embedded message")
}
}
How can I print this message from a parent controller?
What are you doing in your prepare for segue? Aren't you supposed to set your delegate (protocol) there? Like this:
if (segue.identifier == "embedViewSegue"){
if let embed = segue.destinationViewController as? EmbedController {
self.handleEmbedController = embed
}
}
If you put a breakpoint in sendMsgButtonAction you should see that the property handleEmbedController is nil. And that's why the method call does nothing since you are safely unwrapping it with ?.

Swift - how to transfer from one view controller to another?

Back to my first swift app, I'm doing the sign in/sign up part, and basically the design with the view controllers so far are:
Welcome screen (sign in/sign up buttons) - Sign in - Sign up - Main Program
I got to the part where in the sign up view controllers, if the user misses one of the required field blanks and they click the button "submit", they will be prompted and back to the current view controller (sign up) to fill the missing fields. Now I want to set that when all the fields are filled, what line of code can I use so when they click the button "submit" it will head back to the welcome screen, so they can sign in afterwards?
Similarly, I got all the the part to check if the user enters correct user name and password, so when they entered the wrong info, they will be prompted and hack to the current view controller (sign in), and how can I do so if they entered the correct user name - password, it will head to the main program view controller (the 4th one)
If what I said above is confusing, I'll post my current controllers and the related code in here. Anyone has any idea? Again, this is my first program so it'd mean very much if I can get some helps from you. Thank you for reading.
An example on how to do it:
import UIKit
class WelcomeVC: UIViewController {
//make it IBAction or call it from IBAction
func singInButtonPressed(){
performSegueWithIdentifier("sign in", sender: self)
}
//make it IBAction or call it from IBAction
func singUPButtonPressed(){
performSegueWithIdentifier("sign up", sender: self)
}
}
class SignUPVC: UIViewController {
#IBOutlet var userNameField:UITextField!
#IBOutlet var passwordField:UITextField!
//make it IBAction or call it from IBAction
func submit(){
if checkValidity() {
presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
func checkValidity() ->Bool{
if !userNameField.text!.isEmpty{
if !passwordField.text!.isEmpty {
return true
} else {
//do something to inform passwordfield is missing
}
} else{
//username field is missing
}
return false
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "sign in" {
if let sINvc = segue.destinationViewController.contentViewController as? SignInVC{
sINvc.username = userNameField.text
sINvc.password = passwordField.text
//and pass more of the other info you gathered in this vc
}
}
}
}
class SignInVC: UIViewController {
#IBOutlet var userNameField:UITextField!{
didSet {
if username != nil { userNameField.text = username }
}
}
#IBOutlet var passwordField:UITextField! {
didSet {
if password != nil { passwordField.text = password }
}
}
var password:String?
var username:String?
//make it IBAction or call it from IBAction
func signInPressed(){
if !userNameField.text!.isEmpty && !passwordField.text!.isEmpty {
performSegueWithIdentifier("Main page", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Main page"{
//your preparation
}
}
}
//this extension is helpful to avoid typing this everytime there is nav vc in your way of segueing
extension UIViewController{
var contentViewController: UIViewController {
if self is UINavigationController{
if let cvc = (self as! UINavigationController).visibleViewController { return cvc }
}
return self
}
}
simple way to transfer one view to another in swift is this given below:-
let storyboard = UIStoryboard(name: "Main", bundle: nil)
//in "Main" your storyboard name
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("LoginPage") as! UIViewController
//in place of Login Page your storyboard identifier name
navigationController?.pushViewController(secondViewController, animated: true)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let mainTabBar = storyBoard.instantiateViewController(withIdentifier: "MainTabBar") as! UITabBarController
self.navigationController?.pushViewController(mainTabBar, animated: true)
works for me. thanks! update for the swift 3.1 and Xcode 8.3.2

Resources