Pass Variable from Struct to Class in SwiftUI - swift

i Have a Structure and have some variable in it
struct Home: View {
var token: String
var loginapiurl: String
var companyname: String
#StateObject var dataservice = StatAPI()
i have to pass this token and loginapiurl in the class
class StatAPI: ObservableObject {
#Published var statsdetails = [Result]()
init()
{
print("in init")
let currenDate = getCurrentDate()
let past7DaysBeforeDate = past7dayDate(date1: currenDate)
getStats(dateFrom: past7DaysBeforeDate, dateTo: currenDate)
}
i want to use that two variable in init and passed in to getStats function
I am new to Swiftui. Can anyone help me about this

Related

Class with an Observable Object has been used by is not initializing

I've added an Observable Object to my class DetailViewModel, but I am getting an error "Class 'DetailViewModel' has no initializers". Can anyone explain why?
import Foundation
import UIKit
#MainActor
class DetailViewModel: ObservableObject{
#Published var strMeal: String = ""
#Published var strInstructions: String
#Published var strIngredient: String
#Published var strMeasure: String
#Published var strMealThumb:URL?
private func loadMealDetails(idMeal: String) async {
do {
let mealDetailResponse = try await WebServiceRequest().loadData(url: Constants.Urls.getMealByIdUrl(strMeal)) { data in
return try? JSONDecoder().decode(MealDetailModel.self, from:data )
}
} catch {
print(error)
}
}
You have defined some properties (strInstructions, strIngredient, and strMeasure) that don't have initial values specified. Unlike structs, which get synthesized initializers (eg the compiler makes a initializer for us), with a class, we have to create an initializer ourselves (or give all of the properties default values).
With default values, it may look like:
#MainActor
class DetailViewModel: ObservableObject{
#Published var strMeal: String = ""
#Published var strInstructions: String = ""
#Published var strIngredient: String = ""
#Published var strMeasure: String = ""
#Published var strMealThumb:URL?
}
Or, with an initializer, it could be something like:
#MainActor
class DetailViewModel: ObservableObject{
#Published var strMeal: String = ""
#Published var strInstructions: String
#Published var strIngredient: String
#Published var strMeasure: String
#Published var strMealThumb:URL?
init(strInstructions: String, strIngredient: String, strMeasure: String) {
self.strInstructions = strInstructions
self.strIngredient = strIngredient
self.strMeasure = strMeasure
}
}
You may also want to accept values for strMeal and strMealThumb in your initializer -- it's up to you.

How to pass Binding variable to an ObservableObject?

I have a binding variable that I need to pass to an ObservableObject in swiftUI.
Let's say I have this code:
struct MYView: View {
#ObservedObject var fetch = Fetch()
#Binding var IDTxt: Int
var body: some View{
//some code here
}
}
Now I want to pass the IDTxt value to this:
public class Fetch: ObservableObject {
//I need to pass the Binding variable here. something like this?
#Binding var IDTxt: Int
init(){
load()
}
func load() {
//So I can use it here like this:
let url = URL(string: "http://someurl.com/\(IDTxt)")
}
Could someone please advice on the above?
You do not need to pass the Binding value. But you can pass direct value.
public class Fetch: ObservableObject {
var IDTxt: Int
init(id: Int){
self.IDTxt = id
load()
}
func load() {
//So I can use it here like this:
let url = URL(string: "http://someurl.com/\(IDTxt)")
}
}
struct MYView: View {
#ObservedObject var fetch: Fetch
#Binding var IDTxt: Int
init(IDTxt: Binding<Int>) {
self._IDTxt = IDTxt
self.fetch = Fetch(id: IDTxt.wrappedValue)
}
var body: some View{
//some code here
Color.red
}
}
If you want to observe IDTxt text then use #Published in class.
public class Fetch: ObservableObject {
#Published var IDTxt: Int
There's no need for Bindings if the property you are trying to inject into Fetch is coming from a parent view. You can simply inject the value in the init.
Also, if you are creating an ObservableObject inside a View, you need to declare it as #StateObject. #ObservedObject should only be used when injecting the object into the view.
public class Fetch: ObservableObject {
init(id: Int) {
load(id: id)
}
func load(id: Int) {
let url = URL(string: "http://someurl.com/\(id)")
}
struct MYView: View {
#StateObject private var fetch: Fetch
init(id: Int) {
self._fetch = StateObject(wrappedValue: Fetch(id: id))
}
var body: some View{
EmptyView()
}
}

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 !!
}
// ...

How to connect published properties of model and viewmodel in Swift?

Let's assume a model, which implements the protocol ObservableObject and has got a #Published property name.
// MARK: Model
class ContentSinglePropertyModel: ObservableObject {
#Published public var name: String
}
Now, I would like to display that name in a view and update the view, whenever name in the model changes. Additionally, I would like to use the Model-View-ViewModel (MVVM) pattern to achieve this goal.
// MARK: ViewModel
final class ContentSinglePropertyViewModel: ObservableObject {
private let model: ContentSinglePropertyModel
#Published var name: String = ""
init() {
self.model = ContentSinglePropertyModel()
}
}
// MARK: View
struct ContentSinglePropertyView: View {
#ObservedObject var viewModel: ContentSinglePropertyViewModel
var body: some View {
Text(self.viewModel.name)
}
}
Since I don't like the idea to make the model or it's properties public within the viewmodel, one option is to wrap the model's property name in the viewmodel. My question is: How to connect the name of the model and the viewmodel in the most idiomatic way?
I've came up with the solution to update the viewmodel's property through the use of Combine's assign method:
self.model.$name.assign(to: \.name, on: self).store(in: &self.cancellables)
Is there a better solution?
My working example:
import SwiftUI
import Combine
// MARK: Model
class ContentSinglePropertyModel: ObservableObject {
#Published public var name: String
init() {
self.name = "Initial value"
}
func doSomething() {
self.name = "Changed value"
}
}
// MARK: ViewModel
final class ContentSinglePropertyViewModel: ObservableObject {
private let model: ContentSinglePropertyModel
private var cancellables: Set<AnyCancellable> = []
#Published var name: String = ""
init() {
self.model = ContentSinglePropertyModel()
// glue Model and ViewModel
self.model.$name.assign(to: \.name, on: self).store(in: &self.cancellables)
}
func doSomething() {
self.model.doSomething()
}
}
// MARK: View
struct ContentSinglePropertyView: View {
#ObservedObject var viewModel: ContentSinglePropertyViewModel
var body: some View {
VStack {
Text(self.viewModel.name)
Button("Do something!", action: {
self.viewModel.doSomething()
})
}
}
}
struct ContentSinglePropertyView_Previews: PreviewProvider {
static var previews: some View {
ContentSinglePropertyView(viewModel: .init())
}
}

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