20

I'm attempting to use Swift classes in my Objective-C code, however my Swift classes don't seem to appear in the generated header. As a result, my build fails with "Use of undeclared identifier 'HelloWorld'".

I used the templates to create a project called TestApp.

I have the following Build Settings in my target:

  • Product Name : TestApp
  • Product Module Name : TestAppModule
  • Defines Module : Yes

Apple's documentation says to use #import <TestApp/TestAppModule-Swift.h> but this doesn't work.

Instead, I'm using #import "TestAppModule-Swift.h" in my ".m" file. It seems to find this.

I'm able to navigate to it, and it looks like this...

// Generated by Swift version 1.0 (swift-600.0.34.4.5)

#if defined(__has_include) && __has_include(<swift/objc-prologue.h>)
# include <swift/objc-prologue.h>
#endif

...etc...

but no classes defined in there.

I have a Swift file in the project that looks like this...

class HelloWorld {    
    func hello() {
        println("hello world")
    }
}

Why isn't this working using the standard header file location #import <TestApp/TestAppModule-Swift.h>?

How can I get my swift classes in that header file, so I won't get the "undeclared identifier" error?

TJez
  • 1,969
  • 2
  • 19
  • 24

8 Answers8

22

Here's how I have gotten it to work. You can see a more large-scale answer here.

Change this:

class HelloWorld {    
    func hello() {
        println("hello world")
    }
}

To:

@objc class HelloWorld { 

    class func newInstance() -> HelloWorld {
        return HelloWorld()
    }

    func hello() {
        println("hello world")
    }
}

Then, In your ObjC file:

#import "TestApp-Swift.h"

And call like this:

HelloWorld * helloWorld = [HelloWorld newInstance];
[helloWorld hello];
Community
  • 1
  • 1
Logan
  • 52,262
  • 20
  • 99
  • 128
  • Thanks! But edit please your answer ... "class func newInstance()" – TJez Jun 06 '14 at 16:54
  • Thanks for pointing that out @TroyJ -- no compiler warnings on SO :) – Logan Jun 06 '14 at 16:58
  • 2
    Update: Mentioning @echelon answer here as a comment to the official answer, so that it might help someone. I had everything in place. The objc keyword, my swift class a sub class of NSObject. Still it wasn't working. Turns out, as mentioned by echelon in his answer, we need to have the bridging header in place, even though we won't be calling obj-c in Swift code. Weird, but it worked. – Naz Mir May 18 '15 at 08:54
  • what if we wanted to add some instance variables to `HelloWorld`? For example: `var name:String` and `var idNumber:Int`? When I do that I get all sorts of crazy errors re incorrect initialization. I tried a half-dozen variations of solutions - nothing works. What would be the correct way to solve this? – sirab333 May 31 '15 at 01:13
  • So, your problems on this one are two-fold @sirab333. The 'incorrect initialization' error is because `var name: String` is a non optional, which means it must have value before leaving initialization context. You can get this without building an initializer by replacing it with `var name = ""`. This should work in ObjC. The second problem is type `Int?`. In objective-c, `int` or `NSInteger` types can never be nil, so this won't work. Change this one to `var idNumber = 0`. Good luck :) – Logan May 31 '15 at 01:36
  • @Logan, yes, if you provide default values to your vars you'll avoid the errors - but hard-coding default values to all your vars isn't very elegant, especially if you have a whole slew of them. I was trying to do this with an `init` method - which is where I was running into all the issues. My current solution is to provide a `setValues` method that does exactly what a true designated `init` method would do, which is let you pass in values for all the Class' properties/vars - but can it be done with a true `init`? – sirab333 May 31 '15 at 13:55
  • @sirab333 - https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2-ID1 -- see 'Objects and Classes' – Logan May 31 '15 at 14:15
  • Oh yeah, read that one before, up and down, many times :-) In the meantime, I figured out a way to get this to do what I need - it almost renders the accepted answer up above invalid. That being said, the original poster didn't ask about setting values for class variables, passing arguments, or init'ing, only about instantiation - and the answer did address that 100%, so its correct. There's just a lot more to this...but it probably belongs in a different question so I'll leave it be in order to not "dilute" this topic with other related/off-topic things... Thanks for your help though! – sirab333 May 31 '15 at 15:01
  • I've updated my answer below to explain the subtle problem whereby unless a class is explicitly marked `public`, it will not appear in the `"ModuleName-Swift.h"` header unless you also have a bridging header in your project. – Echelon Dec 02 '15 at 09:16
  • For Xcode Version 9.4 (9F1027a) : Make sure you clean and then build up to at least 8 times before you decide its not working. For me cleaning and then building allowed the Xcode project (original Swift created project) to take a few runs at building up the symbol tables, decide to create the #import "MsbHrv-Swift.h" header, and then re-run the symbol tables for the compiler. So, after a clean or code change it usually takes my Xcode 9 1 qty CLEAN then 3 qty (subsequent) BUILD. After 8 subsequent builds I go look for the problem. – Matthew Ferguson Jun 11 '18 at 02:15
