SwiftUI: how to properly use init()? - swift

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

Related

Manipulating binding variables in SwiftUI

Suppose I have a string that's binding:
#Binding var string: String
But, I want to manipulate that string, and then pass it to a child view.
struct ViewOne: View {
#State var string: String = "Hello"
var body: some View {
ViewTwo(string: string + " World") // this is invalid, as ViewTwo requires Binding<String>
}
}
struct ViewTwo: View {
#Binding var string: String
var body: some View {
Text(string)
}
}
How should I go about manipulating that string, such that it will update the UI when the state changes? Let's assume that I want to re-use ViewTwo for different components, and so I would want to manipulate the string before it is passed to the view.
A computed variable doesn't work, as it isn't Binding
private var fixedString: String {
return string + " World"
}
And I don't want to create a binding variable, because the setter makes no sense in this context
private var fixedString: Binding<String> {
Binding<String> (
get: {
string + " World"
}, set: {
// this function doesn't make sense here - how would it update the original variable?
}
)
}
Am I just using #State and #Binding wrong?
Just remove the #Binding inside ViewTwo:
struct ViewTwo: View {
var string: String /// no need for the `#Binding`!
var body: some View {
Text(string)
}
}
SwiftUI will update ViewTwo even if you don't have the #Binding (it basically re-renders the entire body whenever a #State or #Published var gets changed).
You only need Bindings when you need to update/set the original #State or #Published property. In that case you'd add something like the var fixedString: Binding<String> in your question.

Using an #Environment(\.managedObjectContext)—NSManagedObjectContext—in an init function

I have this view.
struct AView: View {
#Environment(\.managedObjectContext) private var viewContext
#State var timestamp: Date
#State private var obj: MyObject
init(date: Date)
{
// this is fine
_timestamp = State(initialValue: date)
// this gives an error
_obj = State(initialValue: MyObject(context: viewContext))
}
var body: some View {
...
}
}
I need to initialize the object obj, which must be initialized with an NSManagedObjectContext, inside of the init function. However, when I try to use the code above, I get this error
Variable 'self.obj' used before being initialized
How do I initalize a variable in the init function that depends on an NSManagedObjectContext?

#EnvironmentObject is passed after init is called [duplicate]

This question already has answers here:
SwiftUI - How to pass EnvironmentObject into View Model?
(7 answers)
Closed last year.
#EnviromentObject is passed down when the body is called, so it doesn't yet exist during the initialization phase of the View struct. Ok, that is clear. The question is, how do you solve the following problem?
struct MyCollectionScreen: View {
// Enviroment
#EnvironmentObject var viewContext: NSManagedObjectContext
// Internal dependencies
#ObservedObject private var provider: CoreDataProvider
init() {
provider = CoreDataProvider(viewContext: viewContext)
}
}
The previous doesn't compile and throws the error:
'self' used before all stored properties are initialized.
That is because I am trying to use the #EnviromentObject object before it is actually set.
For this, I am trying a couple of things but I am not super happy with any of them
1. Initialize my provider after the init() method
struct MyCollectionScreen: View {
// Enviroment
#EnvironmentObject var viewContext: NSManagedObjectContext
// Internal dependencies
#ObservedObject private var provider: CoreDataProvider
var body: some View {
}.onAppear {
loadProviders()
}
mutating func loadProviders() {
provider = CoreDataProvider(viewContext: viewContext)
}
}
but that, doesn't compile either and you get the following error within the onAppear block:
Cannot use mutating member on immutable value: 'self' is immutable
2. Pass the viewContext in the init method and forget about Environment objects:
This second solution works, but I don't like having to pass the viewContext to most of my views in the init methods, I kind of liked the Environment object idea.
struct MyCollectionScreen: View {
// Internal dependencies
#ObservedObject private var provider: CoreDataProvider
init(viewContext: NSManagedObjectContext) {
provider = CoreDataProvider(viewContext: viewContext)
}
}
You can make viewContext in CoreDataProvider an optional, and then set it onAppear
struct MyCollectionScreen: View {
#EnvironmentObject var viewContext: NSManagedObjectContext
#ObservedObject private var provider = CoreDataProvider()
var body: some View {
Text("Hello")
.onAppear {
provider.viewContext = viewContext
}
}
Downside is that you'll now have an optional.
I think that what I would do is to create CoreDataProvider at the same time viewContext is created, and pass it as an environment object as well.

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

SWIFT5: Initilize only selected variables in init method

Is there a way to use init method without initializing Binding parameters? If they are not mentioned, I get error of "Initialize all parameters". If they are initialized, then it gives another error.
How can I avoid initialising them?
struct DetailView: View {
#Binding var isPresented: Bool
#Binding var quest: QuestObject
#State var selectedOption : String = ""
init()
{
//self.isPresented = $isPresented //I would like to skip initializing this paremeter
//self.quest = $quest //I would like to skip initializing this paremeter
self.selectedOption = "Apples"
}