Using Struct to pass a variable Swift - swift

I'm trying to use a Struct to pass some variables that I get from a Facebook Graph Request such as email, name, gender, etc.
I've created the Struct ('fbDemographics') and the variables in 'ViewController' but I get an error when I try to call the struct and one of the variables in 'SecondViewController' (Type 'ViewController' has no member 'fbDemographics'). I've never used struct before so a bit baffled why I am getting this error. Thanks for any thoughts. The code for both view controllers is below:
class ViewController: UIViewController, FBSDKLoginButtonDelegate {
override func viewDidLoad() {
super.viewDidLoad()
struct fbDemographics {
static var relationship_status: String?
static var gender: String?
static var user_education_history: String?
static var user_location: String?
static var email: String?
static var name: String?
}
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, relationship_status, gender, user_location, user_education_history, email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil){
//let fbDetails = result as! NSDictionary
//print(fbDetails)
if let userDataDict = result as? NSDictionary {
fbDemographics.gender = userDataDict["gender"] as? String
fbDemographics.email = userDataDict["email"] as? String
fbDemographics.name = userDataDict["name"] as? String
fbDemographics.user_location = userDataDict["user_location"] as? String
fbDemographics.user_education_history = userDataDict["user_education_history"] as? String
fbDemographics.relationship_status = userDataDict["relationship_status"] as? String
let myEducation = fbDemographics.user_education_history
let myEmail = fbDemographics.email
let myGender = fbDemographics.gender
let myName = fbDemographics.name
let myStatus = fbDemographics.relationship_status
let myLocation = fbDemographics.user_location
self.performSegue(withIdentifier: "LoginToHome", sender: (Any).self)
}
}
SECOND VIEW CONTROLLER
class SecondViewController: UIViewController {
#IBAction func verticalSliderChanged(_ sender: UISlider) {
let currentValue = String(sender.value);
sliderLabel.text = "\(currentValue)"
func viewDidLoad() {
super.viewDidLoad()
***ViewController.fbDemographics.myEmail***
}
}

The problem is that you've defined your struct inside viewDidLoad. So the scope is limited to that method. Move it out of viewDidLoad, but still in ViewController, and you should be able to access it from the SecondViewController. Obviously, you'll have to fix that reference to myEmail, too, because it's called email. Also, in your SecondViewController you should pull viewDidLoad implementation out of the verticalSliderChanged method; the viewDidLoad should be a top-level instance method of SecondViewController, not defined inside another method.
There are deeper problems here, though. Rather than using struct with static variables, you really should make those simple instance variables, create an instance of your FbDemographics type (note, start struct types with uppercase letter), and then pass this instance in prepare(for:sender:).
For example, the right way to pass data would be to:
eliminate static variables;
give your struct a name that starts with uppercase letter;
create instance of your struct; and
pass this instance to the destination view controller in prepare(for:sender:).
E.g.
struct FbDemographics {
var relationshipStatus: String?
var gender: String?
var userEducationHistory: String?
var userLocation: String?
var email: String?
var name: String?
}
class ViewController: UIViewController {
var demographics: FbDemographics?
override func viewDidLoad() {
super.viewDidLoad()
performRequest() // I actually don't think you should be initiating this in `viewDidLoad` ... perhaps in `viewDidAppear`
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? SecondViewController {
destination.demographics = demographics
}
}
func performRequest() {
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, relationship_status, gender, user_location, user_education_history, email"]).start { connection, result, error in
guard let userDataDict = result as? NSDictionary, error == nil else {
print("\(error)")
return
}
self.demographics = FbDemographics(
relationshipStatus: userDataDict["relationship_status"] as? String,
gender: userDataDict["gender"] as? String,
userEducationHistory: userDataDict["user_education_history"] as? String,
userLocation: userDataDict["user_location"] as? String,
email: userDataDict["email"] as? String,
name: userDataDict["name"] as? String
)
self.performSegue(withIdentifier: "LoginToHome", sender: self)
}
}
}
And then SecondViewController could:
class SecondViewController: UIViewController {
var demographics: FbDemographics!
override func viewDidLoad() {
super.viewDidLoad()
let value = demographics.email // this should work fine here
}
}

Related

Why I am getting 'valueForUndefinedKey' error when using AnyObject in Swift?

Im have a question. Why the following crash reason? since "name" exists?
viewcontroller1:
struct pointDict {
let id: Int
let name: String
}
var pointsPlane: [pointDict] = []
let wp = pointDict(id: 123, name: "test")
pointsPlane.append(wp)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? viewcontroller2 {
destVC.dt = pointsPlane[0] as AnyObject
}
}
viewcontroller2:
var dt: AnyObject?
let detail = self.dt
name.text = detail?.value(forKeyPath: "name") as? String
Crash Report:
valueForUndefinedKey:]: this class is not key value coding-compliant
for the key name
AnyObject is reference type but a struct is value type and therefore is not ObjC key-value coding compliant.
Your approach is bad practice anyway. A swiftier way is to declare the properties with the real type. The default declaration of detail in the template is not needed.
viewcontroller1:
struct PointDict {
let id: Int
let name: String
}
var pointsPlane: [PointDict] = []
let wp = PointDict(id: 123, name: "test")
pointsPlane.append(wp)
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
if let destVC = segue.destination as? viewcontroller2 {
destVC.dt = pointsPlane.first
}
}
viewcontroller2:
var dt : PointDict?
override func viewDidLoad() {
super.viewDidLoad()
name.text = dt?.name ?? ""
}

