Swift protocol called in Objective-C not working and application crashes with error message "unrecognized selector" - objective-c

I am trying to get the Swift protocol to work in Objective-C file, but the application crashes when the error as below.
+[OpenCamera onCameraClose]: unrecognized selector sent to class 0x102ff8580
I am not sure as to what I am missing.
//Swift: UIViewController Code
#objc protocol CameraViewControllerDelegate {
func onCameraClose()
}
#objc class CameraViewController: UIViewController {
var delegate : CameraViewControllerDelegate? = nil
func closeCamera(sender: Any) {
delegate?.onCameraClose()
}
}
// Objective-C : UIViewController Code
OpenCamera.h
#interface OpenCamera : UIViewController <CameraViewControllerDelegate>
OpenCamera.m
#import <MyProjectName/MyProjectName-Swift.h>
#implementation OpenCamera
- (void)viewDidLoad {
[super viewDidLoad];
CameraViewController *cameraViewController = [[CameraViewController alloc] initWithNibName:#"CameraView" bundle:nil];
cameraViewController.delegate = self; //Warning - Incompatible pointer types assigning to 'id<CameraViewControllerDelegate> _Nullable' from 'Class'
}
- (void)onCameraClose {
NSLog(#"Swift Protocol method executed from Objective-C");
}
#end

The warning here did in fact predict the crash:
cameraViewController.delegate = self;
//Warning - Incompatible pointer types assigning to 'id<CameraViewControllerDelegate> _Nullable' from 'Class'
Clearly it thinks self is a class, not an instance. That's very odd.
My guess is that there is something wrong with your import arrangements, but you have not shown enough information to see what it is. I'll just show an arrangement that works.
Let's assume you have both Objective-C and Swift code in one target (i.e. that no frameworks are involved). Then in Swift, you say:
#objc protocol CameraViewControllerDelegate {
func onCameraClose()
}
class CameraViewController: UIViewController {
#objc var delegate : CameraViewControllerDelegate? = nil
func closeCamera(sender: Any) {
delegate?.onCameraClose()
}
}
Note the use of #objc var to expose the delegate property. There is no need to expose the class to Objective-C, as it is already an NSObject derivative.
Okay, in Objective-C, here is your interface file:
#import <UIKit/UIKit.h>
#interface OpenCamera : UIViewController
#end
Note that you do not import the generated header in a .h file, and you do not attempt to mention an imported protocol here.
On to the implementation file:
#import "OpenCamera.h"
#import "MyProject-Swift.h"
#interface OpenCamera () <CameraViewControllerDelegate>
#end
#implementation OpenCamera
- (void)viewDidLoad {
[super viewDidLoad];
CameraViewController *cameraViewController = [[CameraViewController alloc] initWithNibName:#"CameraView" bundle:nil];
cameraViewController.delegate = self;
}
- (void)onCameraClose {
NSLog(#"Swift Protocol method executed from Objective-C");
}
#end
We import the corresponding .h file and the generated header .h file here. We use an anonymous category to declare conformance to the protocol, and the rest is as you have it. You won't see any warnings.

Related

Unable to call an Objective C class delegate from a Swift class

My issue here is that I need the "protocolMethod" in the OldViewController.m to be called from the NewViewController.swift in a delegate fashion.
The project I'm working on was completely written in Objective C, and there's a new View Controller that was written in Swift.
The new View Controller is supposed to call a delegate method, conformed by the Objective C caller object, so the Objective C View Controller can process that call.
The app runs and the delegate is called from Swift, but never actually executed in the Objective C View Controller.
I've tried including all the sample files and the code that's supposed to be called, so here it is:
MyProtocol.h
#import <Foundation/Foundation.h>
#protocol MyProtocol <NSObject>
- (void)protocolMethod:(NSString*)input;
#end
My-Bridging-Header.h
#ifndef My_Bridging_Header_h
#define My_Bridging_Header_h
#import <UIKit/UIKit.h>
#import "OldViewController.h"
#import "MyProtocol.h"
#endif
OldViewController.h
#import <UIKit/UIKit.h>
#interface OldViewController : UIViewController
#end
OldViewController.m
#interface OldViewController ()<MyProtocol>
#end
#implementation OldViewController
//THIS IS THE PROBLEM: NEVER GETS CALLED
- (void)protocolMethod:(NSString *)input {
NSLog(#"protocolMethod:%#", input);
}
- (IBAction)myButtonClicked:(UIButton *)sender {
NewViewController *newViewController =[self.storyboard instantiateViewControllerWithIdentifier:#"NewViewController"];
[newViewController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self presentViewController:newViewController animated:YES completion:nil];
}
#end
NewViewController.swift
import UIKit
class NewViewController: UIViewController {
#objc weak var myProtocol: MyProtocol?
override func viewWillAppear(_ animated: Bool) {
self.myProtocol?.protocolMethod("testing 123")
}
}
Thank you for your help!
Call a objective-C protocol method in Objective-C class from Swift
define the protocol in Objective-c MyProtocol.h
#ifndef MyProtocol_h
#define MyProtocol_h
#import <Foundation/Foundation.h>
#protocol MyProtocol <NSObject>
#optional
- (void)protoMethod:(NSString *)input;
#end
#endif
define interface of LoginViewController.h
#import <UIKit/UIKit.h>
#import "MyProtocol.h"
#class NewViewController; //declare knowledge of swift class
#interface LoginViewController : UIViewController <MyProtocol>
// method to trigger testing manual
-(void)haveFun;
#end
define implementation of LoginViewController.m
#import "LoginViewController.h"
#import "YourProductModulName-Swift.h" // will bridge exposed swift class to objc
#implementation LoginViewController
-(void)protoMethod:(NSString*)input {
NSLog(#"protocolMethod:%#", input);
}
- (void)haveFun {
// did not use the storyboard to make testing simple
// this is an #objc exposed swift class
NewViewController *newViewController = [[NewViewController alloc] init];
// now trigger predefined method that checks for protocol
[newViewController fun:self];
}
#end
now let us write a swift class that is subclassing an NSObject, here UIViewController and expose it to objc
import Foundation
import UIKit
#objc //expose class when you need to use it from Objective-C
class NewViewController: UIViewController {
// as objc bridging is working with NSObjects,
// we can use it instead of Any, which can't be exposed.
#objc(foo) weak var foo : NSObject?
override func viewWillAppear(_ animated: Bool) {
// question was: how to call (id<MyProtocol>)-(void)protoMethod:
// as we dont use storyboard for testing here..
// nothing in here
}
// no autonaming, defined -(void)fun: instead
#objc(fun:) func fun(protocolObj: MyProtocol) {
let bar = protocolObj
bar.protoMethod!("direct invokation of protoMethod")
// ah why not, lets remember the object for later.
foo = bar as? NSObject
testme()
}
#objc func testme() {
// as foo is NSObject here, check if exist
if (foo != nil) {
// cast foo and check for protocol
if let loginview = foo as? MyProtocol {
loginview.protoMethod!("PROTOCOL-LOL!")
} else {
print("i will allways love youuu!")
}
}
}
}
now lets have some fun and use the back and forth bridged stuff.
This will allocate a Objective-c class LoginViewController following <MyProtocol> and invoke -(void)haveFun method. This method will allocate a Swift Class NewViewController (important! based on NSObject) that will invoke #objc exposed swift func fun: and give self as parameter that must follow the MyProtocol. It will then invoke the protocol method of LoginViewController with a parameter NSString and store the LoginViewController object in weak var foo as NSObject. Then it will trigger internal swift testme() and use the stored foo to check again if it exists and conforms to protocol MyProtocol and invokes the protocolMethod -(void)protoMethod:(NSString*)string; again or print something if not conforming.
LoginViewController *login = [[LoginViewController alloc] init];
[login haveFun];
Don't forget the example here needs Swift bridging to Objective-C and Objective-C bridging to Swift. If that is a perfect design pattern is another talk.

How to initialize a subclass when the init method of the superclass is NS_UNAVAILABLE

I'm attempting to write a test for an objective-c class. The class I'm trying to test is MyClass and it looks like this:
#interface MyClass : NSObject
- (void)dispatchEvent:(IMAAdEvent *)event;
#end
In order to test this dispatchEvent method, I need to pass in an instance of IMAAdEvent. The IMAAdEvent class comes from Google's library GoogleAds-IMA-iOS-SDK.
Unfortunately, I can't call init on this class because the init method is marked as NS_UNAVAILABLE. In XCode I get an error that reflects this:
'init' in unavailable
Ideally, I would like to make my own mock subclass of IMAAdEvent like this. Is there some way I can initialize my subclass without calling the unavailable init method on the superclass?:
#interface MockImaAdEvent : IMAAdEvent
#end
#implementation MockImaAdEvent
- (id)init {
// is there something I can do here so that I return an instances
// of the subclass without calling [super init]?
}
#end
As of Xcode 12.5, Swift is no longer able to use the previous solution. The compiler has started returning errors for init() is unavailable on lines of code where new init functions have been added. The Xcode 12.5 Release Notes indicate the following:
Clang now infers the availability of +new from availability annotations on -init methods. Since +new calls [[Foo alloc] init], +new isn’t available unless +init is available.
Despite this release note, there is still a valid workaround. By writing the mock class in Objective-C and using a bridging-header to bring it into Swift, the mock class can still call super.new to get a new instance of the parent class (and then customize the subclass from there).
Here is an example:
#interface MockInAppMessagingCampaignInfo ()
#property (strong, nonatomic) NSString *campaignNameValue;
#end
#implementation MockInAppMessagingCampaignInfo
+ (id)newWithCampaignName:(NSString *)campaignName {
MockInAppMessagingCampaignInfo *newObject = super.new;
newObject.campaignNameValue = campaignName;
return newObject;
}
- (NSString *)campaignName {
self.campaignNameWasCalled = YES;
return self.campaignNameValue ?: #"";
}
#end
If I use a method that's not called init then this seems to work. It still seems really weird to not call [super init] in this function, but it's working and returning a new instance of the MockImaAdEvent class
#interface MockImaAdEvent : IMAAdEvent {
enum IMAAdEventType type;
}
#property (nonatomic) enum IMAAdEventType type;
#end
#implementation MockImaAdEvent
#synthesize type;
- (id)initWithType:(NSInteger)_type {
type = _type;
return self;
}
#end
// in my test I can initialize like this:
MockImaAdEvent *adEvent = [[MockImaAdEvent alloc] initWithType:kIMAAdEvent_LOADED];
An alternative solution for Xcode 12.5 that doesn't require Objective-C bridging-header is to create a custom static initialiser that uses the objc runtime to invoke new.
I have used that for fakes that subclass objects with unavailable initialisers and works great.
Example:
static func customInit() -> SomeObjectFake {
let instance = SomeObjectFake.perform(NSSelectorFromString("new")).takeRetainedValue() as! SomeObjectFake
...
return instance
}

Can not find Swift Protocol declaration in Obj-C class

I have created on Class in Swift and that class and its protocol I am using in Obj-C enabled project but I am getting below error while compiling my project.
cannot find protocol declaration for 'SpeechRecognizerDelegate'; did
you mean 'SFSpeechRecognizerDelegate'?
Can anyone guide me on this how can I use Swift class protocol in my Obj-C class.
Here is my Swift code:
protocol SpeechRecognizerDelegate : class {
func speechRecognitionFinished(_ transcription:String)
func speechRecognitionError(_ error:Error)
}
class SpeechRecognizer: NSObject, SFSpeechRecognizerDelegate {
open weak var delegate: SpeechRecognizerDelegate?
}
Protocol use in Obj-C:
#import "ARBot-Swift.h"
#interface ChatScreenViewController : JSQMessagesViewController <SpeechRecognizerDelegate>
Let me know if required more info.
Thanks in Advance.
in Swift:
#objc public protocol YOURSwiftDelegate {
func viewReceiptPhoto()
func amountPicked(selected: Int)
}
class YourClass: NSObject {
weak var delegat: YOURSwiftDelegate?
}
in Objective-C headerFile.h
#protocol YOURSwiftDelegate;
#interface YOURController : UIViewController < YOURSwiftDelegate >
in Objective-C Implementation.m
SwiftObject * swiftObject = [SwiftObject alloc] init];
swiftObject.delegate = self
Define your Swift protocol like this inside your Swift file.
#objc protocol SpeechRecognizerDelegate: class{
func speechRecognitionFinished(_ transcription:String)
func speechRecognitionError(_ error:Error)
}
Create a Swift Module inside your project setting then use it. You can find here complete blog for the mix language coding.
Then use Protocol inside Objective C class,
We required to add protocol inside Objective C file -
#import "ARBot-Swift.h"
#interface ChatScreenViewController : JSQMessagesViewController <SpeechRecognizerDelegate>
Then you need to conform to the protocol methods -
- (void)viewDidLoad {
[super viewDidLoad];
SpeechRecognizer * speechRecognizer = [[SpeechRecognizer alloc] init];
speechRecognizer.delegate = self;
}
#pragma mark - Delegate Methods
-(void)speechRecognitionFinished:(NSString *) transcription{
//Do something here
}
-(void)speechRecognitionError:(NSError *) error{
//Do something here
}
I had a similar issue after following (import header + Objc annotation on protocol). I got the warning when using Swift code from Objective C headers. Solved by only importing into the implementation .m files.
Add #objc attribute to your protocol:
#objc protocol SpeechRecognizerDelegate : class {
//...
}
Include Swift Classes in Objective-C Headers Using Forward Declarations
//MySwiftClass.swift
#objc protocol MySwiftProtocol {}
#objcMembers class MySwiftClass {}
// MyObjcClass.h
#class MySwiftClass;
#protocol MySwiftProtocol;
#interface MyObjcClass : NSObject
- (MySwiftClass *)returnSwiftClassInstance;
- (id <MySwiftProtocol>)returnInstanceAdoptingSwiftProtocol;
// ...
#end

