Swift structs defined in separate files do they need initialisers? - swift

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
}

Related

Is it possible to have default member initialization with a class in Swift (like with a struct)

In the following example code, I create a struct and a class with similar members. With the struct I can initialize an instance by any number of the members into its constructor, and the rest will default. With a class, I have to specify every version of init I want to use. Seems like I must be missing some way to do it with a class though -- is there any way to do this? It looks like in 2016 there was not, but I know Swift has changed a ton since then. I'm hoping there is a way now.
import Foundation
struct FooStruct {
var id: UUID = UUID()
var title = ""
}
// these statements both work fine
let a = FooStruct(id: UUID())
let a2 = FooStruct(title: "bar")
class FooClass {
var id: UUID = UUID()
var title = ""
}
// these statements both give the same error:
// Argument passed to call that takes no arguments
let b = FooClass(id: UUID())
let b2 = FooClass(title: "bar")
What you are seeing with Structure types is what is called a memberwise initializer. Swift does not provide one of these to Class types because of the more complex way Classes are initialized, due to their inheritance model.
Swift provides a default initializer—different than a memberwise initializer—for any structure or class that provides default values for all of its properties and doesn’t provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values.
you could just use this:
class FooClass {
var id: UUID = UUID()
var title = ""
init(id: UUID = UUID(), title: String = ""){
self.id = id
self.title = title
}
}
and this will work:
let b = FooClass(id: UUID())
let b2 = FooClass(title: "bar")

Why are there parenthesis after the value of this variable in Swiftui?

Why are there parenthesis after the value of the entries variable in Swiftui? What does this mean?
func barChartItems() -> [ChartDataEntry] {
var entries = [ChartDataEntry]()
...
}
This is just a syntax to create an object of an empty typed array of ChartDataEntry type.
Creating an Empty Array
var someInts = [Int]() // an empty array of Int
Specifically the parenthesis following the declaration is the init() method call for the class or type. Because an Array type can be created without any init variables they are not needed to initialize the Array type, in-fact Swift best practice is not to add them if you aren't passing anything:
https://developer.apple.com/documentation/swift/array
In the case of a class, you always need the parenthesis to create an object instance from the class.
I built a small SwiftUI example with an #Observable class that has an multiple init() options. You can see how the init() parameters can change the properties of the object by calling the different initializers:
class MultipleInitOptions:ObservableObject {
#Published var name = "hard coded name"
init(){} // has no parameters, just use empty parenthesis
init(name:String) { // has parameters, pass in a name parameter inside parenthesis
self.name = name
}
}
struct TesterView: View {
#StateObject var hardCoded = MultipleInitOptions()
#StateObject var custom = MultipleInitOptions(name:"custom name")
var body: some View {
VStack {
Text("\(hardCoded.name)")
.padding()
Text("\(custom.name)")
.padding()
}
}
}

How do I use an existing property in a property wrapper when self hasn't been initialized? (SwiftUI)

I have a struct with two variables inside property wrappers. One of the variables is supposed to be computed from the other. When I try to do this, I get the following error:
Cannot use instance member 'name' within property initializer; property initializers run before 'self' is available.
I tried assigning a temporary value to these variables, and then re-assigning them within a custom init() function, but that doesn't seem to work ether. I made a simplified version of the code to see if I could isolate the issue.
import SwiftUI
struct Person {
#State var name: String = ""
#State var nameTag: NameTag = NameTag(words: "")
init(name: String) {
// not changing name and nameTag
self.name = name
nameTag = NameTag(words: "Hi, my name is \(name).")
}
}
class NameTag {
var words: String
init(words: String) {
self.words = words
}
}
var me = Person(name: "Myself")
// still set to initial values
me.name
me.nameTag.words
I noticed that when I changed nameTag to an #ObservedObject, rather than #State, it was able to be re-assigned correctly. Although I don't believe I can change name to #ObservedObject. Could anyone tell me what I'm doing wrong?
To use property wrappers in initializers, you use the variable names with preceding underscores.
And with State, you use init(initialValue:).
struct Person {
#State var name: String
#State var nameTag: NameTag
init(name: String) {
_name = .init(initialValue: name)
_nameTag = .init( initialValue: .init(words: name) )
}
}
Here's what a #State property really looks like, as your tear down levels of syntactic sugar:
name
_name.wrappedValue
$name.wrappedValue
_name.projectedValue.wrappedValue
You can't use the underscore-name outside of the initial type definition.

Changing a struct with one type to another type

I have two structs with the same fields. What is the best way to merge them.
struct Type1{
var variable1:String?
var variable2:Double?
var variable3:String?
var notImporant:String?
}
struct Type2{
var variable1A:String?
var variable2A:String?
var variable3A:String!
}
What is the best way to convert type2 to type1? I am getting a return from an API and parsing it using codable but there are two different structs and I need to get one struct. The data is the same, it is just mapped differently in terms of types. Some of the structs have more info and others have less.
Just make a copy constructor in both structs like so:
struct Type2 {
var variable1A:String?
var variable2A:String?
var variable3A:String!
init(_ otherType: Type1) {
variable1A = otherType.variable1
variable2A = otherType.variable2
variable3A = otherType.variable3
}
}
You cannot cast two unrelated structs. What you can do is define a common Protocol for the two of them, and use them in places where you don't care which underlying object it is.
protocol CommonProtocol {
var variable1: String? { get }
var variable3: String? { get }
}
struct Type1: CommonProtocol {
var variable1:String?
var variable2:Double?
var variable3:String?
var notImporant:String?
}
struct Type2: CommonProtocol {
var variable1A:String?
var variable2A:String?
var variable3A:String!
}
Then, in whichever place you're currently stuck with a type1 instead of a type2, have that function just accept a CommonProtocol instead, and you can use either.
Note that, while both of your types have a variable2, one of them is a Double? while the other is a String?. There are a few different ways to approach that, which I leave to you. I just left it out of the protocol.
On another note, it's Swift standard to capitalize the names of structs (Type1, Type2). In certain instances, you can run into problems if you don't, so I suggest you do.

Creating a global variable in 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