How do I change the variable "url" which is inside of a class by using a struct? in swift - 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

Related

SwiftUI: how to properly use init()?

I am trying to make use of init to call the fetchProducts function in my ViewModel class. When I add init though, I am getting the following 2 errors:
Variable 'self.countries' used before being initialized
and
Return from initializer without initializing all stored properties
The variable countries is binding though so there shouldn't need to be an initialized value in this view. Am I using init incorrectly?
struct ContentView: View {
#Namespace var namespace;
#Binding var countries: [Country];
#Binding var favLists: [Int];
#State var searchText: String = "";
#AppStorage("numTimeUsed") var numTimeUsed = 0;
#Environment(\.requestReview) var requestReview
#StateObject var viewModel = ViewModel();
init() {
viewModel.fetchProducts()
}
var body: some View {
}
}
Look at the initialiser that autocomplete gives you when you use ContentView…
ContentView(countries: Binding<[Country]>, favLists: Binding<[Int]>)
If you're creating your own initialiser, it will need to take those same parameters, e.g.
init(countries: Binding<[Country]>, favLists: Binding<[Int]>) {
_countries = countries
_favLists = favLists
viewModel.fetchProducts()
}
Alternatively, use the default initialiser, and instead…
onAppear {
viewModel.fetchProducts()
}

SwiftUI how to use enviromentObject in viewModel init?

I have a base API:
class API: ObservableObject {
#Published private(set) var isAccessTokenValid = false
#AppStorage("AccessToken") var accessToken: String = ""
#AppStorage("RefreshToken") var refreshToken: String = ""
func request1() {}
func request2() {}
}
And it was passed to all views by using .environmentObject(API()). So in any views can easily access the API to do the http request calls.
Also I have a view model to fetch some data on the view appears:
class ViewModel: ObservableObject {
#Published var data: [SomeResponseType]
init() {
// do the request and then init data using the response
}
}
struct ViewA: View {
#StateObject private var model = ViewModel()
var body: View {
VStack {
model.data...
}
}
}
But in the init(), the API is not accessable in the ViewModel.
So, to solve this problem, I found 3 solutions:
Solution 1: Change API to Singleton:
class API: ObservableObject {
static let shared = API()
...
}
Also we should change the enviromentObject(API()) to enviromentObject(API.shared).
So in the ViewModel, it can use API.shared directly.
Solution 2: Call the request on the onAppear/task
struct ViewA: View {
#EnvironmentObject var api: API
#State private var data: [SomeResponseType] = []
var body: View {
VStack {}
.task {
let r = try? await api.request1()
if let d = r {
data = d
}
}
}
}
Solution 3: Setup the API to the ViewModel onAppear/task
class ViewModel: ObservableObject {
#Published var data: [SomeResponseType]
var api: API?
setup(api: API) { self.api = api }
requestCall() { self.api?.reqeust1() }
}
struct ViewA: View {
#EnvironmentObject var api: API
#StateObject private var model = ViewModel()
var body: View {
VStack {}
.onAppear {
model.setup(api)
model.requestCall()
}
}
}
Even though, I still think they are not a SwiftUI way. And my questions is a little XY problem. Probably, the root question is how to refactor my API. But I am new to SwiftUI.
Solution 2 is best. You can also try/catch the exception and set an #State for an error message.
Try to avoid using UIKit style view model objects because in SwiftUI the View data struct plus #State already fulfils that role. You only need #StateObject when you need a reference type for view data which is not very often given now we have .task.

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.

Changing a property of my ObservableObject instance doesn't update View. Code:

//in ContentView.swift:
struct ContentView: View {
#EnvironmentObject var app_state_instance : AppState //this is set to a default of false.
var body: some View {
if self.app_state_instance.complete == true {
Image("AppIcon")
}}
public class AppState : ObservableObject {
#Published var inProgress: Bool = false
#Published var complete : Bool = false
public static var app_state_instance: AppState? //I init this instance in SceneDelegate & reference it throughout the app via AppState.app_state_instance
...
}
in AnotherFile.swift:
AppState.app_state_instance?.complete = true
in SceneDelegate.swift:
app_state_instance = AppState( UserDefaults.standard.string(forKey: "username"), UserDefaults.standard.string(forKey: "password") )
AppState.app_state_instance = app_state_instance
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(app_state_instance!))
This ^ does not trigger an update of my view.Anyone know why?
Also, is it possible to make a static var # published?
Also, I'm kind somewhat new to class-based + structs-as-values paradigm, if you have any tips on improving this please do share.
I've considered using an 'inout' ContentView struct reference to update struct state from other files (as going through app_state_instance isn't working with this code).

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

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