How do you pass data between view controllers in Swift? - ios

I tried creating global variables and updating the information when the view is loaded but data isn't being rendered.
GLOBAL VARIABLES
var viewName:String = ""
var viewDuration:String = ""
var viewPeriod:String = ""
var viewMinAmp:String = ""
var viewMaxAmp:String = ""
var viewStep:String = ""
var viewType:String = ""
Is there a more efficient way of passing information other than having global variables?
#IBOutlet var txtName: UITextField!
#IBOutlet var txtDuration: UITextField!
#IBOutlet var txtPeriod: UITextField!
#IBOutlet var txtMinAmp: UITextField!
#IBOutlet var txtMaxAmp: UITextField!
#IBOutlet var txtStep: UITextField!
#IBOutlet var txtType: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setInfo(viewName, duration: viewDuration, period: viewPeriod, minAmp: viewMinAmp, maxAmp: viewMaxAmp, step: viewStep, type: viewType)
}
func setInfo(name: String, duration: String, period: String, minAmp: String, maxAmp: String, step: String, type: String) {
txtName.text = name
txtDuration.text = duration
txtPeriod.text = period
txtMinAmp.text = minAmp
txtMaxAmp.text = maxAmp
txtStep.text = step
txtType.text = type
}

One solution would be to override prepareForSegue(segue:sender:) from within the view controller which contains the data that you wish to pass to the destination view controller.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "YourSegueName") {
//get a reference to the destination view controller
let destinationVC:ViewControllerClass = segue.destinationViewController as! ViewControllerClass
//set properties on the destination view controller
destinationVC.name = viewName
//etc...
}
}

For Swift 3.0
final class Shared {
static let shared = Shared() //lazy init, and it only runs once
var stringValue : String!
var boolValue : Bool!
}
To set stringValue
Shared.shared.stringValue = "Hi there"
to get stringValue
if let value = Shared.shared.stringValue {
print(value)
}
For Swift version below 3.0
You can pass data between views using singleton class. It is easy and efficient way. Here is my class ShareData.swift
import Foundation
class ShareData {
class var sharedInstance: ShareData {
struct Static {
static var instance: ShareData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = ShareData()
}
return Static.instance!
}
var someString : String! //Some String
var selectedTheme : AnyObject! //Some Object
var someBoolValue : Bool!
}
Now in my ViewControllerOne I can set above variable.
//Declare Class Variable
let shareData = ShareData.sharedInstance
override func viewDidLoad() {
self.shareData.someString ="Some String Value"
}
And in my ViewControllerTwo I can access someString as
let shareData = ShareData.sharedInstance
override func viewDidLoad() {
NSLog(self.sharedData.someString) // It will print Some String Value
}

Personally, I prefer ways as follow:
If you want to jump forward between two view controllers (from A to B), as -pushViewController:animated: in navigation, you can define a property of model for Controller B and expose it publicly, then set this property explicitly before jumping from Controller A, it's pretty straightforward;
In case you want to jump backward from Controller B to A, use Delegate+Protocol mode. Controller B drafts a public protocol and own a "delegate" property, any object who would like to be the delegate of Controller B shall comply and implement its protocol(optionally). then prior to the jumping-backward, Controller B makes its delegate perform certain action(s) listed in protocol, the data could be transferred in this way;
Under certain circumstance, you may want to transfer data from a Controller(or controllers) to other multiple Controllers, use Notification mechanism if this is the case.
Apple has detailed instructions about delegate mode, notification mode in official documentation, check them out in XCode, :)

Just need to follow 3 steps, let's assume you want to pass data from ViewControllerA to ViewControllerB:
create a segue between ViewControllerA and ViewControllerB
name the segue with a Identifier in the attributes inspector of it
override the prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) at ViewControllerA
For step#3,:
if you are not using swift 2.1, please follow #Scott Mielcarski 's answer at this question
for people who are using swift 2.1, or who get error "Cannot convert value of type 'UIViewController' to specified type 'your view Controller class name', After following #Scott Mielcarski 's answer at this question, Please use:let destinationVC:ViewControllerClass = segue.destinationViewController as! ViewControllerClass instead of
let destinationVC:ViewControllerClass = segue.destinationViewController
This is tested on Swift 2.1.1 and it works for me.

If you don't actually want to pass data between view controllers but rather simply want to store a global variable you can do this:
This gives a great explanation for how to do this in Swift 5: https://www.hackingwithswift.com/example-code/system/how-to-save-user-settings-using-userdefaults
Summary:
To set a value:
let defaults = UserDefaults.standard
defaults.set("value", forKey: "key")
To get a String value:
let key = defaults.object(forKey: "StringKey") as? [String] ?? [String]()
To get integer value:
let key = defaults.integer(forKey: "IntegerKey")

Related

Displaying image selected between collection view controllers

