Creating a global variable in swift - swift

I am trying to create a global variable from my view controller (inside the class but outside the functions) as follows:
var modelData: CustomTabBarController.model // THE ERROR IS HERE
This is how that class is defined:
CustomTabBarController.swift:
import UIKit
// This class holds the data for my model.
class ModelData {
var name = "Fred"
var age = 50
}
class CustomTabBarController: UITabBarController {
// Instantiate the one copy of the model data that will be accessed
// by all of the tabs.
var model = ModelData()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
However I am getting the following error:
"'model' is not a member type of "CustomTabBarController"
How do I declare it so that I can access model? Thanks.
Update #1
Sorry I forgot to mention this:
I need the model data to be the SAME in every tab of the tabbar. For example if I change the age to 51 in the first tab, the second tabbar should retrieve 51. Which would be the correct method above to use it this way?
Update #2
I am able to create the variable inside a function with dean's suggestion:
func setupModelData()
{
var modelData = (self.tabBarController as! CustomTabBarController).model
}
However this does not work, since I need to access the modelData from other functions. When I attempt to move this line outside of the function as follows:
import UIKit
class FirstViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
var modelData = (self.tabBarController as! CustomTabBarController).model
...
I receive the error:
Value of type '(NSObject) -> () -> FirstViewController' has no member 'tabBarController'

I ended up following holex's suggestion of creating a shared class (singleton):
import UIKit
class ModelData: NSObject {
static let shared: ModelData = ModelData()
var name = "Fred"
var age = 50
}
Writing in first view: Set age to 51:
ModelData.shared.age = 51
Reading in second view: Get age of 51
let age = ModelData.shared.age

I'm not sure whether you truly want a global variable (i.e. a single instance of ModelData shared between all your view controllers) or an instance variable which is public, so I'll try to answer both :)
1) global model
This line attempts to get the model property from the CustomerTabBarController class - i.e. if you made multiple tab bar controllers they would all use the same model.
var modelData: CustomTabBarController.model
If this is what you want, then you need to change this line to include the static keyword.
static var model = ModelData()
However, this almost certainly isn't what you're after.
2) shared instance variable
This means that the model is part of each instance of CustomTabBarController. Here, you would need to change the line which is throwing the error to be something like this:
var modelData: myCustomTabBarController.model
Without knowing more about your architecture, I can't help you get hold of your tab bar controller instance, but something like this might work (inside other view controllers):
var modelData = (self.tabBarController as! CustomTabBarController).model

model is an instance variable.
Either create an instance of CustomTabBarController
var modelData = CustomTabBarController().model
Or declare model as class variable
static var model = ModelData()
...
var modelData = CustomTabBarController.model
However to use ModelData as a single global variable with the two members, use a struct rather than a class and declare the members as static.
struct ModelData {
static var name = "Fred"
static var age = 50
}
You can access the name from everywhere for example
let name = ModelData.name
and there is no need to create an extra variable in another class.
An – instance based –  alternative is a singleton
struct ModelData {
static let shared = ModelData()
var name = "Fred"
var age = 50
}
and use it
let name = ModelData.shared.name

Related

Swift structs defined in separate files do they need initialisers?

I have defined a simple struct in a separate swift file as follows:
import AppKit
//Declaring a new struct for Company
public struct CompanyStruct {
var idCompany: Int
var company: String
var compType: String
}
However, when I try to use this struct it finds the struct if do:
var c = CompanyStruct
and I can select it but I get no parameters prompted when I open the bracket. If I initialise the struct as:
import AppKit
//Declaring a new struct for Company
public struct CompanyStruct {
var idCompany: Int
var company: String
var compType: String
init(idCompany: Int, company: String, compType: String) {
self.idCompany = idCompany
self.company = company
self.compType = compType
}
}
Then it works fine if I use the struct in say View Controller
I thought you did not have to initialise structs? Is it because I define the struct in an separate file?
No you don't need to add initialiser for struct. It has member wise initialiser by default. This is an xCode bug. When it happens just use YourStruct.init and it will show autocompletion. After that you can remove init part and it will work for the rest of the structs.
Structs have a default initializer hence you can create an object without passing parameters.
You can create custom initialiers which you have done.
One thing about structs is you can't create a convenience initializer.
When you use ( ) after structure name when define instance for it you are making initialization automatically.
Let companyStruct = CompanyStruct ( )
But you should give some default values in struct like;
struct CompanyStruct {
var idCompany: Int = 1
}

