Invoke Swift closure in Objective-C - objective-c

I want to invoke a Swift closure in Objective-C.
There is an error like this even though I declared the function:
No visible #interface for “User” declares the selector “isReady”
Swift:
#objcMember
class User:NSObject {
func isReady(isTrue: Bool) -> Bool {
return true
}
}
Objective-C:
User *user = [[User alloc] init];
[_user isReady]. <- error

Add to the function #objc modifier:
#objcMember
class User:NSObject {
#objc public func isReady(isTrue: Bool) -> Bool {
return true
}
}
And add public modifier to the function to allow access from other modules (swift code builds as module and ObjC code should export it and access via open interfaces).

Related

Exposing a Swift variable in Objective-C

I am using a Swift class which uses a variable sharedInstance. When I am trying to use this variable in my Objective-C file, I am getting the error:
No known class method for selector 'sharedInstance'
My Swift class:
class SpeakToMeForUnity : NSObject, SFSpeechRecognizerDelegate {
fileprivate var speechRecognizer : SFSpeechRecognizer?
fileprivate var recognitionRequest : SFSpeechAudioBufferRecognitionRequest?
fileprivate var recognitionTask : SFSpeechRecognitionTask?
fileprivate var audioEngine : AVAudioEngine?
static let sharedInstance = SpeakToMeForUnity()
override fileprivate init() {
super.init()
self.speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
self.audioEngine = AVAudioEngine()
self.speechRecognizer?.delegate = self
}
// Some other functions
}
Below is my Objective-C file:
#import <Speech/Speech.h>
#import "SpeakToMeForUnity-Swift.h"
extern "C"
{
void _prepareRecording()
{
SpeakToMeForUnity *instance = [SpeakToMeForUnity sharedInstance];
[instance prepareRecording];
}
void _recordButtonTapped()
{
SpeakToMeForUnity *instance = [SpeakToMeForUnity sharedInstance];
[instance recordButtonTapped];
}
}
I am using Swift 4 and Xcode 10.3.
Normally to make Swift available in Objective-C you just need to import your swift module #import "ProductModuleName-Swift.h", as you're doing.
If you need the swift code in the Objective-C header use #class MySwiftClass; or #protocol MySwiftClass;.
You can force some class or property to be accessible in Objective-C by adding the attribute #objc.
Remember that private or fileprivate access level attributes will not be accessible unless you add the previous attribute.
To expose things to ObjC, you need to mark them #objc. In this case you should add this before class and before the static let.
In previous versions of Swift, inheritance from NSObject automatically implied #objc, but that was removed, and it now needs to be explicit.

Dynamic subclass instantiation in swift

