I have a class that adopts the ObservableObject
protocol that I want to refactor. Instead of adopting the protocol the class should be marked with the @Observable
macro. Now I’m encountering a problem with one of the class’s properties: @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
. When using the macro I get an error Property wrapper cannot be applied to a computed property
. I’ve tried removing the property and accessing the AppDelegate
in all places where it’s needed in the class using let appDelegate = UIApplication.shared.delegate as! AppDelegate
but that causes a crash.
How can I access the AppDelegate
from a class marked with the @Observable
macro?
Edit
The AppDelegate
class. I’m using the AppAuth to authenticate users. Following the examples in docs I’ve made an AppDelegate
class with a property (currentAuthorizationFlow
) to hold the session.
class AppDelegate: NSObject, UIApplicationDelegate {
var currentAuthorizationFlow: OIDExternalUserAgentSession?
}
This is how the class I’m refactoring looked.
class AuthModel: ObservableObject {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
// Some @Published properties
func login() async {
// builds authentication request
let request = OIDAuthorizationRequest(configuration: configuration,
clientId: clientID,
clientSecret: clientSecret,
scopes: [OIDScopeOpenID, OIDScopeProfile],
redirectURL: redirectURI,
responseType: OIDResponseTypeCode,
additionalParameters: nil)
// performs authentication request
print("Initiating authorization request with scope: (request.scope ?? "nil")")
await withCheckedContinuation { continuation in
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
if let authState = authState {
self.setAuthState(authState)
print("Got authorization tokens. Access token: " + "(authState.lastTokenResponse?.accessToken ?? "nil")")
} else {
print("Authorization error: (error?.localizedDescription ?? "Unknown error")")
self.setAuthState(nil)
}
continuation.resume()
}
}
}
}
In the example of the library let appDelegate = UIApplication.shared.delegate as! AppDelegate
is used to access the AppDelegate
, to not have to repeat that line of code each time I needed to access the AppDelegate
I instead opted to have the @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
property in my class. I also have @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
in the @main
of my app, from the answers I understand that I shouldn’t have that line twice in my project.
I’m refactoring the AuthModel
to use the @Observable
macro.
@Observable
class AuthModel {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
// Removed the @Published macro from the properties
func login() async {
// builds authentication request
let request = OIDAuthorizationRequest(configuration: configuration,
clientId: clientID,
clientSecret: clientSecret,
scopes: [OIDScopeOpenID, OIDScopeProfile],
redirectURL: redirectURI,
responseType: OIDResponseTypeCode,
additionalParameters: nil)
// performs authentication request
print("Initiating authorization request with scope: (request.scope ?? "nil")")
await withCheckedContinuation { continuation in
// I've tried to get the AppDelegate here in the same way the docs suggest, removing the AppDelegate property higher in the class
// let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
if let authState = authState {
self.setAuthState(authState)
print("Got authorization tokens. Access token: " + "(authState.lastTokenResponse?.accessToken ?? "nil")")
} else {
print("Authorization error: (error?.localizedDescription ?? "Unknown error")")
self.setAuthState(nil)
}
continuation.resume()
}
}
}
}
In the refactored class I get an error on the @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
property: Property wrapper cannot be applied to a computed property.
If I remove the property and try to access the AppDelegate
in the login
method using let appDelegate = UIApplication.shared.delegate as! AppDelegate
the app crashes at runtime with: Thread 4: signal SIGABRT An abort signal terminated the process. Such crashes often happen because of an uncaught exception or unreacoverable error or calling the abort() function. There is also a warning that UIApplication.delegate must be used from the main thread only, but placing it in an Task { @MainActor }
result in an error that local variable appDelegate
cannot have a global actor.
2
Answers
@UIApplicationDelegateAdaptor
should just be used in one place, e.g. in theApp
struct:Then if you want to access it in other places first you should conform it to
ObservableObject
, e.g.Then in your
View
models you can do:If you need the
AppDelegate
in an@Observable
object then you could init the other object inside a lazy var insideAppDelegate
and pass inweak self
so it can access the delegate without a retain cycle.Then if you wanted to observe that in a
View
model then inbody
you could init a childView
model and pass inappDelegate.someObservable
which would then uselet someObservable
and then if you access its properties inbody
the fact it is@Observable
should take care of monitoring for update automatically, e.g.As you can see, objects are a bit of a nightmare to manage, so best stick to funcs and structs whenever you can in Swift/SwiftUI.
It seems to me that you have been copy pasting code from the AppAuth docs without really understanding it. It is not clear whether you actually got something working at all.
From what I can gather, the AppAuth lib doesn’t seem to be designed with SwiftUI in mind. It is a bit old fashion and requires hooking into the AppDelegate. A good starting point is to understand how to do this in a SwiftUI app. This is well explained in the doc for
@UIApplicationDelegateAdaptor
.Using code from the AppAuth docs, here is a simple SwiftUI implementation :