21

tl;dr Ensure you have a bridging header if you're doing any cross-calling between Objective-C and Swift.

I had the exact same problem: I could see the -Swift.h file in DerivedData but it made no mention of my Swift classes. I was importing the header file correctly, the Defines Module setting was YES, and the Product Module Name was correct. I tried deleting and re-adding the Swift files, clean buiild, quitting XCode, etc, with no luck.

Then I realised I had no -Bridging-Header.h file in my project, presumably due to the way I'd cobbled it together from a previous project. Shouldn't be a problem because I was not (yet) calling Objective-C from Swift. But when I added a bridging header, and referred to its path in the build settings (Swift Compiler - Code Generation -> Objective-C Bridging Header), it magically fixed the problem - my -Swift.h file was suddenly full of SWIFT_CLASS() goodness!

So I'm guessing the bridging header is fundamental to the process, even if you're NOT using Objective-C from Swift.


UPDATE: I finally understand this. It is related to public/internal access modifiers. Not sure if I missed this originally or if it's an addition to the Apple docs, but it now clearly states:-

By default, the generated header contains interfaces for Swift declarations marked with the public modifier. It also contains those marked with the internal modifier if your app target has an Objective-C bridging header.

Echelon
  • 7,306
  • 1
  • 36
  • 34
  • The bridging header was not created in my case even when I created a new Swift file. This caused a problem when I wanted to use a Swift class from an Objective C class. The problem was resolved by manually adding a bridging header. Thank you!! – John Jul 16 '15 at 14:00
  • 1
    How can I use non public swift classes if I have a framework, not app. Using bridging headers with framework targets is unsupported. – Radost Aug 09 '19 at 12:20
12

It is proper to use #import "TestAppModule-Swift.h" in your .m files. If you need to reference a class in a .h, use the @class forward declaration.

Further, if you want to use a Swift class from Objective-C, the Swift class must be marked with the @objc attribute. Xcode will only include classes with that attributed in the generated header. See also this documentation.

Matt Bridges
  • 48,277
  • 7
  • 47
  • 61
  • 1
    Thanks for pointing out the @objc annotation, I gave you an upvote. I also needed to either extend from NSObject, or use a newInstance class method. Logan gave a complete, correct answer with an example. – TJez Jun 06 '14 at 16:57
  • 1
    Thank you very much ! Add -Swift.h files at the top of my .m files fix the problem for me ! – Jean Lebrument Jun 17 '14 at 12:54
  • 2
    The documentation states: "If your Swift class is a descendant of an Objective-C class, the compiler automatically adds the `@objc` attribute for you." However under the latest Xcode 7 this appears to no longer be the case (at least for a `UIViewController` subclass). I had to add the annotation manually. – simeon Jul 27 '15 at 07:02
  • For reference, with Swift 4 compiler you need to add `@objc` to all Swift class methods you want to use in Objective-C code! I wish to get a few hours of my life back I spend trying to solve it... – Krzysztof Skrzynecki Nov 26 '19 at 20:53
10

Class should be declared as @objc public class

Ievgen
  • 1,596
  • 16
  • 13
  • Unless you have a bridging header. See my answer for the explanation. The problem is, sometimes people subclass from the Cocoa framework without explicitly marking the subclass (and all overridden methods) as `public` because it's less typing and it will happily work with the default `internal` access anyway. And it's arguably correct, from an OO standpoint. This then causes a problem when they want to expose the class to Objective-C and they don't have a bridging header. – Echelon Dec 02 '15 at 09:24
  • Adding the public qualifier is important when your Swift code is in a Framework (Pod). – Visionscaper Nov 13 '20 at 00:31
3

A more convenient way would be to inherit from NSObject. Like so:

class HelloWorld: NSObject {    
    func hello() {
        println("hello world")
    }
}
Simon
  • 1,076
  • 7
  • 13
1

In my case, by following Apple guidelines, it did not work until I ran the project. The xcode editor kept flagging the unknown swift class, until i clicked "run". The build succeeded, and the swift method worked.

Rowan Gontier
  • 821
  • 9
  • 14
0

In my case the class was not being compiled, because I first added it to my test target only... After adding it to my main target (Build Phases -> Compile Sources), it was actual compiled and added to the header file.

So much for TDD ;-)

Hendrik
  • 41
  • 2
0

Maybe you defined a Swift class with the same name as an existing Objective-C class which wouldn't be unusual if you want to refactor your Objective-C code to Swift.

As long as you have a class defined simultaneously in Swift and Objective-C the compiler quietly stops updating the bridging header altogether ("ProductModuleName-Swift.h") - which also affects subseqeuent changes in other bridged Swift files.


For general reference how to import Swift into Objective-C see: Importing Swift into Objective-C | Apple Developer Documentation

Tysac
  • 257
  • 2
  • 11