How to pass a value from an EnvironmentObject to a class instance in SwiftUI? - swift

I'm trying to assign the value from an EnvironmentObject called userSettings to a class instance called categoryData, I get an error when trying to assign the value to the class here ObserverCategory(userID: self.userSettings.id)
Error says:
Cannot use instance member 'userSettings' within property initializer; property initializers run before 'self' is available
Here's my code:
This is my class for the environment object:
//user settings
final class UserSettings: ObservableObject {
#Published var name : String = String()
#Published var id : String = "12345"
}
And next is the code where I'm trying to assign its values:
//user settings
#EnvironmentObject var userSettings: UserSettings
//instance of observer object
#ObservedObject var categoryData = ObserverCategory(userID: userSettings.id)
class ObserverCategory : ObservableObject {
let userID : String
init(userID: String) {
let db = Firestore.firestore().collection("users/\(userID)/categories") //
db.addSnapshotListener { (snap, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
for doc in snap!.documentChanges {
//code
}
}
}
}
Can somebody guide me to solve this error?
Thanks

Because the #EnvironmentObject and #ObservedObject are initializing at the same time. So you cant use one of them as an argument for another one.
You can make the ObservedObject more lazy. So you can associate it the EnvironmentObject when it's available. for example:
struct CategoryView: View {
//instance of observer object
#ObservedObject var categoryData: ObserverCategory
var body: some View { ,,, }
}
Then pass it like:
struct ContentView: View {
//user settings
#EnvironmentObject var userSettings: UserSettings
var body: some View {
CategoryView(categoryData: ObserverCategory(userID: userSettings.id))
}
}

Related

Update EnvironmentObject value in ViewModel and then reflect the update in a View

I have an environment object with the property auth in my root ContentView:
class User: ObservableObject {
#Published var auth = false
}
My goal is to update auth to true inside of a function in my AuthViewModel:
class AuthViewModel: ObservableObject {
var user: User = User()
func verifyCode(phoneNumber: String, secret: String) {
self.user.auth = true
}.task.resume()
}
}
And then in my AuthView, I want to print the change when the function verifyCode is called:
#EnvironmentObject var user: User
var body: some View {
print("AUTH SETTINGS -------->",user.auth)
return VStack() { ........
If your class User doesn´t contain any further logic it would be best to declare it as struct and either let it live inside your AuthViewModel or in the View as a #State var. You should have only one source of truth for your data.
As for the print question:
let _ = print(....)
should work.

How do I change the variable "url" which is inside of a class by using a struct? in swift

I'm new to swift and I cannot figure out how to change the url variable by inputting the Binding var url from the struct. I keep getting errors regardless of how I try it. Any help would v vvv appreciated
struct SearchView : View {
#State var showSearchView = true
#State var color = Color.black.opacity(0.7)
**#Binding var url: String**
#ObservedObject var Books = getData()
var body: some View{
if self.showSearchView
{
NavigationView{
List(Books.data) {i in
....}
class getData : ObservableObject{
#Published var data = [Book]()
**var url** = "https://www.googleapis.com/books/v1/volumes?q=harry+potter"
init() {....}
First of all if the current view owns the model object use #StateObject.
Second of all please name classes with starting uppercase and functions and variables with starting lowercase letter.
#StateObject var books = GetData()
...
class GetData : ObservableObject {
You don't need a Binding just address the property directly
books.url = "https://apple.com"
and delete
#Binding var url: String
And if you need to display the changed value immediately use a #Published property and bind the it directly
class GetData : ObservableObject {
#Published var url = "https://www.googleapis.com/books/v1/volumes?q=harry+potter"
...
struct SearchView : View {
#StateObject var books = GetData()
var body: some View {
VStack{
Text(books.url)
TextField("URL", text: $books.url)
}
}
}
Change the *var url** = "https://www.googleapis.com/books/v1/volumes?q=harry+potter" in the second view with: #State var url = "https://www.googleapis.com/books/v1/volumes?q=harry+potter" so it can be mutable

Data communication between 2 ObservableObjects

I have 2 independent ObservableObjects called ViewModel1 and ViewModel2.
ViewModel2 has an array of strings:
#Published var strings: [String] = [].
Whenever that array is modified i want ViewModel1 to be informed.
What's the recommended approach to achieve this?
Clearly, there are a number of potential solutions to this, like the aforementioned NotificationCenter and singleton ideas.
To me, this seems like a scenario where Combine would be rather useful:
import SwiftUI
import Combine
class ViewModel1 : ObservableObject {
var cancellable : AnyCancellable?
func connect(_ publisher: AnyPublisher<[String],Never>) {
cancellable = publisher.sink(receiveValue: { (newStrings) in
print(newStrings)
})
}
}
class ViewModel2 : ObservableObject {
#Published var strings: [String] = []
}
struct ContentView : View {
#ObservedObject private var vm1 = ViewModel1()
#ObservedObject private var vm2 = ViewModel2()
var body: some View {
VStack {
Button("add item") {
vm2.strings.append("\(UUID().uuidString)")
}
ChildView(connect: vm1.connect)
}.onAppear {
vm1.connect(vm2.$strings.eraseToAnyPublisher())
}
}
}
struct ChildView : View {
var connect : (AnyPublisher<[String],Never>) -> Void
#ObservedObject private var vm2 = ViewModel2()
var body: some View {
Button("Connect child publisher") {
connect(vm2.$strings.eraseToAnyPublisher())
vm2.strings = ["Other strings","From child view"]
}
}
}
To test this, first try pressing the "add item" button -- you'll see in the console that ViewModel1 receives the new values.
Then, try the Connect child publisher button -- now, the initial connection is cancelled and a new one is made to the child's iteration of ViewModel2.
In order for this scenario to work, you always have to have a reference to ViewModel1 and ViewModel2, or at the least, the connect method, as I demonstrated in ChildView. You could easily pass this via dependency injection or even through an EnvironmentObject
ViewModel1 could also be changed to instead of having 1 connection, having many by making cancellable a Set<AnyCancellable> and adding a connection each time if you needed a one->many scenario.
Using AnyPublisher decouples the idea of having a specific types for either side of the equation, so it would be just as easy to connect ViewModel4 to ViewModel1, etc.
I had same problem and I found this method working well, just using the idea of reference type and taking advantage of class like using shared one!
import SwiftUI
struct ContentView: View {
#StateObject var viewModel2: ViewModel2 = ViewModel2.shared
#State var index: Int = Int()
var body: some View {
Button("update strings array of ViewModel2") {
viewModel2.strings.append("Hello" + index.description)
index += 1
}
}
}
class ViewModel1: ObservableObject {
static let shared: ViewModel1 = ViewModel1()
#Published var onReceiveViewModel2: Bool = Bool() {
didSet {
print("strings array of ViewModel2 got an update!")
print("new update is:", ViewModel2.shared.strings)
}
}
}
class ViewModel2: ObservableObject {
static let shared: ViewModel2 = ViewModel2()
#Published var strings: [String] = [String]() {
didSet { ViewModel1.shared.onReceiveViewModel2.toggle() }
}
}

Passing parameter value to Observable object

I have an ObservableObject class used to fetch data from an api. It takes one parameter which is the api key. I am trying to pass that key from a parameter of my ContentView to the object.
class UnsplashAPI: ObservableObject {
//some code
var clientId: String
init(clientId: String) {
self.clientId = clientId
}
//some more code
}
This works fine when I'm asking for a parameter in my struct
struct ContentView: View {
#ObservedObject var api = UnsplashAPI(clientId: "APIKEY")
var body: some View {
//View
}
}
However this doesn't:
struct ContentView: View {
var clientId: String
#ObservedObject var api = UnsplashAPI(clientId: self.clientId) // Cannot find 'self' in scope
init(clientId: String){
self.clientId = clientId
}
var body: some View {
//View
}
}
I think i'm initialising the struct wrong as I am getting the error "Cannot find 'self' in scope"
Initialize it inside init
struct ContentView: View {
var clientId: String
#ObservedObject var api: UnsplashAPI
init(clientId: String){
self.clientId = clientId
self.api = UnsplashAPI(clientId: clientId) // << here !!
}
// ...

Can i pass a Bool as an environment object to subViews in SwiftUI?

I have a bool
#State var isDragging: Bool
How can I pass this as an environment object to subViews?
You need to create an ObservableObject:
class Model: ObservableObject {
#Published var isDragging: Bool = false
}
And then use:
struct MyView: View {
#EnvironmentObject var mymodel: Model
var body : some View {
if mymodel.isDragging { ... }
}
}
And also, you should watch to WWDC 2019 session "Data Flow in Swift". Although some of the type names have been changed since, the concepts remain the same.