Use delegate in objective c class to call swift method

I have two files
Question.m
Question.h
These two are written by Objective-C
MainView.swift
This is written by Swift
Question Class has the delegate
#interface Question : NSObject{
id delegate;// put MainViewController here
- (void)trythisfunction{
[delegate test] // compiler doesn't find this method.
}
}
and I make class instance and put MainViewController as delegate of Question in MainViewController.swift
class MainViewController: UIViewController {
override func viewDidLoad(){
q = Question()
q.delegate = self // put self in delegate
}
func test(){
NSLog("test is OK")
}
}
However Compiler found error [delegate test]
Question.m:169:19: No known instance method for selector 'test:'
How can I solve this??
You need to make few changes.
Below class declaration doesn't compile because you can't declare variables inside interface.
#interface Question : NSObject{
id delegate;
- (void)trythisfunction {
[delegate test]
}
}
I have fixed above and the class now looks like this,
# Question.h file
#import <Foundation/Foundation.h>
#interface Question : NSObject
#property (nonatomic, strong) id delegate;
#end
Below is the implementation of the class
# Question.m file
#import "Question.h"
#implementation Question
#synthesize delegate;
- (void)trythisfunction{
[delegate test];
}
#end
As we are integrating this swift and so we will need a Bridging Header whose content look like.
#import "Test.h"
Finally in your swift class now you can import this class
import UIKit
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let q = Test()
q.delegate = self
}
func test(){
NSLog("test is OK")
}
}
And above code works like a charm.