Keeping a value updated throughout many ViewController's in iOS using Swift

In my ViewController (VC1) I have the following variable:
var orderInfo: Order!
Order is a struct itself like the following:
struct Order {
var orderId: String
var orderReferenceNumber: String
//more variables...
init(
orderId: String,
orderReferenceNumber: String,
){
self.orderId = orderId
self.orderReferenceNumber = orderReferenceNumber
}
init(data: [String: Any]){
orderId = data[DatabaseRef.orderId] as? String ?? ""
orderReferenceNumber = data[DatabaseRef.orderReferenceNumber] as? String ?? ""
}
static func modelToData(order: Order) -> [String: Any] {
let data : [String: Any] = [
DatabaseRef.orderId: order.orderId,
DatabaseRef.orderReferenceNumber: order.orderReferenceNumber,
]
return data
}
}
In VC1, I have a listener that updates its info from Firestore Database (throught addsnapshotslistener). When the variable orderInfo gets updated in VC1 because of a change in the order in the database the listener will update the orderInfo variable in VC1. While the user is in another ViewController (e.g. VC2), I would like to access orderInfo variable with its updated info from VC1. How can I make this happen?
There are numerous of ways of doing that. Lets mention some of them.
You can use NotificationCenter
Example
class VC1: UIViewController {
var orderInfo: Order!
func updateOrder() {
var orderDict = ["orderInfo":orderInfo]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "orderInfoUpdated"), object: nil, userInfo: orderDict)
}
}
// The receiving end
class VC2: UIViewController {
var orderInfo: Order!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(doSomething(_: )), name: NSNotification.Name(rawValue: "orderInfoUpdated"), object: nil)
}
func doSomething(_ notification: NSNotification) {
if let orderInfo = notification.userInfo?["orderInfo"] as? Order {
// Do something
}
}
}
You can create an "app state" service where you use the singleton
pattern making the user info accessible in all the views
Example:
class AppState {
static let shared = AppState()
var orderInfo: Order!
...
}
On your ViewController
class ViewController: UIViewController {
func doSomeWork() {
print(AppState.shared.orderInfo)
}
}
You can make it global (Not Recommended)

How to assign realm result object to variable with. a class type

Is there any way to assign realm result object to variable with a class type: I have var movies: Results! in one view controller and variable var currentMovie : Movie? in another and I'm trying to pass it thru prepare for segue method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destVc = segue.destination as? PlaySelectedFavoritesViewController
destVc?.movies = movies[selectedRow]
}
But, that results in the following error.
Error: Cannot assign value of type 'FavoriteMoviesModel' to type
'Movie?'
Here is class and struct
class FavoriteMoviesModel: Object {
#objc dynamic var title = ""
#objc dynamic var poster_path = ""
#objc dynamic var id = 0
}
```
Movie struct
```
struct Movie: Codable {
let title: String?
let poster_path: String?
let id: Int?
enum CodingKeys: String, CodingKey {
case title = "title"
case poster_path = "poster_path"
case id = "id"
}
}

Call a func from another class in swift

I would like to call a function which is coded on another class.
So far I have made a struct on the file structs.swift for my data:
struct defValues {
let defCityName: String
let loadImages: Bool
init(defCity: String, loadImgs: Bool){
self.defCityName = defCity
self.loadImages = loadImgs
}
}
I have made the file Defaults.swift containing:
import Foundation
class DefaultsSet {
let cityKey: String = "default_city"
let loadKey: String = "load_imgs"
func read() -> defValues {
let defaults = NSUserDefaults.standardUserDefaults()
if let name = defaults.stringForKey(cityKey){
print(name)
let valuesToReturn = defValues(defCity: name, loadImgs: true)
return valuesToReturn
}
else {
let valuesToReturn = defValues(defCity: "No default city set", loadImgs: true)
return valuesToReturn
}
}
func write(city: String, load: Bool){
let def = NSUserDefaults.standardUserDefaults()
def.setObject(city, forKey: cityKey)
def.setBool(load, forKey: loadKey)
}
}
in which I have the two functions read, write to read and write data with NSUsersDefault respectively.
On my main ViewController I can read data with:
let loadeddata: defValues = DefaultsSet().read()
if loadeddata.defCityName == "No default city set" {
defaultCity = "London"
}
else {
defaultCity = loadeddata.defCityName
defaultLoad = loadeddata.loadImages
}
But when I try to write data it gives me error. I use this code:
#IBOutlet var settingsTable: UITableView!
#IBOutlet var defaultCityName: UITextField!
#IBOutlet var loadImgs: UISwitch!
var switchState: Bool = true
#IBAction func switchChanged(sender: UISwitch) {
if sender.on{
switchState = true
print(switchState)
}else {
switchState = false
print(switchState)
}
}
#IBAction func saveSettings(sender: UIButton) {
DefaultsSet.write(defaultCityName.text, switchState)
}
You need an instance of the DefaultsSet class
In the view controller add this line on the class level
var setOfDefaults = DefaultsSet()
Then read
let loadeddata = setOfDefaults.read()
and write
setOfDefaults.write(defaultCityName.text, switchState)
The variable name setOfDefaults is on purpose to see the difference.
Or make the functions class functions and the variables static variables and call the functions on the class (without parentheses)
From the code you posted, it seems you either need to make the write method a class method (just prefix it with class) or you need to call it on an instance of DefaultsSet: DefaultsSet().write(defaultCityName.text, switchState).
Another issue I found is that you also need to unwrapp the value of the textField. Your write method takes as parameters a String and a Bool, but the value of defaultCityName.text is an optional, so String?. This results in a compiler error.
You can try something like this:
#IBAction func saveSettings(sender: UIButton) {
guard let text = defaultCityName.text else {
// the text is empty - nothing to save
return
}
DefaultsSet.write(text, switchState)
}
This code should now compile and let you call your method.
Let me know if it helped you solve the problem

