'self' used before all stored properties are initialized SwiftUI - swiftui

This is my code. Do you know what is this error? Thank you!
struct PlaceholderImageView: View {
#ObservedObject var imageModel: PlaceholderImageViewModel
#Environment(\.MyImageCacheValue) var cache: MyImageCache
init(urlString: String) {
imageModel = PlaceholderImageViewModel(urlString: urlString, cache: cache) // error: 'self' used before all stored properties are initialized
}
var body: some View {
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
}
}

You are trying to initialize PlaceholderImageViewModel before MyImageCache is initialized. One way to resolve this is to create PlaceholderImageViewModel in the caller view of PlaceholderImageView and just pass the model directly.
struct PlaceholderImageViewSuperView: View {
#Environment(\.MyImageCacheValue) var cache: MyImageCache
var body: some View {
NavigationLink("Go to image view", destination: PlaceholderImageView(imageModel: PlaceholderImageViewModel(urlString: urlString, cache: cache)))
}
}
struct PlaceholderImageView: View {
#ObservedObject var imageModel: PlaceholderImageViewModel
var body: some View {
Text("Any text here")
}
}

Related

SwiftUI - Binding in ObservableObject

Let's say we have a parent view like:
struct ParentView: View {
#State var text: String = ""
var body: some View {
ChildView(text: $text)
}
}
Child view like:
struct ChildView: View {
#ObservedObject var childViewModel: ChildViewModel
init(text: Binding<String>) {
self.childViewModel = ChildViewModel(text: text)
}
var body: some View {
...
}
}
And a view model for the child view:
class ChildViewModel: ObservableObject {
#Published var value = false
#Binding var text: String
init(text: Binding<String>) {
self._text = text
}
...
}
Making changes on the String binding inside the child's view model makes the ChildView re-draw causing the viewModel to recreate itself and hence reset the #Published parameter to its default value. What is the best way to handle this in your opinion?
Cheers!
The best way is to use a custom struct as a single source of truth, and pass a binding into child views, e.g.
struct ChildViewConfig {
var value = false
var text: String = ""
// mutating funcs for logic
mutating func reset() {
text = ""
}
}
struct ParentView: View {
#State var config = ChildViewConfig()
var body: some View {
ChildView(config: $config)
}
}
struct ChildView: View {
#Binding var config: ChildViewConfig
var body: some View {
TextField("Text", text: $config.text)
...
Button("Reset") {
config.reset()
}
}
}
"ViewConfig can maintain invariants on its properties and be tested independently. And because ViewConfig is a value type, any change to a property of ViewConfig, like its text, is visible as a change to ViewConfig itself." [Data Essentials in SwiftUI WWDC 2020].

Passing a map location variable to other view in SwiftUI

I was able to get the location using CLLocationManager() and the city information is able to display in the Text(config.text). Can someone help how to pass that "config.text" to other view (mainView())
struct CityLocationView2 : View {
let location: Location
#State var config = CityLocationView2Config()
var body: some View {
Text(config.text)
mainView(). // How to pass the city ?????
.task(id: location.id) {
await config.reverseGecode(location: location)
}
}
}
Thanks,
try this:
struct CityLocationView2 : View {
let location: Location
#State var config = CityLocationView2Config()
var body: some View {
Text(config.text)
MainView(text: config.text) // <-- here
.task(id: location.id) {
await config.reverseGecode(location: location)
}
}
}
struct MainView: View {
#State var text: String // <-- here
var body: some View {
Text(text)
}
}
You can of course also use let text: String or var text: String, etc...
I recommend you read and do the tutorial at: https://developer.apple.com/tutorials/swiftui/
Knowing how to pass values to other Views is essential to use SwiftUI.

SwiftUI #EnvironmentObejct can't seem update values

