Generic Struct 'ObservedObject' Requires That Conform To 'ObservableObject - swiftui

I am having trouble with understanding why the code below will not compile. I'm getting an error stating that I must conform to ObservableObject and I cannot see why I wouldn't be.
I've simplified to show that I am seeing. I have two classes. The second observes the first and then the view observes the second.
First Class
import Foundation
import SwiftUI
import CoreBluetooth
class BLEPeripheralDevice: NSObject, ObservableObject {
#Published var bodySesnorLocation: String = ""
}
Second Class
import Foundation
import SwiftUI
import CoreBluetooth
class BLEManager: NSObject, ObservableObject {
#ObservedObject var blePeripheralDevice: BLEPeripheralDevice!
#Published var blePeripheralName: String = ""
}
View
import SwiftUI
struct BluetoothDeviceView: View {
#ObservedObject var bleManager = BLEManager()
var body: some View {
VStack (spacing: 10) {
Text("Bluetooth Devices")
}
}
When I compile this code I am getting an error in the second class on the following line.
#ObservedObject var blePeripheralDevice: BLEPeripheralDevice!
Generic struct 'ObservedObject' requires that 'BLEPeripheralDevice?'
conform to 'ObservableObject'
I don't understand why this would be. Any help is appreciated.

ObservedObject is a property wrapper mainly for Views. Use Published instead..
#Published var blePeripheralDevice: BLEPeripheralDevice!

Related

SwiftUI: How to pass NSManagedObjectContext into several viewmodels

I'm trying to inject my core data viewcontext into several view models, so I don't need to pass one big view model throughout the whole app.
I'm using the SwiftUI lifecycle, so the NSManagedObjectContext is generated here:
#main
struct Core_Data_VM_TestApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
Following along this answer I didn't succeed, also after playing along with different initializers.
What I want to do is something like this, which isn't working ('Cannot use instance member 'viewContext' within property initializer; property initializers run before 'self' is available')
Content View
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#StateObject var itemVM = ItemViewModel(viewContext: viewContext)
(...)
Viewmodel:
class ItemViewModel: ObservableObject {
var viewContext: NSManagedObjectContext
init(viewContext: NSManagedObjectContext) {
self.viewContext = viewContext
}
}
Thanks for any help!
Just get it from your controller. The variable in the ViewModel will look something like
var viewContext: NSManagedObjectContext = PersistenceController.shared.container.viewContext
There is no need to pass it in the init.
Also, as a best practice if several anything are using something you should create an object for that something and put the common code there.
All the ViewModels can reference this single object. It will make changing things and debugging a million times easier as you app grows.
One approach is to not instantiate the #StateObject in the declaration, but in the view's init method. Note that the StateObject struct that wraps the itemVM property is stored as _itemVM:
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#StateObject var itemVM: ItemViewModel
init() {
_itemVM = StateObject(wrappedValue: ItemViewModel(viewContext: viewContext))
}
// ...
}

Pass `environmentObject` data to another class (singleton) [duplicate]

This question already has an answer here:
How to access a global environment object in a class?
(1 answer)
Closed 1 year ago.
I have an observableObject class that holds some data.
Observable Class
class UserManager:ObservableObject {
#Published
var profile:userProfileModel = userProfileModel()
#Published
var settings:Settings = Settings()
#Published var currentView:String
#Published var isLoggedIn:Bool
and I create and pass it from SceneDelegate as an environmentObject to view hierarchy.
SceneDelegate
#StateObject var userManager = UserManager()
let contentView = loginRoot().environmentObject(userManager)
The problem is that I have a singleton class that syncs with the server and I need to update the data in the UserManager class.
Singleton Class
public class UserModelAPI {
#ObservedObject var userManager: UserManager = UserManager()
public static let shared = UserModelAPI()
func syncServer() {
userManager.isLoggedIn = true
}
but it doesn't work at all.
I can not publish changes from the singleton class.
inside your singleton you have a new UserManager(), it is different from the SceneDelegate "#StateObject var userManager ...",
that's probably why it does not work as you expect.
Try this in your SceneDelegate:
#StateObject var userManager = UserModelAPI.shared.userManager

In SwiftUI, Why there is no `body` method for View [duplicate]

The View protocol requires a body property:
public protocol View {
associatedtype Body : View
#ViewBuilder var body: Self.Body { get }
}
Why have some built-in Views in SwiftUI no body?
#frozen public struct EmptyView : View {
#inlinable public init()
public typealias Body = Never
}
#frozen public struct VStack<Content> : View where Content : View {
#inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, #ViewBuilder content: () -> Content)
public typealias Body = Never
}
have no body at all..
let emptyView = EmptyView().body
// Value of type 'EmptyView' has no member 'body'
let vStackView = VStack { Text("some text")}.body
// Value of type 'VStack<Text>' has no member 'body'
How are these Views implemented?
Each primitive view does have a body, but it isn't directly accessible at compile-time. The official Swift forums have a thread about this topic. I'll reproduce my analysis here.
Let's consider Text.
A Text value has a body property, as all Views do. Text's body calls fatalError:
import SwiftUI
func body<V: View>(of view: V) -> V.Body { view.body }
body(of: Text("hello"))
// runtime output:
SwiftUI/View.swift:94: Fatal error: body() should not be called on Text.
However, if we attempt to access the body property directly, the compiler rejects the program:
import SwiftUI
Text("hello").body
Output:
The compiler rejects the program because SwiftUI's swiftinterface file doesn't declare a body property for Text.
Why doesn't Text's declaration include body? I don't know for sure because I don't have access to the SwiftUI source code, but I have discovered that we can use #_spi to omit a declaration from a .swiftinterface file.
I put the following into MyView.swift:
import SwiftUI
public struct MyView: View {
#_spi(Private)
public var body: Never { fatalError() }
}
Then I compile it as follows, based on the “Directly invoking the compiler” instructions found here:
swiftc MyView.swift -module-name MyLib -emit-module -emit-library -emit-module-interface -enable-library-evolution
The compiler writes MyLib.swiftinterface as follows, omitting the body declaration:
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.7 (swiftlang-5.7.0.123.7 clang-1400.0.29.50)
// swift-module-flags: -target arm64-apple-macosx12.0 -enable-objc-interop -enable-library-evolution -module-name MyLib
import Swift
import SwiftUI
import _Concurrency
import _StringProcessing
public struct MyView : SwiftUI.View {
public typealias Body = Swift.Never
}
And it writes MyLib.private.swiftinterface as follows, containing the body declaration:
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.7 (swiftlang-5.7.0.123.7 clang-1400.0.29.50)
// swift-module-flags: -target arm64-apple-macosx12.0 -enable-objc-interop -enable-library-evolution -module-name MyLib
import Swift
import SwiftUI
import _Concurrency
import _StringProcessing
public struct MyView : SwiftUI.View {
#_spi(Private) #_Concurrency.MainActor(unsafe) public var body: Swift.Never {
get
}
public typealias Body = Swift.Never
}
So my best guess is that SwiftUI applies the #_spi attribute to the body property of each primitive View.
I'm not an expert but this seems very logical. Imagine that no views are offered by SwiftUI and you want to create the very first view. This view has the computed property body that is expecting you to a return a type that conforms to the View protocol (i.e. should have the body property). This will go forever. Hence, there has to be a View without the body property.

