SwiftUI: reference represented view controller? - swiftui

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?

Related

EnvironmentValues getter is being called on app launch without access

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.

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))
}
// ...
}

Generic Struct 'ObservedObject' Requires That Conform To 'ObservableObject

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!

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.

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