Calling an Objective-C function defined in a .h file from Swift - objective-c

I want to call this Objective-C function from Swift code.
The accepted answer on this question shows how to use an Objective-C class in Swift, not a function. Secondly, the Objective-C implementation in it is contained in a .m file, while the one I have linked above is a .h file.
What is the easiest way I can call an Objective-C function defined in a .h file from Swift?

As mentioned in the comments first you'll need to do all the usual steps for using Objective-C classes from Swift (bridging header, etc.) Then you'll already be able to call your function and you'd only need a bit of tweaking to make it more usable in Swift.
func CopyLaunchedApplicationsInFrontToBackOrder_Swift() -> [[String: AnyObject]]? {
guard let cfarray = CopyLaunchedApplicationsInFrontToBackOrder() else {
return nil
}
let array = unsafeBitCast(cfarray, to: NSArray.self) as [AnyObject]
guard let result = array as? [[String: AnyObject]] else {
return nil
}
return result
}
If your function is implemented in .h file it would be better to move implementation into a corresponding .m file leaving in .h only it's declaration:
#import Foundation;
CFArrayRef CopyLaunchedApplicationsInFrontToBackOrder(void);
It's also possible to put this declaration into your bridging header and leave implementation in the .h file, but that would be unusual making your code more error prone and harder to maintain.

Related

Invoke Swift closure in Objective-C via `id`

