This question already has answers here:
What does the dollar sign do in Swift / SwiftUI?
(2 answers)
Closed 2 years ago.
It was confuse me what the difference between with or without $ symbol
class UserData: ObservableObject {
#Published var name: String = "Light one"
#Published var lightOn: Bool = true
}
struct ContentView: View {
#EnvironmentObject var userData: UserData
var body: some View {
Toggle(isOn: $userData.lightOn, label: {
Text(userData.name)
}).padding()
}
}
if Toggle(isOn: $userData.lightOn,... without $ it's error. I don't why?
if Text(userData.name) with $ it's also error. confuse me...
By using $ you are accessing the binding. So that the view can communicate back with the view you are parsing with.
Related
This question already has answers here:
How to define a protocol as a type for a #ObservedObject property?
(6 answers)
Closed 7 months ago.
I created a protocol JSONService that conforms to the ObservableObject.
protocol JSONService {
var screenModel: ScreenModel? { get set }
func load(_ resourceName: String) async throws
}
Next, I created the LocalService which conforms to the ObservableObject as shown below:
class LocalService: JSONService, ObservableObject {
#Published var screenModel: ScreenModel?
func load(_ resourceName: String) async throws {
// some code
}
}
Now, when I created a property in my View (SwiftUI) then I get an error:
struct ContentView: View {
#StateObject private var jsonService: any JSONService
Type 'any JSONService' cannot conform to 'ObservableObject'
How can I use #StateObject with protocols?
You just add the conformance to the protocol.
protocol JSONService: ObservableObject, AnyObject {
var screenModel: ScreenModel? { get set }
func load(_ resourceName: String) async throws
}
Then you can add the generic to the View
struct GenericOOView<JS: JSONService>: View {
#StateObject private var jsonService: JS
init(jsonService: JS){
_jsonService = StateObject(wrappedValue: jsonService)
}
var body: some View {
Text("Hello, World!")
}
}
In many cases in SwiftUI, values are marked with a $ to indicate that they’re a Binding variable, and allow them to update dynamically. Here’s an example of this behavior:
class Car: ObservableObject {
#Published var isReadyForSale = true
}
struct SaleView: View {
#Binding var isOn: Bool
var body: some View {
Toggle("Ready for Sale", isOn: $isOn)
}
}
struct ContentView: View {
#ObservedObject var car: Car
var body: some View {
Text("Details")
.font(.headline)
SaleView(isOn: $car.isReadyForSale) // generates a Binding to 'isReadyForSale' property
}
}
The $ is used twice to allow the Toggle to change whether the car is ready for sale, and for the car’s status to update the Toggle.
However, some values seem to update without the $. For instance, in this tutorial about different property wrappers, they show the following example:
class TestObject: ObservableObject {
#Published var num: Int = 0
}
struct ContentView: View {
#StateObject var stateObject = TestObject()
var body: some View {
VStack {
Text("State object: \(stateObject.num)")
Button("Increase state object", action: {
stateObject.num += 1
print("State object: \(stateObject.num)")
})
}
.onChange(of: stateObject.num) { newStateObject in
print("State: \(newStateObject)")
}
}
}
Why does it use Text("State object: \(stateObject.num)") and not Text("State object: \($stateObject.num)") with a dollar sign prefix? It was my understanding when you wanted a view to automatically update when a variable it uses changes, you prefix it with a dollar sign. Is that wrong?
I'm downloading data from Firebase and trying to edit it. It works, but with an issue. I am currently passing data to my EditViewModel with the .onAppear() method of my view. And reading the data from EditViewModel within my view.
class EditViewModel: ObservableObject {
#Published var title: String = ""
}
struct EditView: View {
#State var selected_item: ItemModel
#StateObject var editViewModel = EditViewModel()
var body: some View {
VStack {
TextField("Name of item", text: self.$editViewModel.title)
Divider()
}.onAppear {
DispatchQueue.main.async {
editViewModel.title = selected_item.title
}
}
}
}
I have given you the extremely short-hand version as it's much easier to follow.
However, I push to another view to select options from a list and pop back. As a result, everything is reset due to using the onAppear method. I have spent hours trying to use init() but I am struggling to get my application to even compile, getting errors in the process. I understand it's due to using the .onAppear method, but how can I use init() for this particular view/view-model?
I've search online but I've found the answers to not be useful, or different from what I wish to achieve.
Thank you.
You don't need to use State for input property - it is only for internal view usage. So as far as I understood your scenario, here is a possible solution:
struct EditView: View {
private var selected_item: ItemModel
#StateObject var editViewModel = EditViewModel()
init(selectedItem: ItemModel) {
selected_item = selectedItem
editViewModel.title = selectedItem.title
}
var body: some View {
VStack {
TextField("Name of item", text: self.$editViewModel.title)
Divider()
}.onAppear {
DispatchQueue.main.async {
editViewModel.title = selected_item.title
}
}
}
}
This question already has an answer here:
iOS14 introducing errors with #State bindings
(1 answer)
Closed 2 years ago.
This code has the following issue: The first click on any list item will show the sheet with "This is sheet 0" - meaning that #State property "var example: Int" is not updated as part of the button code (exit live preview and start it again, will reproduce the issue).
Any idea what is happening here !?
Code:
import SwiftUI
struct testing2: View {
#State private var showSheet = false
#State var example: Int = 0
var computedItem: String {
"This is sheet \(example)"
}
var body: some View {
List(0 ..< 5) { item in
Button("List item: \(item)") {
self.example = item
showSheet.toggle()
}
}
.sheet(isPresented: $showSheet, content: {
Text("\(computedItem)")
})
}
}
Not sure whether this would be the best answer, but this worked for me:
struct ContentView: View {
#State private var showSheet = false
#State var example: Int = 0
#State var computedItem: String = ""
var body: some View {
List(0 ..< 5) { item in
Button("List item: \(item)") {
self.example = item
self.computedItem = "This is sheet " + String(self.example)
showSheet.toggle()
}
}
.sheet(isPresented: $showSheet) {
sheetView(computedItem: $computedItem)
}
}
}
struct sheetView: View {
#Binding var computedItem: String
var body: some View {
Text(self.computedItem)
}
}
I'm having a problem trying to get textfields working in SwiftUI.
I get Fatal error: Accessing State> outside View.body whenever I try to run the following code.
Anyone have a suggestion?
struct SearchRoot : View {
#State var text: String = ""
var body: some View {
HStack {
TextField($text,
placeholder: Text("type something here..."))
Button(action: {
// Closure will be called once user taps your button
print(self.$text)
}) {
Text("SEND")
}
}
}
}
I'm running Xcode Version 11.0 beta (11M336w) on macOS 10.15 Beta (19A471t)
Edit: Simplified code, still getting the same error.
struct SearchRoot : View {
#State var text: String = ""
var body: some View {
TextField($text,
placeholder: Text("type something here..."))
}
}
The compiler emits an error if the $ operator is used outside body, in a View.
The button initializer is defined as:
init(action: #escaping () -> Void, #ViewBuilder label: () -> Label)
You're using $ in an escaping closure, in the first snippet of code.
That means the action may outlive (escape) the body, hence the error.
The second snippet compiles and works fine for me.
Eureka! SwiftUI wants a single source of truth.
What I neglected to include in my original code snippets is that this struct is within a tabbed application.
To fix this I needed to define the #State var text: String = "" in the struct that creates the top level TabbedView, then use $Binding in the SearchRoot.
I'm not sure if this is works as designed or just a beta 1 issue, but it's the way it works for now.
struct ContentView : View {
#State private var selection = 0
#State private var text: String = "searching ex"
var body: some View {
TabbedView(selection: $selection){
ShoppingListRoot().body.tabItemLabel(Text("Cart")).tag(0)
SearchRoot(text: $text).body.tabItemLabel(Text("Search")).tag(1)
StoreRoot().body.tabItemLabel(Text("Store")).tag(2)
BudgetRoot().body
.tabItemLabel(Text("Budget"))
.tag(3)
SettingsRoot().body
.tabItemLabel(Text("Settings"))
.tag(4)
}
}
}