I have this App structure, where I need to initialize appearancesStore and behavioursStore with userManager.
But I get the error Escaping auto closure captures mutating 'self' parameter and Variable 'self.appearancesStore' captured by a closure before being initialized.
I have tried some different methods of doing this, but it all failed.
Do anyone have a suggestion of how to do this?
#main
struct DuneApp: App {
#StateObject var userManager = UserManager()
#StateObject var appearancesStore: AppearancesStore
#StateObject var behavioursStore: BehavioursStore
init() {
_appearancesStore = StateObject(wrappedValue: AppearancesStore(manager: self.userManager))
_behavioursStore = StateObject(wrappedValue: BehavioursStore(manager: self.userManager))
}
var body: some Scene {
WindowGroup {
AuthenticateView()
.environmentObject(userManager)
.environmentObject(appearancesStore)
.environmentObject(behavioursStore)
}
}
}
Try the following
#main
struct DuneApp: App {
#StateObject var userManager: UserManager
#StateObject var appearancesStore: AppearancesStore
#StateObject var behavioursStore: BehavioursStore
init() {
let userManager = UserManager()
_userManager = StateObject(wrappedValue: userManager)
_appearancesStore = StateObject(wrappedValue: AppearancesStore(manager: userManager))
_behavioursStore = StateObject(wrappedValue: BehavioursStore(manager: userManager))
}
var body: some Scene {
WindowGroup {
AuthenticateView()
.environmentObject(userManager)
.environmentObject(appearancesStore)
.environmentObject(behavioursStore)
}
}
}
Related
I want to create a global variable for showing a loadingView, I tried lots of different ways but could not figure out how to. I need to be able to access this variable across the entire application and update the MotherView file when I change the boolean for the singleton.
struct MotherView: View {
#StateObject var viewRouter = ViewRouter()
var body: some View {
if isLoading { //isLoading needs to be on a singleton instance
Loading()
}
switch viewRouter.currentPage {
case .page1:
ContentView()
case .page2:
PostList()
}
}
}
struct MotherView_Previews: PreviewProvider {
static var previews: some View {
MotherView(viewRouter: ViewRouter())
}
}
I have tried the below singleton but it does not let me update the shared instance? How do I update a singleton instance?
struct LoadingSingleton {
static let shared = LoadingSingleton()
var isLoading = false
private init() { }
}
Make your singleton a ObservableObject with #Published properties:
struct ContentView: View {
#StateObject var loading = LoadingSingleton.shared
var body: some View {
if loading.isLoading {
Text("Loading...")
}
ChildView()
Button(action: { loading.isLoading.toggle() }) {
Text("Toggle loading")
}
}
}
struct ChildView : View {
#StateObject var loading = LoadingSingleton.shared
var body: some View {
if loading.isLoading {
Text("Child is loading")
}
}
}
class LoadingSingleton : ObservableObject {
static let shared = LoadingSingleton()
#Published var isLoading = false
private init() { }
}
I should mention that in SwiftUI, it's common to use .environmentObject to pass a dependency through the view hierarchy rather than using a singleton -- it might be worth looking into.
First, make LoadingSingleton a class that adheres to the ObservableObject protocol. Use the #Published property wrapper on isLoading so that your SwiftUI views update when it's changed.
class LoadingSingleton: ObservableObject {
#Published var isLoading = false
}
Then, put LoadingSingleton in your SceneDelegate and hook it into your SwiftUI views via environmentObject():
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
static let singleton = LoadingSingleton()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(SceneDelegate.singleton))
self.window = window
window.makeKeyAndVisible()
}
}
}
To enable your SwiftUI views to update when changing isLoading, declare a variable in the view's struct, like this:
struct MyView: View {
#EnvironmentObject var singleton: LoadingSingleton
var body: some View {
//Do something with singleton.isLoading
}
}
When you want to change the value of isLoading, just access it via SceneDelegate.singleton.isLoading, or, inside a SwiftUI view, via singleton.isLoading.
To clarify in code. Is there a difference between:
struct Shim: View {
#StateObject private var viewModel = {
let viewModel = ContentView.ViewModel()
//do some configuration
return viewModel
}()
var body: some View {
ContentView(viewModel: viewModel)
}
}
AND
struct Shim: View {
#StateObject private var viewModel: ContentView.ViewModel
init() {
let viewModel = ContentView.ViewModel()
//do some configuration
_viewModel = StateObject(wrappedValue: viewModel)
}
var body: some View {
ContentView(viewModel: viewModel)
}
}
I'm using view models for my SwiftUI app and would like to have the focus state also in the view model as the form is quite complex.
This implementation using #FocusState in the view is working as expected, but not want I want:
import Combine
import SwiftUI
struct ContentView: View {
#ObservedObject private var viewModel = ViewModel()
#FocusState private var hasFocus: Bool
var body: some View {
Form {
TextField("Text", text: $viewModel.textField)
.focused($hasFocus)
Button("Set Focus") {
hasFocus = true
}
}
}
}
class ViewModel: ObservableObject {
#Published var textField: String = ""
}
How can I put the #FocusState into the view model?
Assuming you have in ViewModel as well
class ViewModel: ObservableObject {
#Published var hasFocus: Bool = false
...
}
you can use it like
struct ContentView: View {
#ObservedObject private var viewModel = ViewModel()
#FocusState private var hasFocus: Bool
var body: some View {
Form {
TextField("Text", text: $viewModel.textField)
.focused($hasFocus)
}
.onChange(of: hasFocus) {
viewModel.hasFocus = $0 // << write !!
}
.onAppear {
self.hasFocus = viewModel.hasFocus // << read !!
}
}
}
as well as the same from Button if any needed.
I faced the same problem and ended up writing an extension that can be reused to sync both values. This way the focus can also be set from the view model side if needed.
class ViewModel: ObservableObject {
#Published var hasFocus: Bool = false
}
struct ContentView: View {
#ObservedObject private var viewModel = ViewModel()
#FocusState private var hasFocus: Bool
var body: some View {
Form {
TextField("Text", text: $viewModel.textField)
.focused($hasFocus)
}
.sync($viewModel.hasFocus, with: _hasFocus)
}
}
extension View {
func sync<T: Equatable>(_ binding: Binding<T>, with focusState: FocusState<T>) -> some View {
self
.onChange(of: binding.wrappedValue) {
focusState.wrappedValue = $0
}
.onChange(of: focusState.wrappedValue) {
binding.wrappedValue = $0
}
}
}
I need to save an instance of a child view into a variable, so I can call a method on it afterward.
However, I need to pass a binding into this child view when its initialized. How do I do that?
struct EditImageView: View {
#State private var currentSelectedText:String
#State private var currentSelectedFilter:Filter
var imageCanvasView: ImageCanvasView
init() {
currentSelectedText = "Hello"
currentSelectedFilter = Filter.noFilter
imageCanvasView = ImageCanvasView(imageText: $currentSelectedText, filter: $currentSelectedFilter)
//Error: 'self' used before all stored properties are initialized
}
var body: some View {
imageCanvasview
Button("Take screenshot") {
imageCanvasview.takeScreenshot()
}
}
}
One way is to declare imageCanvasView in body, like:
struct EditImageView: View {
#State private var currentSelectedText = "Hello"
#State private var currentSelectedFilter = Filter.noFilter
var body: some View {
let imageCanvasView = ImageCanvasView(imageText: $currentSelectedText, filter: $currentSelectedFilter)
VStack {
imageCanvasView
Button("Take screenshot") {
imageCanvasView.takeScreenshot()
}
}
}
}
All you need to do is to change the property wrapper prefix. For example, if you wanted to pass your currentSelectedText you would pass it like so.
var currentSelectedText: Binding<String>
// Effectively is the equivalent of `#State`
The same can be done in your init()
init(someString: Binding<String>) { ....
Probably a better way is to use a view model which both EditImageView and ImageCanvasView use, something like:
class EditImageViewModel: ObservableObject {
#Published var currentSelectedText: String = "Hello"
#Published var currentSelectedFilter = Filter.noFilter
func takeScreenshot() {
}
}
struct ImageCanvasView: View {
#EnvironmentObject var editImage: EditImageViewModel
var body: some View {}
}
struct EditImageView: View {
#StateObject var editImage = EditImageViewModel()
var body: some View {
VStack {
ImageCanvasView()
Button("Take screenshot") {
editImage.takeScreenshot()
}
}
.environmentObject(editImage)
}
}
How to update view, when view models publish var's (user's) , name property is updated. I do know why its happening but what is the best way to update the view in this case.
class User {
var id = "123"
#Published var name = "jhon"
}
class ViewModel : ObservableObject {
#Published var user : User = User()
}
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
userNameView
}
var userNameView: some View {
Text(viewModel.user.name)
.background(Color.red)
.onTapGesture {
viewModel.user.name += "update"
print( viewModel.user.name)
}
}
}
so one way i do it, is by using onReceive like this,
var body: some View {
userNameView
.onReceive(viewModel.user.$name){ output in
let tmp = viewModel.user
viewModel.user = tmp
print("onTapGesture",output)
}
}
but it is not a good approach it will update all view using users properties.
should i make a #state var for the name?
or should i just make a ObservedObject for user as well?
Make you class conform to ObservableObject
class User: ObservableObject {
var id = "123"
#Published var name = "jhon"
}
But he catch with that is that you have to observe it directly you can't chain it in a ViewModel
Use #ObservedObject var user: User in a View
You should use struct:
import SwiftUI
struct User {
var id: String
var name: String
}
class ViewModel : ObservableObject {
#Published var user : User = User(id: "123", name: "Mike")
}
struct ContentView: View {
#ObservedObject var viewModel: ViewModel = ViewModel()
var body: some View {
userNameView
}
var userNameView: some View {
Text(viewModel.user.name)
.background(Color.red)
.onTapGesture {
viewModel.user.name += " update"
print( viewModel.user.name)
}
}
}