Couldn't cast SwiftUI.ExtensionDelegate to ExtensionDelegate - swiftui

Working working with a SwiftUI Architecture WatchOS app, if you want to use ExtensionDelegate you need to create your own. I have done this, but when I try to actually access the delegate in the code, I am getting the following error message Could not cast value of type SwiftUI.ExtensionDelegate' (0x7fff8441b480) to 'TestMe_WatchKit_Extension.ExtensionDelegate' (0x10c3b36d0).
I have defined the ExtensionDelegate as -
class ExtensionDelegate: NSObject, WKExtensionDelegate {
var meetingStatistics: MeetingStatistics = MeetingStatistics()
override init(){
super.init()
}
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
print("applicationDidFinishLaunching for watchOS")
}
func applicationDidBecomeActive() {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillResignActive() {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, etc.
}
}
in my #main - I have the following:
#main
struct WatchApp: App {
#WKExtensionDelegateAdaptor(ExtensionDelegate.self) var delegate
// code
}
When I try to access the delegate as -
let delegate = WKExtension.shared().delegate as! ExtensionDelegate I get the above error.

The WKExtension.shared().delegate is not your ExtensionDelegate but internal SwiftUI.ExtensionDelegate (as stated), which provides your adapter delegate. You must use (pass everywhere) only instance of adapter delegate provided for your via
#WKExtensionDelegateAdaptor(ExtensionDelegate.self) var delegate
Update:
To use your delegate you have to pass it as argument next to view, eg as environment (so you can use it in any subview as #Environment(\.appDelegate) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.appDelegate, delegate)
}
}
and
struct DelegateKey: EnvironmentKey {
typealias Value = ExtensionDelegate?
static let defaultValue: ExtensionDelegate? = nil
}
extension EnvironmentValues {
var appDelegate: DelegateKey.Value {
get {
return self[DelegateKey.self]
}
set {
self[DelegateKey.self] = newValue
}
}
}

Well, - in the meantime I collected more experience. For my application delegate it is important that it is a singleton (only one instance exists). I realised that I have two.
#NSApplicationDelegateAdaptor(AppDelegate.self) var applicationDelegate
in the SwiftUI App struct initialises the first. To this App struct I added a init() method.
init() {
AppDelegate.shared = self.applicationDelegate
}
That initialises my AppDelegate static variable "shared" very early in the programme.
I removed all #Environment based access to the delegate, the key, the default value etc.. Global access is insured by
AppDelegate.shared
The issue with the self-defined #Environment variables is, that they can only be set with an .environment(_ keyPath:, _ value:) modifier on a view. However, my delegate is accessed before the first view sets the environment variable. Hence, my AppDelegate shared variable is now an ordinary static variable which I set myself as early as possible: in the init() method of the App struct.

I have the same problem in macOS. After declaring a class as the application delegate using in the #main App struct:
#NSApplicationDelegateAdaptor(AppDelegate.self) var applicationDelegate
The AppDelegate is correctly initialised when the Application launches.
I declare the environment key:
struct DelegateKey: EnvironmentKey {
typealias Value = AppDelegate
static let defaultValue: AppDelegate = AppDelegate()
}
extension EnvironmentValues {
var appDelegate: DelegateKey.Value {
get {
return self[DelegateKey.self]
}
set {
self[DelegateKey.self] = newValue
}
}
}
to access the variable via
#Environment(\.appDelegate) var appDelegate
That is fine for all SwiftUI environments.
The problem is that the general access to the application delegate via
NSApp.delegate
does not work any longer.
The solution is to declare a static variable in the AppDelegate class itself:
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
#Environment(\.appDelegate) static var shared
}
This makes the instantiated AppDelegate object accessible from everywhere via:
AppDelegate.shared
.

Related

SwiftUI: How wrong is to call the objectWillChange.send() after the change in the model data happened?