I've come to Swift from Objective-C and there's a lot of things that Objective-C can do but swift is much more complicated. Such as OOP dynamic initializer.
E.g. I've got this code working in Objective-C:
#interface CommonVC: UIViewController
+ (instancetype)showFrom:(UIViewController *)vc;
#end
#implementation CommonVC
+ (instancetype)showFrom:(UIViewController *)vc {
CommonVC *instance = [self instantiateFrom:vc.nibBundle];
[vc presentViewController:instance animated:YES completion:nil];
return instance;
}
// this is like convenience initializer.
+ (instancetype)instantiateFrom:(NSBundle *)aBundle {
return [self.alloc initWithNibName:NSStringFromClass(self.class) bundle:aBundle];
}
#end
#interface SubClassVC: CommonVC
#end
And then use the subclass or superclass like this:
SubClassVC *subVC = [SubClassVC showFrom:self];
// or in swift:
SubClassVC.show(from: self)
However, in swift, it seems impossible to implement something like that. I've tried a few, but always got compile error. Here's one:
class CommonVC: UIViewController {
class func show(from sender: UIViewController) -> Self {
let vc = self(sender: sender) // Compiler error: Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer
sender.present(vc, animated: true, completion: nil)
return unsafeDowncast(vc, to: self)
}
convenience init(sender: UIViewController) {
self.init(nibName: type(of: self).className, bundle: sender.nibBundle)
loadView()
}
}
So how do I write generic convenience initializer of a viewController from the super class and then call that with the subclass?
Of course, my convenience init has lots of stuff that I just cut down to this simple code, also the function show(from:) has a different presentation instead of this simple present(_:animated:completion:).
Even if I make a function to do the setup after initialize, it still wouldn't work
class CommonVC: UIViewController {
class func show(from sender: UIViewController) -> Self {
let vc = self.init(nibName: type(of: self).className, bundle: sender.nibBundle) // Compiler error: Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer
vc.setupAfterInitialize()
sender.present(vc, animated: true, completion: nil)
return unsafeDowncast(vc, to: self)
}
convenience init(sender: UIViewController) {
self.init(nibName: type(of: self).className, bundle: sender.nibBundle)
setupAfterInitialize()
}
internal func setupAfterInitialize() {
// do stuff
loadView()
}
}
And the code looks stupid, doesn't make convenience init convenience.
For now, I can't use the class function show(from:) but have move the presentation outside and make things like:
CommonVC.show(from: self)
SubClassVC(sender: self).present()
// instead of this simple presentation:
// SubClassVC.show(from: self)
I've even tried this but still not working:
class func show<T: CommonVC>(from sender: UIViewController) -> Self {
T.init(nibName: type(of: self).className, bundle: sender.nibBundle)
....
When you switch from Objective-C to Swift, it's tempting to simply translate your Objective-C style into Swift code. But Swift is fundamentally different in some ways.
It may be possible to implement a generic class which all your controllers are subclass of, but we tend to try and avoid inheritance in Swift where possible (in favour of protocols and extensions).
A good Swift rule of thumb, from Apple, is: "always start with a protocol"...
It's actually very easy to implement what you want using a protocol, and extension:
protocol Showable {
init(className: String, bundle: Bundle?)
static func show(from: UIViewController) -> Self
}
extension Showable where Self: UIViewController {
init(className: String, bundle: Bundle?) {
self.init(nibName: className, bundle: bundle)
}
static func show(from: UIViewController) -> Self {
let nibName = String(describing: self)
let instance = self.init(className: nibName, bundle: from.nibBundle)
from.present(instance, animated: true, completion: nil)
return instance
}
}
In the above code, I've declared a Showable protocol and an extension that provides a default implementation that applies where the adopting class is an instance of UIViewController.
Finally, to provide this functionality to every single view controller in your project, simply declare an empty extension:
extension UIViewController: Showable { }
With these two short snippets of code added you can now do what you describe in your question (as long as an appropriately named nib exists for your view controller instance):
let secondController = SecondViewController.show(from: self)
ThirdController.show(from: secondController)
And that's the beauty of Swift. All your UIViewController subclasses now get this functionality for free; no inheritance required.

Swift class: Fatal error: Use of unimplemented initializer 'init()' for class

I created a little convenience class for haptics and I try to reference the class in my Objc code:
self.haptics = [[Haptics new] initWithStyle:UIImpactFeedbackStyleLight];
#objc final class Haptics: NSObject {
private var feedbackGenerator: UIImpactFeedbackGenerator? = nil
init?(style: UIImpactFeedbackStyle) {
guard UserFeedback.soundsAndVibrationsEnabled() else { return nil }
self.feedbackGenerator = UIImpactFeedbackGenerator(style: style)
}
func prepare() {
feedbackGenerator?.prepare()
}
func hit() {
feedbackGenerator?.impactOccurred()
}
}
I get a crash on the Haptics class declaration:
Fatal error: Use of unimplemented initializer 'init()' for class 'MyProject.Haptics'
Not quite sure what I'm doing wrong here
[Haptics new]
is a short form for
[[Haptics alloc] init]
therefor init() without parameter is called, but it doesn't exist.
do
self.haptics = [[Haptics alloc] initWithStyle:UIImpactFeedbackStyleLight];

Call a Swift Singleton from Objective-C

I'm having some trouble accessing a Swift Singleton from Objective-C.
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
}
swiftSharedInstance can not be reached.
Nicky Goethlis's answer is correct but I just want to add another way of Singleton creation termed as One line Singleton" in Swift which I came across recently and it does not use Struct:
Singleton.swift
#objc class Singleton: NSObject {
static let _singletonInstance = Singleton()
private override init() {
//This prevents others from using the default '()' initializer for this class.
}
// the sharedInstance class method can be reached from ObjC. (From OP's answer.)
class func sharedInstance() -> Singleton {
return Singleton._singletonInstance
}
// Some testing
func testTheSingleton() -> String {
return "Hello World"
}
}
SomeObjCFile.m
Singleton *singleton = [Singleton sharedInstance];
NSString *testing = [singleton testTheSingleton];
NSLog(#"Testing---> %#",testing);
Swift 5 and above
final class Singleton: NSObject {
#objc static let shared = Singleton()
#objc var string: String = "Hello World"
private override init() {}
}
use in Objective-C
#import <ProjectName-Swift.h> // change ProjectName to actual project name
NSLog("Singleton String = %#", [Singleton shared].string);
For now I have the following solution. Maybe I am overlooking something that would enable me to access "swiftSharedInstance" directly?
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
// the sharedInstance class method can be reached from ObjC
class func sharedInstance() -> SingletonTest {
return SingletonTest.swiftSharedInstance
}
// Some testing
func testTheSingleton() -> String {
return "Hello World"
}
}
Then in ObjC I can get the sharedInstance class method (after importing the xcode generated swift header bindings)
SingletonTest *aTest = [SingletonTest sharedInstance];
NSLog(#"Singleton says: %#", [aTest testTheSingleton]);
To make members of the SingletonTest class accessible (swiftSharedInstance is a member of this class), use #objcMembers modifier on the class, or add #objc modifier directly on the swiftSharedInstance:
#objc #objcMembers class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
}
Or:
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
#objc class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
}
After creating the Bridging header, be sure to have the Objective-C Generated Interface Header Name set in your Build Settings from your app target. If the value is empty, add the following value:
$(SWIFT_MODULE_NAME)-Swift.h
You need add #objc property wrapper to your singleton:
#objc final class Singleton: NSObject {
#objc static let sharedInstance = Singleton()
#objc func foo() { }
}
Then, in the Objective-C class, import the following:
// Replace the "App" with your Target name.
#import "App-Swift.h"
Finally, after compiling the project, you will be able to use your singleton from Swift inside your Objective-C class:
[[Singleton sharedInstance]foo];
You pretty much have it. To use Swift classes in Obj-C you both need to #import "SingletonTest-Swift.h the generated header or forward declaration with #class MySwiftClass.
Additionally the class needs to inherit from an Obj-C class like you have don here with NSObject or be marked with #objc to expose it. You don't need to do both though, #objc is there to be a more granular option when choosing things to expose.
Apple has some good documentation on all of this and there are two different WWDC sessions you can watch on the topic of Obj-C interoperability as well.
Don't forget to set sharedInstance as public
public final class TestSwiftMain: NSObject {
#objc public static let sharedInstance = TestSwiftMain()
private override init() {}
#objc public func test() {
print("testing swift framework")
}
}
Using it in Objc
[[testSwiftMain sharedInstance] test];
Update 12 Oct 2022
ObcMember So you won't have to write objC behind every function
Swift Class
#objcMembers class SwiftHelpingExtentions: NSObject {
static let instanceShared = SwiftHelpingExtentions()
func testingMethod() {
print("testing")
}
}
}
On objective C View Controller
import : #import "App-Swift.h"
Call The method:
[SwiftHelpingExtentions.instanceShared testingMethod];