This question has been inspired by this one: Swift closure in array becomes nil in Objective-c, which I answered with a workaround using a wrapper class, but I'm still curious as to why one can't call, in Objective-C, a Swift closure passed via id. A simple example follows.
Objective-C code:
// In a header
typedef void (^EmptyBlock)();
#interface MyClassOC : NSObject
-(void)invoke:(id)blk;
#end
// In an implementation (.m) file
#implementation MyClassOC
-(void)invoke:(id)blk {
EmptyBlock emptyBlock = blk;
emptyBlock();
}
#end
No problem providing an Objective-C block:
EmptyBlock block = ^{ puts("In OC empty block..."); };
MyClassOC * myClassOC = [[MyClassOC alloc] init];
[myClassOC invoke:block];
However, the Objective-C code in invoke... can't call a Swift closure passed in via id:
let myClassOC = MyClassOC()
let myBlock : EmptyBlock = {
print("In Swift EmptyBlock...")
}
myClassOC.invoke(myBlock)
I end up with EXC_BAD_ACCESS on this line:
EmptyBlock emptyBlock = blk;
Any idea what's going on here?
The reason is probably the support introduced in Swift 3 for any Swift type to be represented as an opaque object of id type in Objective-C. Read Swift Value Types in Objective-C in this Apple blog post for details. In that you will see that Swift types are passed as _SwiftValue * when the parameter type is id, this is an opaque type.
The argued benefit of this approach is that Swift value types can be stored in Objective-C collections; however the disadvantage is that you cannot convert to an Objective-C compatible value on the Objective-C side. Debug your code and you'll see the block is being passed as a _SwiftValue * and not an Objective-C block type.
Declare the bak parameter to have EmptyBlock type and your code works.
HTH
Because Swift closures and Objective-C blocks are not the same things. Swift automatically converts a closure to an Objective-C block if they see that you are calling a method with an Objective-C block type, but it doesn't do that otherwise. And as CRD's answer mentions, in Swift 3 any Swift value can be represented as an Objective-C object, so even though it sees that it expects an Objective-C object, it still doesn't know you want an Objective-C block because a Swift closure can still be bridged to an opaque object.
The only way I figured out to pass it from Swift and work is something like:
myClassOC.invoke(myBlock as #convention(block) () -> Void)

How do I expose a Swift global function to Objective-C? [duplicate]

the documentation says:
Global constants defined in C and Objective-C source files are automatically imported by the Swift compiler as Swift global constants.
But it doesn't say anything about the other way around. I need to define a global swift constant and be able to see it one the objective c side like a global c constant. Like on the swift side define:
public let CARDS = ["card1", "card2"]
and see use it on the objective c side like
NSLog(#"Cards count: %d", [CARDS count])
What should I do? I've already imported the swift automatically generated header like:
#import "MyProject-Swift.h"
and in Xcode if I command-click on it, it takes me to the correct place in the swift code, but at compile time I get:
'User of undeclared Identifier CARDS'
on my objective c side.
Here is the document about it
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
Global variables (including constants) are unaccessible from Objective-C.
Instead, you have to declare a class which has accessors for the global constants.
// Swift
public let CARDS = ["card1", "card2"]
#objc class AppConstant {
private init() {}
class func cards() -> [String] { return CARDS }
}
// Objective-C
NSArray *cards = [AppConstant cards];
Nice answer by #rintaro, but another alternative simple Swift answer for constants that can be used in both Swift and Objective-C:
#objcMembers
class MyConstants: NSObject {
static let kMyConstant1 = "ConstantValue1";
static let kMyConstant2 = "ConstantValue2";
static let CARDS = ["card1", "card2"]
}
You can access this on both Swift and Objective-C by:
MyConstants.kMyConstant1 // this will return "ConstantValue1"
MyConstants.CARDS // this will return array ["card1", "card2"]
Swift global functions (ie. swift top-level functions) cannot be accessed by objC. Period. End of story.
See above answer from rintaro, to wit...
"This excludes Swift-only features such as those listed here:
. . .
Top-level functions defined in Swift"
boom

Can Objective-C code call Swift class extensions?

I searched some posts, I think I cannot write an extension under Swift, and call it from Objective-C code, right?
#objc like attributes only support methods, class, protocols ?
You can write a Swift extension and use it in Objective-C code. Tested with Xcode 6.1.1.
All you need to do is:
create your extension in Swift (#objc annotation needed since Swift 4.0.3)
#import "ProjectTarget-Swift.h" in your Objective-C class (where "ProjectTarget" represents the XCode target the Swift extension is associated with)
call the methods from the Swift extension
I found out that in Swift 4.0 I had to add #objc in front of my extension keyword in order for the Swift extension methods to become visible by an instance of the Objc class I was extending.
In short:
File configuration setup:
CustomClass.h
CustomClass.m
CustomClassExtension.swift
In CustomClassExtension:
#objc extension CustomClass
{
func method1()
{
...
}
}
In my AppDelegate.m:
self.customClass = [[CustomClass alloc] init];
[self.customClass method1];
This solution works for Swift 2.2 and Swift 3. Note that only extensions for classes (not for structs or enums) will be accessible from Objective-C.
import UIKit
extension UIColor {
//Custom colours
class func otherEventColor() -> UIColor {
return UIColor(red:0.525, green:0.49, blue:0.929, alpha:1)
}
}
Then #import "ProductModuleName-Swift.h" in your ObjC file.
Swift 4
extension UIColor {
// As of Swift 4.0.3, the #objc annotation is needed if you want to use the extension in Objective-C files
#objc
class func otherEventColor() -> UIColor {
return UIColor(red:0.525, green:0.49, blue:0.929, alpha:1)
}
}
As covered in the other answers, importing the generated Swift header works in most cases.
An exception to this is when the category is defined on a bridged type (i.e. the extension is defined on String and not NSString). These categories will not automatically be bridged to their Objective-C counterparts. To get around this, you'll either need to use the Objective-C type (and cast the return value in your Swift code with as String) or define an extension for both the Swift and Objective-C types.
Import "#import "ProductModuleName-Swift.h" header in objective-c file and add #objc infront of your extentsion in swift file. It will working fine in swift 4.2 and swift 5
If think you have configured everything correctly (marked your Swift entities with #objcMembers or #objc, imported the bridging header "ProductModuleName-Swift.h" and so on) – but still getting the No visible #interface for 'FooClass' declares the selector 'fooSelector' error:
Check if you see your interfaces in the bridging header by ctrl + cmd clicking on it. If you don't, check out my answer to this question: Swift to Objective-C header does not contain Swift classes

Swift globals and global functions in objective c

the documentation says:
Global constants defined in C and Objective-C source files are automatically imported by the Swift compiler as Swift global constants.
But it doesn't say anything about the other way around. I need to define a global swift constant and be able to see it one the objective c side like a global c constant. Like on the swift side define:
public let CARDS = ["card1", "card2"]
and see use it on the objective c side like
NSLog(#"Cards count: %d", [CARDS count])
What should I do? I've already imported the swift automatically generated header like:
#import "MyProject-Swift.h"
and in Xcode if I command-click on it, it takes me to the correct place in the swift code, but at compile time I get:
'User of undeclared Identifier CARDS'
on my objective c side.
Here is the document about it
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
Global variables (including constants) are unaccessible from Objective-C.
Instead, you have to declare a class which has accessors for the global constants.
// Swift
public let CARDS = ["card1", "card2"]
#objc class AppConstant {
private init() {}
class func cards() -> [String] { return CARDS }
}
// Objective-C
NSArray *cards = [AppConstant cards];
Nice answer by #rintaro, but another alternative simple Swift answer for constants that can be used in both Swift and Objective-C:
#objcMembers
class MyConstants: NSObject {
static let kMyConstant1 = "ConstantValue1";
static let kMyConstant2 = "ConstantValue2";
static let CARDS = ["card1", "card2"]
}
You can access this on both Swift and Objective-C by:
MyConstants.kMyConstant1 // this will return "ConstantValue1"
MyConstants.CARDS // this will return array ["card1", "card2"]
Swift global functions (ie. swift top-level functions) cannot be accessed by objC. Period. End of story.
See above answer from rintaro, to wit...
"This excludes Swift-only features such as those listed here:
. . .
Top-level functions defined in Swift"
boom

Call custom Objective-C methods in Swift?

I'm currently taking several Swift classes online to figure this new syntax out, but I feel like I've run into a wall that I can't really figure out.
My question is: What do I need to do to call custom Objective-C methods inside Swift? Why doesn't Swift recognize where the code is coming from? Why do I get errors like, "UIViewController does not have a member named 'customMethod'"? Is it an import problem?
Now that you know my question, here is why I'm asking and the background
I have an objective-c .m file that calls this method:
- (void)configureMyApp
{
// overridden by subclasses
}
Then this one:
- (void)addAppElement:(MyAppElement *)element
{
[self addAppElement atIndex:[self.elements count]];
}
In my Swift app I've set up the bridging header and imported the .h file into said header file
I attempted calling the configureMyApp method through this Swift function:
func configureMyApp()
{
self.addAppElement...
}
but it doesn't recognize that function as coming from my imported objective-c file, and neither does myswiftfile.swift recognize addAppElement
The way I would call it in obj-c would be this:
- (void)configureMyApp
{
[super configureMyApp];
[self addAppElement:.....];
}
Is there anything else I need to do to import the obj-c code? What could I be missing?
This error: UIViewController does not have a member named 'customMethod' indicates that you're trying to call customMethod on an object typed as UIViewController, not as your custom subclass of UIViewController.