I'm trying to better understand the mechanism how SwiftUI gets notified about changes in observed data and invalidates UI.
In particular, I wonder if something won't work correctly (e.g. animations?) when objectWillChange.send() is called on ObservableObject after the underlying data model have changed, and more importantly why is that.
If it doesn't matter when the synthesized publisher notifies its observers after the change, then why is it named objectWillChange and not objectDidChange?
Let's say I have a view model like this the following and it observes changes in some model via a delegate method (the code is simplified for the example purposes):
protocol ModelDelegate: AnyObject {
func modelDidChange() {
}
class Model {
weak var delegate: ModelDelegate?
var stroredString: String {
didSet { self.delegate?.modelDidChange() }
}
}
class ViewModel: ObservableObject {
private let model: Model = Model(delegate: self)
var calculatedProperty: String { model.storedString }
}
extension ViewModel: ModelDelegate {
func modelDidChange() {
// we get called when the change has already happened.
// is it wrong to publish the change via objectWillChange?
self.objectWillChange.send()
}
}
And then the View looks like this:
struct MyView: View {
#ObservedObject private var viewModel: ViewModel
var body: some View {
Text(self.viewModel.calculatedProperty)
}
}
Question
Can I expect that something's not going to work correctly when objectWillChange.send() is called on ObservableObject after the underlying data model have changed? If yes, in which case and more importantly, why is that?
If it doesn't matter when the synthesized publisher notifies its observers after the change, then why is it named objectWillChange and not objectDidChange?

How to cancel process() in SwiftUI from different class

I am running a binary file using a swift process() in my DataModel class. The user can see the process running from the ViewTasks() struct. How can I give the user the ability to cancel (terminate) the process?
Here is what I have tried:
DataModel class:
class DataModel : ObservableObject {
#Published var task = Process()
func RunTask() {
// add arguments to task here
do {
task.arguments = [args]
try task.run()
} catch {}
}
Here is the ViewTask() class:
struct ViewTask: View {
#ObservedObject var datamodel: DataModel
var body: some View {
///if user presses a cancel button, cancel the task running
datamodel.task.terminate()
}
This doesn't work because the task can't find the arguments. But when I don't make task an #Published variable, and it only exists locally in that Do loop, it works fine.
Maybe I need to declare/initialize it differently in the datamodel?
Thanks
Ok I figured out how to have a process (on background or main thread) visible through an observable object.
class DataModel : ObservableObject {
#Published var task = Process()
func RunTask() {
// add arguments to task here
do {
let temptask = Process()
temptask.arguments = [args]
// set observable object to this process
self.task = temptask
try task.run()
} catch {}
}
This will allow any other view to call datamodel.task.terminate() and it works just fine. If the process is in a background thread, simply use DispatchQueue.main.async{self.task = temptask} to set the observable task as the locally made process.

ObservedObject not triggering view redraw

I've a view which requires data obtaining from an api.
My understanding of SwiftUI is that by using onAppear the api will be called, viewmodel attribute updated, and the StateObject changed which will trigger a View redraw.
The issue is that no redraw is taking place.
I can see an api call occuring, and adding debug into after the decoded data is used I can see a lot of data is returned.
I've removed a lot of code to make the logic easier to follow (below).
Replacing #StateObject with #ObservedObject and passing into the view from a parent makes no difference either.
Thanks
struct FactoryDetailView: View {
var factory: Factory
#StateObject var factoryDetailsViewModel: FactoryDetailsViewModel()
var body: some View {
VStack {
Text(factory.name)
ForEach(factoryDetailsViewModel.details) { det in
Text(det)
}
}
.onAppear { factoryDetailsViewModel.loadDetails(factory) }
}
}
The viewmodel:
class FactoryDetailsViewModel: ApiViewModel {
#Published var details: [ String ]
func loadDetails(factory: Factory) {
// Do api call...
self.objectWillChange.send()
self.details = decodedResultsFromApiCall
self.objectWillChange.send()
}
class ApiViewModel: ObservableObject {
}
Well... removed details might be the reason of the issue, but in general approach should look like following
struct FactoryDetailView: View {
...
// assume it is a type and here there is initialization
#StateObject var factoryDetailsViewModel = FactoryDetailsViewModel()
...
now about self.objectWillChange.send() - don't call it, modifying published property it is called automatically
func loadDetails(factory: Factory) {
// Do api call...
{
// this is inside (!) API callback
DispatchQueue.main.async {
// update published property on main queue, always.
self.details = decodedResultsFromApiCall
}
}
}
This has the answer:
#Published property wrapper not working on subclass of ObservableObject
The issue I'm seeing is the same - subclassing an ObservableObject.
I've now got a solution working by '#Published var api_response' in the parent class and removing #Published from attributes in subclasses (although leaving '#Published' in them doesn't seem to cause any side effects so they may as well remain if only to document the intent).
Thanks for the responses.

How to get implicitly created scope from resolved instance in DryIoc?

We've got Unit of Work as an external dependency of ViewModel. Both ViewModel and UnitOfWork implement IDisposable interface for cleanup. ViewModel uses UnitOfWork, but does not dispose it:
public class UnitOfWork: IDisposable
{
public void Dispose()
{
// some clean-up code here.
}
}
public class ViewModel: IDisposable
{
private readonly UnitOfWork _unitOfWork;
public ViewModel(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public void Dispose()
{
// some clean-up code here, but NO _unitOfWork.Dispose() because ViewModel does not create UnitOfWork.
}
}
ViewModel is transient: new instance should be created every time DI container's Resolve() method is called. ViewModels are disposed by an external code that has no accces to DI container. For now, UnitOfWork is scoped: only one instance should be created for one ViewModel and Dispose() should be called on ViewModel's Dispose().
We use DryIoc 4.0.7. The documentation says that current resolution scope can be injected to constructor as IDisposable object. So we change our ViewModel:
public class ViewModel: IDisposable
{
private readonly UnitOfWork _unitOfWork;
private readonly IDisposable _scope;
public ViewModel(UnitOfWork unitOfWork, IDisposable scope)
{
_unitOfWork = unitOfWork;
_scope = scope;
}
public void Dispose()
{
// some clean-up code here.
_scope.Dispose(); // we also dispose current scope.
}
}
Our composition root now looks like this:
var container = new Container();
container.Register<UnitOfWork>(reuse: Reuse.Scoped);
container.Register<ViewModel>(
setup: Setup.With(openResolutionScope: true, allowDisposableTransient: true));
I can't see how this code is different from the provided one in the documentation, but it throws an exception on Resolve() method:
DryIoc.ContainerException: 'Unable to resolve IDisposable as parameter "scope"
in ViewModel FactoryId=53 IsResolutionCall
from Container with Scope {Name={ServiceType=ViewModel}}
with Rules with {AutoConcreteTypeResolution}
with Made={FactoryMethod=ConstructorWithResolvableArguments}
Where no service registrations found
and no dynamic registrations found in 0 of Rules.DynamicServiceProviders
and nothing found in 1 of Rules.UnknownServiceResolvers'
What are we missing? Is the documentation outdated? Is there another way to get implicitly created scopes to dispose them?
This documentation is outdated - I have opened the respective issue.
The current scope can be injected as IResolverContext:
public ViewModel(UnitOfWork unitOfWork, IResolverContext scopedContext) {...}

Swift protocol delegate unexpectedly found nil in UIViewController

I have declared a protocol in one view controller and want to call a function in it's presenting view controller. I am not using storyboards.
This delegate!.setWishListButton() causes an error unexpectedly found nil while unwrapping an optional. I know I shouldn't use ! but it's for testing since my delegate function is never called.
I know there are many questions about this but none that matches my usage. I have a similar pattern in a UIView which works fine.
Here is my code
protocol SetWishListButtonDelegate {
func setWishListButton()
}
class MyViewController: UIViewController {
var delegate : SetWishListButtonDelegate?
------
func toggleWishListButton() {
delegate?.setWishListButton()
}
In another view controller
class anotherViewController: UIViewController, SetWishListButtonDelegate {
let myViewController = MyViewController()
override func viewDidLoad() {
super.viewDidLoad()
myViewController.delegate = self
}
func setWishListButton() {
print("DELEGATE METHOD FIRED")
wishListButton.isSelected = true
}
This worked after I removed a second declaration of my view controller in a function where it was presented by a button press.