I have a custom EnvironmentValue defined as:
private struct DataProviderKey: EnvironmentKey {
static let defaultValue: DataProvider = {
fatalError()
}()
}
extension EnvironmentValues {
var dataProvider: DataProvider {
get {
self[DataProviderKey.self]
}
set {
self[DataProviderKey.self] = newValue
}
}
}
And main function looks like this:
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
MyView()
.environment(\.dataProvider, MyDataProvider())
}
}
}
My expectation is that the defaultValue won't be accessed as I inject the environment on App Launch. But before that happens, there's another call to the var dataProvider: DataProvider getter that I am not invoking.
Stack trace doesn't reveal much information as well:
So my question is why is the getter being invoked by the system? Even if I remove the access with keyPath .environment(\.dataProvider, MyDataProvider()) it still gets called at the same point in time. I don't want to provide a default value because I want to know whenever such value is not injected immediately.
Related
Starting to learn SwiftUI I am a bit confused on how Viewss are implemented. View is actually not a type but a protocol which requires a var body of type View.
Problem 1: So the protocol requires it self. Is this not an infinite, recursive loop? Implementation of View requires a var body that implements View which requires a var body... How does this work?
Problem 2: var body is usually implemented as computed property. This is nothing unusual. However, the implementation does not return anything but only "creates" subviews which are not explicitly added to their parent view. Initializing the sub views is enough.
This is not only the case with the body var but with all other views which include other views like HStack, VStack, etc.
struct SomeView: View {
var body: some View {
ViewA()
OtherView()
VStack {
Sub1()
Sub2()
}
}
}
How can this work? Is this valid Swift syntax? I mean a "normal" computed property would look like this, wouldn't it?
var someValue: Int {
let value1 = getValue()
let value2 = SomeOtherValue()
return value1 + value2
}
And not like this:
var someValue: Int {
getValue()
SomeOtherValue()
}
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))
}
// ...
}
I have created a view I use when showing a UIViewController subclass.
struct CarView: UIViewControllerRepresentable {
var viewModel:CarViewModel
func makeUIViewController(context: Context) -> CarVC {
let vc = CarVC()
vc.viewModel = viewModel
return vc
}
func updateUIViewController(_ uiViewController:CarVC, context: Context) {
}
}
In the SwiftUI view I show it like so:
...
CarView(viewModel: carViewModel)
...
All works great.
When I want to call functions from the SwiftUI view containing CarView, how can I call functions declared on the actual CarVC instance wrapped in the CarView?
So if CarVC has a function called showModel(), how can the SwiftUI container call that function?
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.
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()
}