I have structs as follows in a First Collection View Controller
struct Area{
var name = String()
var image = String()
}
var area = [Area]()
and in Second Collection View Controller
struct AreaSelected {
var imageSelected = String()
}
var areaSelected = [AreaSelected]()
I want to display image selected from First Collection View Controller in the Second Collection View Controller.
So I did this for navigating them to Second Collection View Controller at didSelectItemAt indexPath
let indexPaths = self.areaCV!.indexPathsForSelectedItems!
var indexPath = indexPaths[0] as IndexPath
let detailViewController = self.storyboard?.instantiateViewController(withIdentifier: "SVC") as? SecondViewController
detailViewController?.areaSelected = [self.area[(indexPath as NSIndexPath).item]]
Them I am getting following compiler error
Cannot convert value of type 'Area' to expected element type
'AreaSelected'
How do I get rid of this error?
Basically
let selectedArea = self.area[(indexPath as NSIndexPath).item]
detailViewController?.areaSelected = [AreaSelected(imageSelected: selectedArea.name)]
PS: You are using different (incompatible) types in different view controllers so definitely you can't assign it directly to each other.
PSS: Much easier, cleaner and better to setup segue on CellSelected between ViewControllers, and assign areaSelected in func prepare(for segue:UIStoryboardSegue, sender: Any?)
The error message is pretty clear, your first struct is type: FirstCollectionViewController.Area and the second has a type SecondCollectionViewController.AreaSelected. The two are as different as Int and String, you can't assign one to another as you can't do let number: Int = "Of course no". However you can define a common type:
protocol AreaDescription {
var image: String { get }
}
class FirstVC: ... {
struct Area: AreaDescription {
var name: String
var image: String
}
}
class SecondVC: ... {
struct AreaSelected: AreaDescription {
var name: String
var image: String
}
}
And set your property as:
var areaSelected = [AreaDescription]()

struct not keeping tuples order when passed (swift3)

My Code produces a a tuples that is displayed on a label on view controller 1. I tried struct the label from vc1 to vc2 but the order is not being kept. All I want to do is replicate the exact order and the way the tuple is displayed on vc 1, on VC 2.
VIEW CONTROLLER 1
import UIKit
var number = [Int]()
var yourArray = [String]()
class ViewController: UIViewController {
#IBOutlet var labez: UILabel!
#IBOutlet var textA: UITextField!
#IBOutlet var textB: UITextField!
#IBAction func move(_ sender: Any) {
bad.mm = [String( labez.text ?? "")]
}
#IBAction func store(_ sender: Any) {
yourArray.append((textA.text!))
number.append(Int(textB.text!)!)
let tuples = zip(yourArray,number)
let sorted = tuples.sorted(by: { this, next in
if this.0 < next.0 {
return true
} else if this.0 == next.0 {
return this.1 < next.1
} else {
return false
}
})
print(sorted)
labez.text = sorted.map { " \($0)" }.joined(separator:"\n")
bad.mm = [String(describing: sorted.map { " \($0)" }.joined(separator:"\n")
)]
}
struct bad {
static var mm = [String]()
}
}
view controller 2
import UIKit
class ViewController2: UIViewController {
#IBOutlet var benCarson: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
benCarson.text = (String(describing: ViewController.bad.mm))
}
}
Besides my critique of how you designed your data and your naming conventions. I believe what you want is to ASSIGN your bad.mminstead of APPEND.
What is happening is the first time you enter values to bad.mm it is (a, 2). Then when it is appended you add the sorted arrays (a, 1), (a, 2) to the existing string, making it (a, 2), (a, 1), (a, 2) If you assign it it will be now just the new, sorted array, (a, 1), (a, 2).
To assign change
bad.mm.append(String(describing: sorted.map { " \($0)" }.joined(separator:"\n"))
In ViewController class to
bad.mm = String(describing: sorted.map { " \($0)" }.joined(separator:"\n")
In ViewController your move function does a similar thing where it APPENDS to bad.mm where you probably want to assign. However you assign it with a UITextField.Text property which is optional. Using the ?? operator you can give unwrap this optional while providing it a default value. An empty string is often a good default value. So for this I would suggest changing the line inside of #IBAction func move to the following:
bad.mm = labez.text ?? ""
Or just actually delete this line since you assign bad.mm and labez.text at the same time in your earlier function. But that is why you were getting the optional() around your text.
That should give you your desired effect. The reason your "tuple" isn't being passed in the right order is that your not grabbing the sorted tuple from one VC to the next, you are grabbing an improperly formatted string from one VC to another. Consider passing the sorted tuple directly and then formatting the string separately in both ViewControllers to reduce confusion.
Why don't you use prepare(for segue: instead of a struct to pass the tuples.
You can do it like this:
In ViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// give your segue an id (in the interface builder)
if segue.identifier == "mySegue" ,
let nextScene = segue.destination as? ViewController2 {
nextScene.yourTuples = yourArray // or anything else
}
}
And in your ViewController2:
class ViewController2: UIViewController {
val yourTuples: [String]()!
#IBOutlet var benCarson: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
benCarson.text = (String(describing: ViewController.bad.mm))
}
}

