2

I have a Swift class called LoginCoordinator that performs a "Sign In with Apple" request. In order respond to callbacks it conforms to the ASAuthorizationControllerDelegate. Because of this requirement, I also need to make this class a subclass of NSObject:

class LoginCoordinator: NSObject { ... }
extension LoginCoordinator: ASAuthorizationControllerDelegate { ... }

This class will not be used by any Objective-C classes at all. It is intended to be consumed by other Swift classes (i.e. LoginViewController) which will indeed be used by Objective-C classes

The problem is as follows. I eventually import the top-level Swift classes into Objective-C by doing:

#import "MyProject-Swift.h"

This causes a compile-time error in the generated MyProject-Swift.h file that says:

Cannot find protocol declaration for 'ASAuthorizationControllerDelegate'; did you mean 'UINavigationControllerDelegate'?

This error appears on the line for the generated Objective-C category that corresponds to the extension that I wrote above in Swift, where I make LoginCoordinator conform to ASAuthorizationControllerDelegate.

If, before importing MyProject-Swift.h, I also import <AuthenticationServices/AuthenticationServices.h>, then the error goes away. But I do not want to import this everywhere I import Swift.

If I understand correctly, what is going on is this: when I import my Swift files into Objective-C, it imports all the generated headers in MyProject-Swift.h. But one of those category headers (LoginCoordinator) references a protocol (ASAuthorizationControllerDelegate) that has not been imported, which raises an error.

I noticed that MyProject-Swift.h only began to generate code for LoginCoordinator once I made it a subclass of NSObject. Removing the subclassing removes the generated code, but of course it then cannot conform to the protocol.

Is there a way to make a Swift class a subclass of NSObject and conform to an Objective-C protocol, without exposing it when importing Swift files in Objective-C?

  • So the problem is that you don't want to import AuthenticationServices everywhere before MyProject-Swift.h? – Cy-4AH Jul 30 '20 at 09:44
  • Alternatively you can put LoginCoordinator in separate module and conform to `ASAuthorizationControllerDelegate` privately. You will just need to import that module instead of importing -Swift.h – Cy-4AH Jul 30 '20 at 09:47
  • @Cy-4AH good idea. Ideally I would like to avoid creating a new module, but if I can't find another way then I will probably go with that. – haskel-lightricks Jul 30 '20 at 09:54
  • The answer worked for me. If it worked for you too, please accept it as the answer. – tpbafk Aug 20 '20 at 15:01

2 Answers2

4

You can create a private object that actually conforms ASAuthorizationControllerDelegate, and forward message to LoginCoordinator. It's ugly, but can solve the quetion.

class LoginCoordinator {
    private lazy var authDelegate: LoginCordinatorASAuthorizationControllerDelegate {
        LoginCordinatorASAuthorizationControllerDelegate(coordinator: self)
    }
}

//MARK: functions from ASAuthorizationControllerDelegate, but not conforms it
extension LoginCoordinator {
    private func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization....)
}

private class LoginCordinatorASAuthorizationControllerDelegate: NSObject, ASAuthorizationControllerDelegate {
    weak var coordinator: LoginCoordinator!
    init(coordinator: LoginCoordinator)
}

You can also make your LoginCoordinator a nested class which is not able to expose to Objective-C.

enum Login {
    class Coordinator: NSObject, ASAuthorizationControllerDelegate {...}
}
1

The easiest way is to add

#import <AuthenticationServices/AuthenticationServices.h>

to the Objective-C bridging header which is MyProject-Swift.h

tpbafk
  • 501
  • 5
  • 26