How do I pass arguments to an objects function without a crash

I'm trying to move the logic of a viewController to a view model but for some reason it always crashes, saying unexpectedly found nil while unwrapping an Optional value. I constantly get this error no matter what code I try to move, so I must be doing something fundamentally wrong. Here's a sample of the code I have in the viewController:
var recipesViewModel: RecipesViewModel! //VIEW MODEL CLASS REFERENCE
var recipeCategory = recipesViewModel.transformToUpperCase(dataRecieverStringRecipeView: "testString")
Then in the view modelclass:
func transformToUpperCase(dataRecieverStringRecipeView: String) -> String {
var recipeCategory = dataRecieverStringRecipeView
var prefixRecipeCategory = recipeCategory.prefix(1).uppercased()
var dropFirstRecipeCategory = recipeCategory.dropFirst()
var upperCasedRecipeCategory = prefixRecipeCategory + dropFirstRecipeCategory
return upperCasedRecipeCategory
}
...it translates the string to have an uppercase letter as its first letter.
The code works perfectly fine when everything is in the view model, but as soon I move it to another class and call the function through an object it crashes. What am I missing?
You need to fix your view model class reference
Line to change:
var recipesViewModel: RecipesViewModel!
Replacement Line:
var recipesViewModel = RecipesViewModel()
This line of code will properly declare/create recipesViewModel as an object of class RecipesViewModel.
Hope that resolves your inquiry!
var recipesViewModel: RecipesViewModel! does not instantiate a new object. Put the category function back in your RecipesViewModel class, then change the variable declaration to:
Declaration
var recipesViewModel: RecipesViewModel!
Later on
recipesViewModel = RecipesViewModel() //or whatever initializer you need.
Edit
As rmaddy pointed out: you probably can ditch the ! and use var recipesViewModel: RecipesViewModel = RecipesViewModel() instead if you don't have any failable initializers or this isn't a runtime injected property and just put it as a 1-liner:
var recipesViewModel: RecipesViewModel = RecipesViewModel()

how to create a singleton in swift with init variables

I am trying to create a singleton class in swift but I am getting an error
"cannot create a single-element tuple with an element label"
i am not getting it.
class GroupObject {
// we want the group object to be a singleton
var name: String
var id: Int
var groupJsonObject: JSON
init(groupJsonObject: JSON){
self.groupJsonObject = groupJsonObject
self.id = groupJsonObject["id"].int!
self.name = groupJsonObject["name"].string!
}
class var sharedInstance : GroupObject {
struct Static {
static let instance : GroupObject = GroupObject(groupJsonObject: JSON) // this is the problem line.
}
return Static.instance
}
}
The problem is that you cannot pass a parameter to the singleton. Your singleton implementation doesn't know to what JSON refers.
If you want this to be a singleton, you'd have to initialize the groupJsonObject separately from the initialization of the shared instance. For example:
class GroupObject {
var name: String!
var id: Int!
var groupJsonObject: JSON! {
didSet {
id = groupJsonObject["id"].int!
name = groupJsonObject["name"].string!
}
}
static let sharedInstance = GroupObject() // btw, this is a more concise syntax for declaring a singleton
}
And then, when you want to initialize those properties, you could do:
GroupObject.sharedInstance.groupJsonObject = json
If your "singleton" is supposed to hold some data passed to it on instantiation, how will it get that data? Where/when is it available?
I think you don't actually want a singleton at all; you want an instance created with your JSON data to be accessible from different points in your application. In that case, pick some "master controller", create it there, then pass it along to other controllers as needed.