Stored TextField into custom class

I'm currently doing a login system test, I'd like to log in the accountTextfield of the string stored in the Account I built the class, so I can use in other controllers, this method should be how to achieve, can anyone help me, thank you
Here is class of Account
class Account {
var id:String?
init(id:String)
self.id = id
}
And here is my LoginViewController
#IBOutlet weak var accountTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBAction func DoLogin(_ sender: AnyObject) {
login_now(username:accountTextField.text!,password:passwordTextField.text!)
}
func login_now(username:String, password:String)
{
let parameters = ["account": accountTextField.text!, "password": passwordTextField.text!] as Dictionary<String, String>
let url = URL(string: "http://163.18.22.78/api/Login")!
.
.
.
}
class Account: NSObject {
var id:String?
}
class Apimanager: NSObject {
static let instance = Apimanager()
var user :Account = Account()
}
func login_now(username:String, password:String)
{
let parameters = ["account": accountTextField.text!, "password": passwordTextField.text!] as Dictionary<String, String>
Apimanager.instance.user.id = accountTextField.text!
print(Apimanager.instance.user.id!)
}
In another view controller
Print(Apimanager.instance.user.id!
if you want use init() method in Account class then you can use
UserDefaults.standard.set(accountTextField.text!, forKey: "account")
UserDefaults.standard.synchronize()
to store particular value and to get value in another view controller you can use
print(UserDefaults.standard.value(forKey: "account"))..
note that both key value should be match for setting and getting value from UserDefaults..hope it work for you!!

How to get rid of "Optional(...)" in my output when transferring data to another ViewController

Im tring to transfer the first name of the user to another view controller when a button is pressed, and have been able to do so successfully. But i have encountered the problem of seeing "Optional(first name)" in my output. How do i get rid of this?
1st View Controller :
#IBOutlet weak var firstName: UITextField!
#IBOutlet weak var lastName: UITextField!
#IBOutlet weak var email: UITextField!
#IBOutlet weak var password: UITextField!
var userFirstName: String = ""
var userLastName = ""
var userEmail = ""
var userPassword = ""
override func viewDidLoad() {
super.viewDidLoad()
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(AccountViewController.dismissKeyboard))
//Uncomment the line below if you want the tap not not interfere and cancel other interactions.
//tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
// Do any additional setup after loading the view.
}
func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
#IBAction func createAccountBtn(_ sender: Any) {
userFirstName = String(describing: firstName.text)
userLastName = String(describing: lastName.text)
userEmail = String(describing: email.text)
userPassword = String(describing: password.text)
print(userFirstName)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let newVC: WelcomingViewController = segue.destination as! WelcomingViewController
newVC.userFirstName = userFirstName
2nd view controller :
#IBOutlet weak var firstNameLabel: UILabel!
var userFirstName: String? = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
firstNameLabel.text = userFirstName!
}
More than a solution you need to get the basic idea about the optional in Swift.
Swift Optional
You can go for
Forced Unwrapping: Do this if you are very sure about the content. Otherwise App will crash
firstNameLabel.text = userFirstName!
Optional Binding: This is more secure way
if let firstName = userFirstName {
firstNameLabel.text = firstName
}
Besides the if let approach, you can use nil coalescing like this:
firstNameLabel.text = userFirstName ?? "n/a"
This provides fallback string (or empty string, if you prefer) in case the original variable is nil.
Try this:
if let firstName = userFirstName {
firstNameLabel.text = firstName
}
This makes sure that userFirstName is not nil before it is passed to your label.
Edit
However, one thing to notice is that unless you are specifically setting userFirstName to nil at some point, you don't need it to be an optional because you give it an initial value of "". An optional is for something that may or may not have a value, and thus needs to be treated more carefully.

Prepare for segue not sending all of the information (Swift)

When trying to pass String variables to a new ViewController, only one of the variables is being sent. I'm not sure what is going on here, because when I print object["eventDescription"], the console does indeed print the description. Only eventName is being passed to the new view controller.
Sending view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "detailSegue") {
if let destination = segue.destinationViewController as? EventDetailViewController {
if let eventIndex = self.tableView.indexPathForCell(sender as! FeedCustomCell) {
var object = self.eventsArray[eventIndex.row]
destination.nameText = object["eventName"] as! String
destination.descriptionText = object["eventDescription"] as! String
print(object["eventDescription"])
The destination view controller (EventDetailViewController):
class EventDetailViewController: UIViewController {
#IBOutlet weak var eventName: UILabel!
#IBOutlet weak var descLabel: UILabel!
var descriptionText = String()
var nameText = String()
override func viewDidLoad() {
super.viewDidLoad()
self.eventName.text = nameText
self.descLabel.text = descriptionText
}
}
Sorry for wasting anybody's time, but I had to place constraints onto the description UILabel in the EventDetailController, and then I could see the description.

Resources