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

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()
}
}
}

Related

Why does calling a method via a pointer end up with a different result than calling via the method directly?

In the example following, I'm calling an instance method of a View using the method directly (saveTitle), and via a pointer to that method (saveAction).
When calling the method, I am passing in the current value of the title variable.
When I call it directly, the current value matches the value inside the method.
When I call it via a pointer to the method, the value inside is the value it was when the struct was first instantiated.
It is almost like there is a new instance of the Struct being created, but without the init method being called a second time.
I suspect this has something to do with how SwiftUI handles #State modifiers, but I'm hoping someone out there will have a better understanding, and be able to enlighten me a bit.
Thanks much for your consideration :)
import SwiftUI
struct EditContentView: View {
#Binding var isPresented: Bool
#State var title: String
var saveAction: ((String) -> Void)?
var body: some View {
TextField("new title", text: $title)
Button("save") {
print("calling saveText")
// this call works as expected, the title inside the saveTitle method is the same as here
saveTitle(title)
print("now calling saveAction, a pointer to saveTitle")
// with this call the title inside the method is different than the passed in title here
// even though they are theoretically the same member of the same instance
saveAction!(title)
isPresented = false
}
}
init(isPresented: Binding<Bool>, title: String) {
print("this method only gets called once")
self._isPresented = isPresented
self._title = State(initialValue: title)
saveAction = saveTitle
}
func saveTitle(_ expected: String) {
if (expected == title) {
print("the title inside this method is the same as before the call")
}
else {
print("expected: \(expected), but got: \(title)")
}
}
}
struct ContentView: View {
#State var title = "Change me"
#State var showingEdit = false
var body: some View {
Text(title)
.onTapGesture { showingEdit.toggle() }
.sheet(isPresented: $showingEdit) {
EditContentView(isPresented: $showingEdit, title: title)
}
}
}
I don't think this is related to #State. It is just a natural consequence of structs having value semantics, i.e.
struct Foo {
init() {
print("This is run only once!")
}
var foo = 1
}
var x = Foo() // Prints: This is run only once!
let y = x // x and y are now two copies of the same *value*
x.foo = 2 // changing one of the copies doesn't affect the other
print(y.foo) // Prints: 1
Your example is essentially just a little more complicated version of the above. If you understand the above, then you can easily understand your SwiftUI case, We can actually simplify your example to one without all the SwiftUI distractions:
struct Foo {
var foo = 1
var checkFooAction: ((Int) -> Void)?
func run() {
checkFoo(expectedFoo: foo)
checkFooAction!(foo)
}
init() {
print("This is run only once!")
checkFooAction = checkFoo
}
func checkFoo(expectedFoo: Int) {
if expectedFoo == foo {
print("foo is expected")
} else {
print("expected: \(expectedFoo), actual: \(foo)")
}
}
}
var x = Foo()
x.foo = 2 // simulate changing the text in the text field
x.run()
/*
Output:
This is run only once!
foo is expected
expected: 2, actual: 1
*/
What happens is that when you do checkFooAction = checkFoo (or in your case, saveAction = saveTitle), the closure captures self. This is like the line let y = x in the first simple example.
Since this is value semantics, it captures a copy. Then the line x.foo = 2 (or in your case, the SwiftUI framework) changes the other copy that the closure didn't capture.
And finally, when you inspect what foo (or title) by calling the closure, you see the unchanged copy, analogous to inspecting y.foo in the first simple example.
If you change Foo to a class, which has reference semantics, you can see the behaviour change. Because this time, the reference to self is captured.
See also: Value and Reference Types
Now you might be wondering, why does saveAction = saveTitle capture self? Well, notice that saveTitle is an instance method, so it requires an instance of EditContentView to call, but in your function type, (String) -> Void, there is no EditContentView at all! This is why it "captures" (a copy of) the current value of self, and says "I'll just always use that".
You can make it not capture self by including EditContentView as one of the parameters:
// this doesn't actually need to be optional
var saveAction: (EditContentView, String) -> Void
assign it like this:
saveAction = { this, title in this.saveTitle(title) }
then provide self when calling it:
saveAction(self, title)
Now you won't get different copies of self flying around.