Set Delegate in a mixed language project

I have an XCode6 mixed-language project, combining Swift and Objective C.
I created a Swift-based SingleView application, then added 2 Objective-C files, having contents as below:
Singleton.h
#import <Foundation/Foundation.h>
#protocol SingletonDelegate <NSObject>
#optional
- (void)methodCalled;
#end
#interface Singleton : NSObject
#property (weak, nonatomic) id <SingletonDelegate> singletonDelegate;
+ (id)sharedSingleton;
- (void)method;
#end
Singleton.m
#import "Singleton.h"
static Singleton *shared = nil;
#implementation Singleton
- (id)init {
self = [super init];
if (self) {
}
return self;
}
#pragma mark - Interface
+ (Singleton *)sharedSingleton {
static dispatch_once_t pred;
dispatch_once(&pred, ^{
shared = [[Singleton alloc] init];
});
return shared;
}
- (void)method {
[self.singletonDelegate methodCalled];
}
#end
After setting up bridging header file as XCode suggested, I added #import "Singleton.h" into it.
In ViewController.swift, I tried to set singletonDelegate but always failed:
import UIKit
class ViewController: UIViewController, SingletonDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Singleton.sharedSingleton().singletonDelegate = self // FAILED HERE!!!
Singleton.sharedSingleton().method()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The error message is:
Cannot assign to the result of this expression
Could any one show me how to fix this? (I am new in integrating Objective-C into Swift project)
Thanks in advance,
Create a class variable. Then set its delegate.
let singleton: Singleton = Singleton.sharedSingleton()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
singleton.singletonDelegate = self
//Singleton.sharedSingleton().singletonDelegate = self // FAILED HERE!!!
//Singleton.sharedSingleton().method()
}
fun methodCalled() {
//This method gets called from the Singleton class through the delegate
}