Assigning values to properties in a singleton implemented with static let in Swift

From reading it seems like the best advice for creating a singleton in Swift is to use static let like this:
struct GameManager {
static let defaultManager = GameManager()
var gameScore = 0
var saveState = 0
private init() {}
}
GameManager.defaultManager.gameScore = 1024 // Error
GameManager.defaultManager.saveState = 12 // Error
let score = GameManager.defaultManager.gameScore
let savedProgress = GameManager.defaultManager.saveState
Because the defaultManager is declared as a constant (with "let"), there is an error when I try to assign gameScore and saveState.
I'm running Xcode 7.0 beta 6 (7A192o) with Swift 2.0 (swiftlang-700.0.57.3 clang-700.0.72).
If I change the defaultManager to be declared as a variable (with "var"), will it no longer be considered a proper singleton?
If I change the GameManager to be declared as a class instead of a structure, then the code works as expected.
class GameManager {
static let defaultManager = GameManager()
var gameScore = 0
var saveState = 0
private init() {}
}
GameManager.defaultManager.gameScore = 1024 // No error, why?
GameManager.defaultManager.saveState = 12 // No error, why?
let score = GameManager.defaultManager.gameScore // 1,024
let savedProgress = GameManager.defaultManager.saveState // 12
Can you explain why the reference-type class is better for implementing the singleton than the value-type structure?
You can't really change it because on your struct you have a let constant named defaultManager. As you know already a struct is a copy type while a class is a pass by reference one. if you want to use it as a struct you will have to replace that let with var instead. Another take of this problem is to change the struct to a class that way you will be able to change the defaultManager value although it is declared as a let.
cheers
edit: The main difference in your code is that when you have a constant in a class and you change that constant you are actually referring to its address rather than its actual value.Same doesn't happen with a struct cause as a value type you can't do that call by reference thingy you do with class and so you are forced to have a constant(let) value that cannot be mutated. Change the let to var inside your struct and see the magic happen

Are Swift constants lazy by default?

Something I still not quite understand about Swift ... let's say I want a property that instantiates a class in a base class used for several sub classes, e.g. ...
let horse = Horse();
Is horse instantiated right after app/class initialisation or when the property is accessed for the first time?
On the other hand using lazy var guarantees that the property is only instantiated the first time it is accessed ...
lazy var horse = Horse()
But then horse is not a constant. So in this case if I access horse multiple times I would get multiple instances of horse created, right?
What if I wanted both, a lazy property that is also a constant?
Not exactly. Say you have a class Farm and inside Farm there is horse property.
class Farm {
let horse = Horse()
}
In this case horse property is initialized when class instance initialized. If you make it lazy you have to make it mutable too.
class Farm {
lazy var horse = Horse()
}
In this case, horse property is initialized when it is accessed the first time. And later when it is accessed again it returns the same instance again instead of reinitializing it. But since it is a mutable property you can assign a new instance of Horse to it. After you assign it new value it will return this new value whenever it is accessed.
EDIT: If let horse = Horse() is defined in global space then it is lazily created at first access.
Well, a bit late, but I think you can create a lazy property as constant using private(set) . Consider the example below :
import Foundation
struct GPS: CustomStringConvertible {
let name: String
init(name: String) {
self.name = name
print("GPS Initialised")
}
var description: String {
return name
}
}
struct Car {
private(set) lazy var gps = GPS(name: "One")
init() {
print("Car Initialised")
}
}
var someCar = Car()
print(someCar.gps) // Comment/Uncomment this to see lazy in action
//someCar.gps = GPS("Two") // This will create a compilation error
//
However, if you remove the private(set), it(gps variable inside Car) will become a variable again which can be mutated.
You can uncomment the last line after removing the private(set) to verify the same