Can Objective-C code call Swift class extensions? - objective-c

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

Related

Obj-C classes inheriting from Swift classes cannot be used by Swift code?

I have a class that is defined in Swift:
// SwiftClass.swift
#objc
class SwiftClass: NSObject {
// ...
}
An Objective-C class can inherit from that Swift class:
// ObjCObject.h
#import "MyModule-Swift.h"
#interface ObjCObject: SwiftClass
// ...
#end
However, when doing so, I cannot use that class in Swift code anymore.
To use it in Swift, I have to add it to the bridging header:
// MyModule-BridgingHeader.h
#import "ObjCObject.h"
as only classes referenced by the bridging header are visible to Swift code. And this creates a cyclic reference, as the bridging header must be processed prior to compiling the Swift source code yet the generated Swift header (MyModule-Swift.h) only exists after compiling the Swift source code and thus won't exist when the bridging header is being processed.
If it was just for method arguments, I could use forward declarations but an Obj-C class cannot inherit from a forward declared class.
So am I just missing something here or is it really the case that you can use Obj-C classes in Swift and Swift classes in Obj-C but you cannot use an Obj-C class in Swift if it inherits from a Swift class?

Writing Swift 2.0 code inside Objc *.m file

I'm trying to use a swift classes methods from my objective-c .m file but I can't get my file to import.
In Build Settings -> Packaging I have: Defines Module YES
my Product Module Name is Library
In Build Options I have set Embedded Content Contains Swift Code as YES
However, when I attempt to import the target module to my *.m file I get file not found error.
#import "Library-Swift.h"
Fails to import.
I have swift files. Any ideas?
I'm also trying to use `"Library-Bridging-Header.h"
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "PLAddBookViewController.h"
..but it doesn't seem to be doing anything.
You can expose your Swift class to ObjC in 2 ways:
class MySwiftClass : NSObject { // inherit from any ObjC class
}
#objc class MyClassClass { // tell the compiler to expose it to ObjC
}
From the Swift book:
When you define a Swift class that inherits from NSObject or any other Objective-C class, the class is automatically compatible with Objective-C. If your Swift class does not derive from an Objective-C class and you want to use an API from that class in Objective-C code, you can use the #objc attribute described below.
The #objc attribute makes your Swift API available in Objective-C and the Objective-C runtime. In other words, you can use the #objc attribute before a Swift method, property, subscript, initializer, class, protocol, or enumeration to use it from Objective-C code.

How would you incorporate a Swift struct/enum into Objective-C?

Scenario: I've exposed the Objective-C file to Swift via the .h bridge; so I can launch the Objective-C via Storyboard from .Swift.
However,
I have some global enum & structs declared in Environment.Swift file that ObjC needs:
enum BarButtonItem:Int {
case cancel = 1
case back
case save
case activate
case upload
case share
}
Accessing enum within Swift:
#IBAction func navButtonAction(sender: UIBarButtonItem) {
if let buttonItem = BarButtonItem(rawValue: sender.tag) {
switch buttonItem {
case .save:
println("{(3.3)AccessBoundaries} Save.")
default:
println("")
}
}
self.dismissViewControllerAnimated(true, completion: nil)
}
I want to access this (and other data types) in an Objective-C file:
- (IBAction)barButtonAction:(UIBarButtonItem *)sender {
BarButtonItem....?
}
Xcode doesn't recognize this.
How do I expose environment data types (or can I?) defined in a .Swift file to an Objective-C file?
...and, is it possible to interpret a .Swift's struct in Objective-C?
There are a number of Swift features that can't be used in Objective-C. I posted an answer to a question the other day that asked something similar. I'll re-post the answer here:
According to Apple's docs on Swift and Objective-C compatibility:
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

"ProductModuleName-Swift.h" not exporting all Swift classes