I'm trying to use #EnvironmentObject to update the Boolean values in the ViewModel. So when I navigate back to the original screen I want the boolean values to have change and therefore changing the text. Tried this with ObservedObject too. This is not working or can not find a way for ContentView to redraw itself upon change.
import SwiftUI
class Global: ObservableObject {
#Published var change = [false, false]
}
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NewView().environmentObject(Global())
}
}
}
}
struct NewView: View {
#EnvironmentObject var env: Global
var body: some View {
Text(env.change[1] ? "WORKS" : "DOESNT WORK")
NavigationLink(destination: ChangeThis().environmentObject(Global())) {
Text("Push Me to Change")
}
}
}
struct ChangeThis: View {
#EnvironmentObject var env: Global
var body: some View {
Button(action: {
env.change[0] = true
env.change[1] = true
}) {
Text(" Want this to Changes the Boolean values in Global and update NewView with those values after clicking back")
}
}
}
You need to use the same instance of the Global EnvironmentObject in all your views:
struct NewView: View {
#EnvironmentObject var env: Global
...
// pass the already-existing instance, don't create a new one
NavigationLink(destination: ChangeThis().environmentObject(env)
...
}

Classes and Observable Object

I'm trying to make a data model class that can be referenced by different views. The data model has a function that can modify one of its published variables. However, this function is called inside one view, the change it makes to the published variable is not reflected in other views which also reference the class. The most simple example I can come up with is this:
struct ContentView: View {
var body: some View {
VStack {
TextView()
ButtonView()
}
}
}
struct TextView: View {
#ObservedObject var data = Data()
var body: some View {
Text(data.currentWord)
}
}
struct ButtonView: View {
#ObservedObject var data = Data()
var body: some View {
Button(action: {self.data.randomWord()}) {
Text("Random word")
}
}
}
class Data: ObservableObject {
#Published var currentWord = "Cat"
func randomWord() {
let word = ["Cat", "Dog", "Mouse", "Horse"].randomElement()!
print(word)
currentWord = word
}
}
Both the ButtonView and TextView reference the same class, and the ButtonView calls the 'Data' class's method 'randomWord' which modifies its 'currentWord' published variable. However, the change to this variable is not reflected in the Text of the TextView which also references the 'Data' class.
I think I'm not understanding something about classes and observableObject correctly. Would anyone be kind enough to point out my mistake here?
You create two different instance of Data in your subviews, instead you need to share one, so create it in ContentView and pass to subviews as below
struct ContentView: View {
#ObservedObject var data = Data()
var body: some View {
VStack {
TextView(data: data)
ButtonView(data: data)
}
}
}
struct TextView: View {
#ObservedObject var data: Data
var body: some View {
Text(data.currentWord)
}
}
struct ButtonView: View {
#ObservedObject var data: Data
var body: some View {
Button(action: {self.data.randomWord()}) {
Text("Random word")
}
}
}
Also, as variant, for such scenario can be used EnvironmentObject pattern. There are a lot of examples here on SO you can find about environment objects usage - just search by keywords.

SwiftUI - Updating #State when Global changes

I'd like to update an UI element on an overview view when data on another view is changed.
I looked into #EnvironmentalObject and #Binding. However, an update to either object does not appear to force a view reload. Only changes to #State force renders.
Also, in the case described below, the ChangeView is not a child of OverviewView. Therefore #Binding is not an option anyway.
Data.swift
struct ExampleData : Hashable {
var id: UUID
var name: String
}
var globalVar: ExampleData = ExampleData(id: UUID(), name:"")
OverviewView.swift
struct OverviewView: View {
#State private var data: ExampleData = globalVar
var body: some View {
Text(data.name)
}
}
ChangeView.swift
struct ChangeView: View {
#State private var data: ExampleData = globalVar
var body: some View {
TextField("Name", text: $data.name, onEditingChanged: { _ in
globalVar = data }, onCommit: { globalVar = data })
}
}
Changes within the ChangeView TextField will update the globalVar. However, this will not update the Text on the OverviewView when switching back to the view.
I am aware that using global variables is "ugly" coding. How do I handle data that will be used in a multitude of unrelated views?
Please advise on how to better handle such a situation.
OverviewView and ChangeView hold different copies of the ExampleData struct in their data variables (When assigning a struct to another variable, you're effectively copying it instead of referencing it like an object.) so changing one won't affect the other.
#EnvironmentObject suits your requirements.
Here's an example:
Since, we're using #EnvironmentObject, you need to either convert ExampleData to
a class, or use a class to store it. I'll use the latter.
class ExampleDataHolder: ObservableObject {
#Published var data: ExampleData = ExampleData(id: UUID(), name:"")
}
struct CommonAncestorOfTheViews: View {
var body: some View {
CommonAncestorView()
.environmentObject(ExampleDataHolder())
}
}
struct OverviewView: View {
#EnvironmentObject var dataHolder: ExampleDataHolder
var body: some View {
Text(dataHolder.data.name)
}
}
struct ChangeView: View {
#EnvironmentObject var dataHolder: ExampleDataHolder
var body: some View {
TextField("Name", text: $dataHolder.data.name)
}
}