Can I use #EnvironmentObject in SwiftUI for all shared data?

There are #State, #ObservedObject and #EnvironmentObject bindings in SwfitUI to share data between views and other objects. Each has its designated usage but #EnvironmentObject seems to be the most powerful and easiest to use. So, can I use it for all state variables and shared data? Are there any downsides to this?
First, #EnvironmentObject is for classes. So if you want to bind primitive type like Int - you can only use Binding.
Second, I think it will couse a problems when you try to define more then one #EnvironmentObject of same type. So, when you can use Binding - you should do that. Thats only my appinion.
class SomeClass: ObservableObject{
#Published var value: Int
init(value: Int){
self.value = value
}
}
struct ContentView: View {
#State var one: SomeClass = SomeClass(value: 1)
#State var two: SomeClass = SomeClass(value: 2)
var body: some View {
Adss().environmentObject(one).environmentObject(two)
}
}
struct Adss: View{
#EnvironmentObject var two: SomeClass
var body: some View{
Text("there must be two: \(two.value)")//prints "1"
}
}
you will have to define all the objects of needed type in straight order even if you don't need them

How to initialize an ObservedObject from a view created with a NavigationLink?

My apologies if this is not the right question to ask, as I am completely new to SwiftUI and iOS programming in general. The question indicates what I want to do, and the error I'm getting I believe is a red herring because of the SwiftUI compiler. It's likely that I am taking the incorrect approach to solving this problem altogether.
I am using XCode Version 11.2.1 (11B500)
View utilizing the ObservedObject:
struct Results: View {
var jobId: String
#ObservedObject var jobDetailService: JobDetailService
init(jobId: String) {
self.jobId = jobId
jobDetailService = JobDetailService(jobId: jobId)
}
var body: some View {
//... view code here
}
}
And it is within this view that I am getting the error (at the ZStack line) "Generic parameter 'C0' could not be inferred". When I comment out the NavigationLink block, the error goes away. Further, when the Results view does not depend on the jobId parameter (and we construct JobDetailService inline with #ObservedObject var jobDetailService = JobDetailService(), this all works. However, I need to be able to pass the jobId parameter to the JobDetailService in order to make the network call to fetch and publish the data.
struct JobList: View {
#ObservedObject var jobListService = JobListService()
var body: some View {
NavigationView {
List(jobListService.jobs) {job in
ZStack {
JobCard(name: job.fullName, date: job.lastUpdated)
NavigationLink(destination: Results(jobId: job.jobId)) {
EmptyView()
}
}
}
}
}
}
After reading this article, and thinking about Asperi's advice on not solely relying on initialization, I opted to do the following:
Remove custom initializer from JobDetailService and instead instantiate the service inside my Results view. Then in an .onAppear method on the Results view, call the getJobDetail method from JobDetailService which in turn makes the network call that populates the #ObservedObject. This allows me to pass in the parameters I need and control when the network call is made. Maybe not the right pattern for this problem but it works for my use case for now.
I assume the following should help you:
Declaration...
struct Results: View {
#ObservedObject var jobDetailService: JobDetailService
var body: some View {
//... view code here
}
}
... and usage
NavigationLink(destination: Results(jobDetailService: JobDetailService(jobId: jobId))) {
EmptyView()
}