I want to observe when a value has been changed, and to load the view

//Patient class
import Foundation
struct Patients {
var family: NSArray
var given: NSArray
var id: String
var birthdate:String
var gender: String
}
struct Address {
var city: String
var country: String
var line: NSArray
}
class Patient {
var flag = 0
var address = Address(city: "", country: "", line: [""])
var patient_info = Patients(family: [""], given: [""], id: "", birthdate: "", gender: "")
var response : AnyObject?
init(response: AnyObject) {
self.response = response
if let entry = response.objectForKey("entry") {
//MARK: Address
if let resource = entry[0].objectForKey("resource") {
if let add = resource.objectForKey("address") {
address.city = add[0].objectForKey("city")! as! String
address.country = add[0].objectForKey("country")! as! String
address.line = add[0].objectForKey("line")! as! NSArray
//MARK: patient
patient_info.birthdate = resource.objectForKey("birthDate")! as! String
patient_info.gender = resource.objectForKey("gender")! as! String
if let name = resource.objectForKey("name") {
patient_info.family = name[0].objectForKey("family")! as! NSArray
patient_info.given = name[0].objectForKey("given")! as! NSArray
}
}
}
//MARK: id
if let link = entry[0].objectForKey("link") {
if let url = link[0].objectForKey("url") {
let id = url.componentsSeparatedByString("/")
patient_info.id = id[id.count-1]
}
}
}
print(patient_info)
}
}
//ViewController class
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
var viewModel = ViewModel()
#IBOutlet weak var family_name: UITextField!
#IBOutlet weak var given_name: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
family_name.delegate = self
given_name.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
switch textField {
case family_name:
family_name.resignFirstResponder()
given_name.becomeFirstResponder()
case given_name:
given_name .resignFirstResponder()
default:
print("")
}
return true
}
#IBAction func search(sender: UIButton) {
let family_name1 = family_name.text!
let given_name1 = given_name.text!
viewModel .searchForPatient(family_name1, given_name: given_name1)
//When the name property from my patient class changed I can call the //below method. How to implement the observer?
performSegueWithIdentifier("showSegue", sender:sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender:AnyObject?){
if segue.identifier == "showPatientSegue" {
if let displayViewController = segue.destinationViewController as? DisplayViewController {
displayViewController.viewModelDisplay.patient = viewModel.patient
}
}
}
}
// ViewModel where I make the request.
import Foundation
import Alamofire
import SystemConfiguration
class ViewModel {
var patient = Patient!()
func searchForPatient(family_name: String, given_name : String) {
let header = ["Accept" : "application/json"]
Alamofire.request(.GET, "https://open-ic.epic.com/FHIR/api/FHIR/DSTU2/Patient?family=\(family_name)&given=\(given_name)", headers: header).responseJSON { response in
self.patient = Patient(response: response.result.value!)
}
}
func checkInternetConnection() -> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(&zeroAddress) {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
return (isReachable && !needsConnection)
}
}
The problem is that the view loads fester than the request and I need to observe when a property has been changed in my Patient class, so the view can be loaded. If the view loads faster than the request I can't display the Patient information which I need.
You have lots options:
Store a delegate (weak!) object to the ViewController so that when your patient finishes, you can load the view. In the meantime, display something sensible in the view instead.
Send an NSNotification out, which the ViewController is a listener for.
KVO (Explanation of it here, just search for 'key-value observing'), which would require your Patient object to extend NSObject so that you can leverage objc's KVO.
Hope that helps!
You can add an observer on your variable this way :
var yourVariable:String!{
didSet{
refreshView()
}
}