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

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.

Related

Swift binding to a computed property

Have the following situation. I have a view model that is an observable object with a computed property of type Bool. I want to be able to enable/disable a navigation link based on the computed property, but I need a binding to do so. Here a simplified example:
struct Project {
var name: String
var duration: Int
}
class MyViewModel: Observable Object {
#Published var project: Project
var isProjectValid: Bool {
return project.name != "" && project.duration > 0
}
}
struct MyView: View {
#EnvironmentObject var myVM: MyViewModel
var body: some View {
...
NavigationLink("Click Link", isActive: ?????, destination: NextView())
...
}
}
Since isActive expects a binding I am not able to access computed property such as myVM.isProjectValid. Tried also with the computed property in project class, still same problem.
Also considered creating custom binding related to the computed property, but not quite sure if/how to go about it.
First question I have posted, so if I am missing some details please be kind :)
Make it a #Published property and update it when project is changed
class MyViewModel: ObservableObject {
#Published var project: Project {
didSet {
isProjectValid = project.name != "" && project.duration > 0
}
}
#Published var isProjectValid: Bool
//...
}
The use of the computed property suggests some type of design where the user is not supposed to trigger the NavigationLink directly. But instead the NavigationLink is expected to be triggered programatically as a side-effect of some other mechanism elsewhere in the code. Such as might be done at the completion of a form or similar process by the user.
Not 100% if this is what's being aimed for, but if it is, then one option would be to pass a constant Binding to the NavigationLink, e.g.
NavigationLink("Click Link", isActive: .constant(myVM.isProjectValid), destination: NextView())`

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.

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

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

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

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