Objective-C asking for alloc on swift class

Some small steps to begin wrapping my head around Swift. I've basically ported an old class that simply finds the matching icon for a name and return the appropriate UIImage. The Swift part of things seems to be up and running, and looks (almost) like this:
#objc class ImageHandler{
func iconForData(data: MyData) -> UIImage{
let imagesAndNames = [
"1": "tree.png",
"2": "car.png",
"3": "house.png",
"7": "boat.png",
]
var imageName: String? = imagesAndNames[data.imageName]
if !imageName{
imageName = "placeholder.png"
}
let icon = UIImage(named: imageName)
return icon
}
}
There are no warnings on the above. My old Objective-C class is however asking for an alloc method on the swift class.
ImageHandler *imageHandler = [ImageHandler alloc] init];
Returns the error "No known class method for selector 'alloc' which is true enough I guess, but how do I escape this? Will I have to base my swift-class of NSObject to avoid this?
You declare your ImageHandler class as a root class. It doesn't have alloc method itself. You need to inherit from NSObject:
#objc class ImageHandler : NSObject {
...
}
Referenced from this ADF thread.
Just wanted to comment here that if you don't want to subclass NSObject or any other ObjC object, you can declare a class level initializer:
#objc class ImageHandler{
class func newInstance() -> ImageHandler {
return ImageHandler()
}
func iconForData(data: MyData) -> UIImage{
let imagesAndNames = [
"1": "tree.png",
"2": "car.png",
"3": "house.png",
"7": "boat.png",
]
var imageName: String? = imagesAndNames[data.imageName]
if !imageName{
imageName = "placeholder.png"
}
let icon = UIImage(named: imageName)
return icon
}
}
Then in ObjC
ImageHandler * imageHandler = [ImageHandler newInstance];
This way, you don't have to be dependent on ObjC Class inheritance unless you want to.
This answer is if you want to keep using pure swift objects, and do not want to inherit from NSObject. If you don't care about using pure swift objects then use akashivskyy's answer above.
I came across this same issue. I took Logan's answer and modified it so that the Objective-C caller does not have to change behavior. This is especially important if you are doing like I am and building a framework that could be consumed by Objective-C or Swift projects.
#objc public class TestClass {
public init(){}
public class func alloc() -> TestClass {return TestClass()}
}
The Objective-C implementation gets to stay familiar.
TestClass *testClass = [[TestClass alloc] init];
Swift implementation stays the same as well.
let testClass = TestClass()
Edit:
Furthermore I went on to implement this into an class that can be subclassed by other pure swift objects.
#objc public class ObjectiveCCompatibleObject {
required public init(){}
public class func alloc() -> Self {return self()}
}
If we want to import swift file in Objective C, Should do
import "productname-Swift.h" or use angular brackets <>
in Objective C file. Then can access alloc, init for specific imported swift class in Objective C.
You could do this:
ImageHandler *imageHandler =
[[NSClassFromString(#"YourProjectName.ImageHandler") alloc] init];
(or, if you had done #objc(ImageHandler) class ImageHandler in Swift, you would do [[NSClassFromString(#"ImageHandler") alloc] init])
Alternately, you can declare (but not implement) a dummy category containing the alloc method at the top of your Objective-C file:
#interface ImageHandler (Dummy)
+ (instancetype)alloc;
#end
And then you can directly use it in your code:
ImageHandler *imageHandler = [[ImageHandler alloc] init];