How Do You Pass A View Input Argument To #State Variable? - swift

I have a basic SwiftUI question - I have a view that takes argument, in my case "symbolName" for which I would like to fetch prices. I have a class function that does this, but passing the view argument to the FetchPrice as an argument does not work. When I use a fixed string, such as "GE", it works. I am sure there is a right way to do this, thanks for any hints and tips!
Error:
Cannot use instance member 'symbolNameV' within property initializer;
property initializers run before 'self' is available
import SwiftUI
struct SymbolRow2: View {
var symbolNameV: String
#ObservedObject var fetchPrice = FetchPrice(symbolName:symbolNameV)
...

property initializers run before 'self' is available
By calling
FetchPrice(symbolName: symbolNameV)
you're accessing self. The code above is actually:
FetchPrice(symbolName: self.symbolNameV)
To solve this you can create a custom init:
struct SymbolRow2: View {
private var symbolNameV: String
#ObservedObject private var fetchPrice: FetchPrice
init(symbolNameV: String) {
self.symbolNameV = symbolNameV
self.fetchPrice = FetchPrice(symbolName: symbolNameV)
}
...
}

Related

Cannot assign value of type 'Binding<Bool>' to type 'Bool'

I'm having trouble with initialising a Bool, it keeps giving me errors and I can't seem to find the solution. The error I'm getting with the below code is "Cannot assign value of type 'Binding' to type 'Bool'"
Any ideas?
struct ProfileView: View {
#ObservedObject var viewModel: ProfileViewModel
#Binding var isFollowed: Bool
init(user: User) {
self.viewModel = ProfileViewModel(user: user)
// error below
self.isFollowed = $isFollowed
// error above
}
I'm not clear what you want to do, but the $ notation is when you pass a property wrapper, such as #State or #Published to someone else.
For example if you want to initialize your #Binding property with some value passed on initialization:
First you need a corresponding argument in the init, and you initialize its value by using the "special" syntax with underscore:
init(...,
isFollowed: Binding<Bool>) {
// This is how binding is initialized
self._isFollowed = isFollowed
Now we assume that some other class or struct (lets call it Other), which has some sort of state or published property:
#Published var isProfileFollowed = false
So from that Other class/struct you can create an instance of ProfileView like this:
ProfileView(...,
isFollowed: $isProfileFollowed)
That is not just passing a current value of isProfileFollowed, but binding a isFollowed of ProfileView to isProfileFollowed of class/struct Other, so that any change in isProfileFollowed is also visible to a binded property isFollowed.
So this is just an explanation of what's not working.

SwiftUI - Use EnvironmentObject in ObservableObject Class/Dependency Injection

I am having trouble using an #EnvironmentObject in an #ObservableObject class. Based on some research, this is not possible, as EnvironmentObject is for views only.
I have resorted to doing the following, but the value is not updated dynamically.
For example, it is initialized with the value of "A", but when I change the value in a class that is using EnvironmentObject, the value found in my ObservableObject class remains "A". It updates in all other locations that are using the #EnvironmentObject, just not the ObservableObject API class.
Is there a way to have the code in the ObservableObject API class update when the EnvironmentObject updates the published variable?
The class that needs a variable in it that operates like EnvironmentObject is the API class.
class SelectedStation: ObservableObject {
#Published var selectedStation: String = "A"
}
class API: ObservableObject {
var selectedStation: SelectedStation
init(selectedStation: SelectedStation) {
self.selectedStation = selectedStation
print(selectedStation.selectedStation)
}
///some code that will utilize the selectedStation variable
}
What exactly am I doing wrong here?
You are initializing a different version of your class. Try adding public static let shared = SelectedStation() like this:
class SelectedStation: ObservableObject {
#Published var selectedStation: String = "A"
public static let shared = SelectedStation()
}
and then where you need to use it, declare it as:
var selectedStation = SelectedStation.shared
Also, you should rename the #Published var to something other than selectedStation, otherwise you could run into the unfortunate selectedStation.selectedStation as a reference to that variable.
Lastly, remember the #Environment needs to be initialized with the SelectedStation.shared so everything is sharing the one instantiation of the class.

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.

Observing a generic class

I am new to SwiftUI.
I have a button struct that contains the following property:
struct CircleTextButton: View {
#ObservedObject var controlModel:MyModelA
// bla bla
I would like to be able to reuse this button. So, I need this controlModel property to be able to assume other values, beyond MyModelA. For example, MyModelB
How should I declare this?
I have tried
#ObservedObject var controlModel:Any
but I get
Property type 'Any' does not match that of the 'wrappedValue' property of its wrapper type 'ObservedObject'
Any ideas?
You need to make your view generic over the type of controlModel and constraint Model to be ObservableObject.
struct CircleTextButton<Model: ObservableObject>: View {
#ObservedObject var controlModel: Model
...
To create a specific button with a specific model, you need to do
let button = CircleTextButton(controlModel: MyModelA())

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.