I have defined two Swift classes in my Xcode project. Everything else is Objective-C.
To use the classes in Objective-C I'm trying to import ProductModuleName-Swift.h but the file contains only the definition for one of the Swift classes. (SearchViewController)
This class is being exported:
class SearchViewController : UIViewController {
but this isn't:
public class Socket {
From "Migrating Your Objective-C Code to Swift" in the "Using Swift with Cocoa and Objective-C" documentation:
To be accessible and usable in Objective-C, a Swift class must be a descendant of an Objective-C class or it must be marked #objc.
When you bring Swift code into Objective-C, remember that Objective-C won’t be able to translate certain features that are
specific to Swift. For a list, see "Using Swift from
Objective-C".
SearchViewController is a descendent of an Objective-C class, but Socket is not.
To make it usable in Objective-C, declare it as a subclass of NSObject:
public class Socket : NSObject { ...
or declare it as
#objc public class Socket { ...

Swift protocol in Objective-C class

I wrote SearcherProtocol in Swift and need to implement an Objective-C class FileSearcher which has to use this protocol.
So I tried this:
#import <Foundation/Foundation.h>
#interface FileSearcher : NSObject <SearcherProtocol>
// ... class content
#end
The compiler tells me
Cannot find protocol declaration for 'SearcherProtocol'
The corresponding bridged header file (modulename-Swift.h) is being imported within FileSearcher.m.
Importing SearcherProtocol into FileSearcher.h throws another compiler error: module name-swift.h file not found
Does anybody have any clue what I'm doing wrong?
I'm using Xcode 6 Beta 5.
Edit
Here is the protocol declaration in Swift:
#objc protocol SearcherProtocol
{
var searchNotificationTarget: SearchCompletedProtocol? { get }
var lastSearchResults: [AnyObject] { get set }
func search(searchParam: String, error: NSErrorPointer) -> Bool
}
And the SearchCompletedProtocol:
#objc protocol SearchCompletedProtocol
{
func searchCompletedNotification(sender: AnyObject!)
}
There are two common reasons for this occuring:
Getting the module name wrong, see my answer.
Having a circular reference - see mitrenegades answer below.
1. Get the module name right:
If both the swift protocol and and Objective C are in the same project then according to apple you should just need to make sure you get the correct module name.
For Xcode6 beta 5 you can find it under BuildSettings->Packaging->Product Module Name
A common mistake would be to think that each swift file/class gets its own file, but instead they are all put into one big one that is the name of the project.
A further mistakes are if the module name has spaces, these should be replaced with underscores.
Edit:
With your protocol I created a test project called 'Test' which compiles perfectly and it has the files:
TestObjClass.h
#import <Foundation/Foundation.h>
#import "Test-Swift.h"
#interface TestObjCClass : NSObject <SearcherProtocol>
#end
TestObjClass.m
#import "TestObjCClass.h"
#implementation TestObjCClass
#end
TestProtocol.swift
import Foundation
#objc protocol SearcherProtocol
{
var searchNotificationTarget: SearchCompletedProtocol? { get }
var lastSearchResults: [AnyObject] { get set }
func search(searchParam: String, error: NSErrorPointer) -> Bool
}
#objc protocol SearchCompletedProtocol
{
func searchCompletedNotification(sender: AnyObject!)
}
2. Avoid circular reference:
Mitrenegades answer explains this, but if your project needs to use the explicit objc class that uses the swift protocol, (rather than just using the protocol) then you will have circularity issues. The reason is that the swift protocol is defined to the swift-objc header, then to your obj-c class definition, which then goes again to the swift-objc header.
Mitrenegades solution is to use an objective-c protocol, is one way, but if you want a swift protocol, then the other would be to refactor the code so as to not use the objective-c class directly, but instead use the protocol (e.g. some protocol based factory pattern). Either way may be appropriate for your purposes.
When you have
#import "moduleName-Swift.h"
in the .h file that you want to be a delegate, and you have that .h file also in the bridging headers file, there's a circular reference that causes the moduleName-Swift.h to fail compilation. for #james_alvarez's test project, it's probably working because you don't need to include TestObjClass.h into the bridging header.
The best way for me to combine objc files that need to be the delegate for a class written in swift, but that also needs to be included in the bridging header so other swift files can access this objc class, is to create a separate protocol file in objc:
MyProtocol.h:
#protocol MyDelegate <NSObject>
-(void)didDoThis;
-(void)didDoThat;
#end
ViewController.h:
#import "MyProtocol.h"
#interface ViewController : UIViewController <MyDelegate>
MyProject-Bridging-Header.h
#import "MyProtocol.h"
#import "ViewController.h"
I know this was a long time ago, but I just struggled with the same problem when adding a protocol to my Swift code, and it wasn't being added to the -Swift.h header file, hence "Cannot find protocol declaration"
The problem was my protocol wasn't marked as Public. I changed my protocol from this:
#objc protocol MyProtocol { //etc.... }
to this:
#objc public protocol MyProtocol { //etc.... }
I'm still not entirely sure why I need 'Public' but nobody else seems to, but hey it works...
Make sure you are including the auto generated Swift header in your ObjectiveC file. It will have the same name as your project module followed by -Swift.h.
For instance if your Project Module is MyTarget, then you would use:
#import "MyTarget-Swift.h"
If you are typing in the import into your Objective C file, it will not autocomplete. You can verify that you have the correct file by Command-clicking on the header after typing it in.
You can do the conformance part from Swift side 😁
So you have a swift protocol and want to make an Objective-C type conforms to it,
Swift Side
Add #objc to your protocol to make it visible to Objective-C world.
#objc protocol IndianCooking {
func cookChicken()
}
Objective-C Side
In the implementation .m file you do the following:
#import "YourProject-Swift.h"
#interface Cheef ()<IndianCooking> {
}
and in the header file .h add the method
cookChicken()
Import delegate as like this in .h file
#protocol AnalyticProtocol;
and add this in to .swift file
#objc public protocol AnalyticProtocol {
}
Try adding #import "Product_Module_Name-Swift.h" to your Product_Module_Name-Prefix.pch file. That fixed it for me, plus you will now have access to your swift files from any objc file.