SwiftUI: What is the difference between var and let as a parameter in List's Row?

What is the difference between var and let as a parameter in List's Row?
Usually, if I don't change the landmark variable, the compiler will warn me, but there is no warning in that row. I wonder why.
struct LandmarkRow: View {
var landmark: Landmark
var body: some View {
Text(landmark.id)
}
}
struct LandmarkRow: View {
let landmark: Landmark
var body: some View {
Text(landmark.id)
}
}
This looks like a same result:
struct LandmarkList: View {
var body: some View {
List(landmarks, id: \.id) { landmark in
LandmarkRow(landmark: landmark)
}
}
}
var is a variable meaning it is mutable. You can reassign it a value as many time as you wish.
In LandmarkRow and LandmarkList, var body is a computed property that calculates (rather than stores) a value. And it's read-only. Computed properties can only be declared using var.
When you implement a custom view, you must implement a computed
body property to provide the content for your view. Return a view
that's composed of primitive views that SwiftUI provides, plus other composite views that you've already defined. https://developer.apple.com/documentation/swiftui/view/body-swift.property
let is used to declare a constant. You can assign it a value exactly once as you have done in LandmarkList. In other words, you can not reassign it a value.
List(landmarks, id: \.id) { landmark in
LandmarkRow(landmark: landmark)
}
Example
struct Example {
// variable that you can change
var name: String
// constant that can only be assigned a value once.
let fileExtension: String
// A computed property that is read-only
var filename: String {
return name + "." + fileExtension
}
}
var example = Example(name: "example", fileExtension: "swift")
You can change name from example to example_2 since it is a variable.
example.name = "example_2"
You can not change fileExtension from swift to js because fileExtension is a constant (let). If you do so, you will get the following error.
Cannot assign to property: 'fileExtension' is a 'let' constant
example.fileExtension = "js"
You can not change fileName because it is a read-only property. If you try to change, you will get this error.
Cannot assign to property: 'filename' is a get-only property
example.filename = "ex.js"
More info
What is the difference between `let` and `var` in swift?
https://docs.swift.org/swift-book/LanguageGuide/Properties.html
https://www.avanderlee.com/swift/computed-property/
The compiler warns you about local variables which are never been modified or being unused.
But you get never a warning about declared struct members / class properties. And a SwiftUI view is actually a regular struct.
However you get an error if you are going to modify a struct member / class property which is declared as constant.
In a SwiftUI View there is no difference except for semantics.
A struct is immutable therefore having a var vs a let is irrelevant.
let Is semantically correct
I am also new to SwiftUI. I only have backgound in C and C++. I am guessing that it has to do with the fact that you declared landmark but didn't initialized it (or in this case, default value). Here, it assumes that you will initialize landmark when you initialize a LandmarkRow. Getting back to the point, I think the compiler doesn't know if landmark changes or not untill it is run.
var => Variable
By defining var you inform the compiler that this variable will be changed in further execution of code.
var current_day = 6
let => Constant
By defining let you inform the compiler that this is a constant variable and its value stays the same.
Like
let earth_gravity = 9.8
Its just best practise to make unchanging variables constant.
There will be no difference in execution of output.

Accessing a lazy property on a struct mutates the struct

I have a lazy property in a struct and every time I access it, it mutates the struct.
var numbers = [1,2,3]
struct MyStruct {
lazy var items = numbers
}
class MyClass {
var myStructPropery: MyStruct = MyStruct() {
didSet {
print(myStructPropery)
}
}
}
var myClass = MyClass()
myClass.myStructPropery.items
myClass.myStructPropery.items
myClass.myStructPropery.items
Result:
Print (didSet) will be called every time.
The ideal behaviour should be first time mutation only (since that's how lazy variables behave). Is this a bug in Swift or am I doing something wrong?
What you are seeing is is that the observed property items has notified its observer that it has been mutated because it was accessed, and a lazy variable is by definition mutating since it will be set at a later time. Therefore didSet gets called to handle this.
The lazy variable is actually only set once even though it signals that it has mutated and the property myStructPropery is only mutated once when the variable is first set but is the same instance after that.
Here is how we can verify this, first change the lazy var declaration so it's more like how we usually declare such a variable
lazy var items: [Int] = { numbers }()
and then add a print statement
lazy var items: [Int] = {
print("inside lazy")
return numbers
}()
If we now run the test code
var myClass = MyClass()
myClass.myStructProperty.items
myClass.myStructProperty.items
myClass.myStructProperty.items
we see that "inside lazy" only prints once. To verify that the property myStructProperty isn't changed we can make the struct conform to Equatable and perform a simple check inside didSet
didSet {
if oldValue != myStructProperty {
print(myStructProperty)
}
}
Now running the test we see that the print inside didSet is never executed so myStructProperty is never changed.
I have no idea if this behaviour is a bug but personally it feels like it might be complicate for the property observer to stop observing a lazy property once it was accessed or for a lazy var to not be defined as mutating once it is set.
I started debugging this with following set up -
var numbers = [1,2,3]
struct MyStruct {
lazy var items = numbers
}
class MyClass {
var myStructPropery: MyStruct = MyStruct() {
didSet {
// Changed this to make sure we are not invoking getter here
print("myStructPropery setter called")
}
}
}
let myClass = MyClass()
myClass.myStructPropery.items
myClass.myStructPropery.items
myClass.myStructPropery.items
I can reproduce the problem on Xcode 12.5 using Swift 5.4.
Attempt 1 : Turn var numbers into let numbers - Does NOT work.
let numbers = [1,2,3]
Attempt 2 : Assign the value inline without using an extra variable - Does NOT work.
struct MyStruct {
lazy var items = [1,2,3]
}
Attempt 3 : Assign the value inline using the full blown getter syntax - Does NOT work.
struct MyStruct {
lazy var items: [Int] = {
return [1,2,3]
}()
}
At this point, we are out of options to try. Even though we can clearly see that return [1,2,3] in the last attempt is executed exactly once, the MyClass.myStructPropery.modify is called repeatedly on access to items.
Maybe Swift Forums is a better place to discuss this.

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.

Swift computed property to return copy of underlying array

I have a model class written in Objective-C that I'm converting to Swift. It contains an NSMutableArray internally, but the method signature for the getter, as well as the actual return value, are NSArray. When called, it creates an immutable copy to return.
Essentially, I want callers to be able to iterate/inspect the container, but not modify it. I have this test snippet:
class Container {
internal var myItems = [String]()
func sayHello() {
"I have: \(myItems)"
}
}
let cont = Container()
cont.myItems.append("Neat") // ["Neat"]
cont.sayHello() // This causes sayHello() to print: "I have: [Neat]"
var isThisACopy = cont.myItems
isThisACopy.append("Huh") // ["Neat", "Huh"]
cont.sayHello() // This ALSO causes sayHello() to print: "I have: [Neat]"
I've been trying to find a way to override the getter for myItems so that it returns an immutable copy, but can't seem to determine how.
Attempt #1
This produces a compiler error: Function produces expected type '_ArrayBuffer<(String)>'; did you mean to call it with '()'?
internal var myItems = [String]() {
var copy = [String]()
for item in ... { // What to use in the ...?
copy.append(item)
}
return copy
}
Attempt #2
This also produces a compiler error, because I'm (understandably) redefining the generated getter Invalid redeclaration of 'myItems()':
internal func myItems() -> [String] {
var copy = [String]()
for item in myItems {
copy.append(item)
}
return copy
}
Try this:
class Container {
private var _myItems: [String] = ["hello"]
internal var myItems: [String] {
return _myItems
}
}
let cont = Container()
cont.myItems.append("Neat") //not allowed
It uses a private stored property and a computed property that returns an immutable copy. It's not possible for a stored property to use custom getters.
A better way to expose mutable properties as immutable:
class Container {
private (set) internal var myItems: [String]
}