Skip to content

Instantly share code, notes, and snippets.

@loganwright
Last active August 29, 2015 14:03
Show Gist options
  • Save loganwright/c1323d81933b743f3c33 to your computer and use it in GitHub Desktop.
Save loganwright/c1323d81933b743f3c33 to your computer and use it in GitHub Desktop.

Swift is great, but there's a lot of useful libraries that still exist in ObjC that we want to be able to use alongside our swift projects. Let's look at how to use Objective-C code in a Swift project!

Let's start a new project. A single view project is fine for what we're doing, just make sure the language is set to Swift. Once a project is created, I'm personally a big fan of running it before I add anything just to make sure everything is configured properly.

Ok, everything is working fine, let's move right in and add an Objective-C class. When you click 'New File' you'll see a new option with Xcode 6 called 'Cocoa Class'. Click that. Once the window is up, let's name our class 'ColorfulLabel', make it a subclass of UILabel, and make sure that our language is set to Objective-C. This way Xcode will generate our .h and .m file simultaneously. Click "Next" and then "Create" our new class.

You should be hit with a prompt that says "Would you like to configure an Objective-C bridging header?". You should click YES. Don't worry if you didn't click yes, or accidentally deleted your bridging header. We'll delete ours now for demonstration purposes.

To create a bridging header, we only need to add a new header file and name it "<#YourProjectModuleName#>-Bridging-Header.h". For our project, it will be named ObjectiveSwift-Bridging-Header.h because "by default, your product module name is the same as your product name. There are however, a few exceptions.

"If your product name has any nonalphanumeric characters, such as a period (.), they are replaced with an underscore (_) in your product module name. If the name begins with a number, the first number is replaced with an underscore.

You can also provide a custom name for the product module name, and Xcode will use this when naming the bridging and generated headers. To do this, change the Product Module Name build setting."

Ok, we've added the files for our ColorfulLabel class, and we have a bridging header, but Swift doesn't know about our class yet. Let's go to the bridging header file we made and import our new class. We'll do this by adding:

#import "ColorfulLabel.h"

On my computer autocomplete wasn't really picking up the file, so don't worry if this is the case for you. At the time of writing this, Xcode 6 is in beta, so this will likely be improved in later iterations. For now however, we have to do a lot of our work without the comfort of autocomplete. Make sure to double check your spellings if you get weird errors.

Now let's go into our ColorfulLabel.h and define our interface. For now, we're going to just add one simple method, and one simple property that we can use for demonstration.

#import <UIKit/UIKit.h>

@interface ColorfulLabel : UILabel

@property (strong, nonatomic) NSArray *availableColors;

- (void) changeColor;

@end

As you have likely surmised, our method will change the color of the label, and availableColors will have our color options. This will make more sense when we start using them, so let's just move on to our implementation. (S: I'm including notes in code for as you're typing.

#import "ColorfulLabel.h"

@implementation ColorfulLabel

// Dragged from code snippets
- (instancetype)init {
    self = [super init];
    if (self) {
    
        // Set default background color for debugging.
        self.backgroundColor = [UIColor grayColor];
    }
    return self;
}

// Add our method
- (void) changeColor {
}

@end

This is a pretty base level class and we haven't built much functionality yet. Our only real addition is setting the background color to gray in the init method. This is done so we can see it and make sure it's working. Let's go to ViewController.swift and start playing with our label to make sure everything works so far! Because we already imported our class into the bridging header, there's no need for us to import explicitly in ViewController.swift.

In viewDidLoad()

var colorfulLabel = ColorfulLabel()
colorfulLabel.bounds = CGRect(x: 0, y: 0, width: 100, height: 100);
colorfulLabel.center = self.view.center
colorfulLabel.text = "Hello World!"

// Notice new constants syntax. 
colorfulLabel.textAlignment = NSTextAlignment.Center
self.view.addSubview(colorfulLabel)

Sometimes I get errors showing that aren't really there, or swift has a hard time finding classes declared in the bridging header. If you think Xcode is incorrect, sometimes running a quick clean or build can help it find everything. Let's build and run our code to see if our custom view is working.

That worked! We just used Objective-C in our Swift project! Let's take it a bit further though, because our class doesn't do very much. Let's fill in our changeColor method in ColorfulLabel.m so that it changes the background color to a random color from our array.

- (void) changeColor {
    NSInteger randomIndex = arc4random_uniform(self.availableColors.count);
    UIColor * colorToChangeTo = self.availableColors[randomIndex];
    self.backgroundColor = colorToChangeTo;
}

That should do it, but now we need something to call it! Let's go back to our ViewController.swift file and start by declaring a global property for our label. Then let's add a gesture recognizer to our file as well that can trigger our changeColor method. When you're all done, ViewController.swift should look like this: (S: Maybe just read this while you're typing and skip this last line)

import UIKit

class ViewController: UIViewController {
    
    // The term "lazy loading" is something most Obj-C programmers are familiar with.  Swift incorporated it as part of the language.  What it's basically saying is that we aren't going to do any of the loading code until we first call our variable.  This can be useful if you don't want to load your properties all at once, or if you only want to load an object in to memory if & when you need it.  For this label, there likely won't be much of a difference, its just a cool language feature that we can use for an example. (J: Skip this if you want and just make it a normal variable)
    @lazy var colorfulLabel: ColorfulLabel = ColorfulLabel()
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        // Add label to view
        colorfulLabel.bounds = CGRect(x: 0, y: 0, width: 100, height: 100);
        colorfulLabel.center = self.view.center
        colorfulLabel.text = "Hello World!"
        colorfulLabel.textAlignment = NSTextAlignment.Center
        
        // **
        Add this line
        colorfulLabel.availableColors = [UIColor.yellowColor(), UIColor.redColor(), UIColor.blueColor(), UIColor.greenColor(), UIColor.cyanColor()]
        // **
        
        self.view.addSubview(colorfulLabel)
        
        // Add gesture recognizer to view
        var tapGesture = UITapGestureRecognizer()
        // Notice how we declare our action as a string.  This is similar to Objective-C selector syntax, but without the @selector().  If you prefer to see that, you can declare it a selector explicitly like so: Selector("handleTap:").  Either way is fine.
        tapGesture.addTarget(self, action: "handleTap:")
        self.view.addGestureRecognizer(tapGesture)
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func handleTap(tap: UITapGestureRecognizer) {
    
        // Here we'll call the changeColor method on our label.  Notice how it adapts to Swift syntax
        colorfulLabel.changeColor()
    }

}

Let's run that and see if it works. Our label should change colors when we 'tap' the screen.

Great, everything seems to be running, and that wasn't too hard. Before we move on to using Swift classes in Objective-C, let's look at a method that takes an argument. In fact, let's make a method with two arguments so we can demonstrate how Swift will adapt that code. Let's start with ColorfulLabel.h and add:

- (void) sayHelloToName:(NSString *)name withTextColor:(UIColor *)textColor;

Now let's flush that out in our implementation:

- (void) sayHelloToName:(NSString *)name withTextColor:(UIColor *)textColor {
    self.text = [NSString stringWithFormat:@"Hello %@!", name];
    self.textColor = textColor;
}

It's a pretty simple method that make's the label's text change and also changes the text color. Let's call this one in handleTap: as well:

    colorfulLabel.sayHelloToName("Mars", withTextColor: UIColor.whiteColor())

This feels fairly natural, and it doesn't look to much different from our Objective-C method call. One thing that you may have noticed is that our arguments were replaced with optionals. (String? & UIColor?). Well actually, Swift didn't change a thing. In Objective-C, any object can be nil, so they can be considered for our purposes 'inherently optional'. Swift doesn't function this way and objects need to be declared as optionals explicitly in order for them to ever be nil. To maintain compatability, Swift makes a smart adjustment. Obviously, this only touches on the capabilities of Objective-C classes, so let's talk about a few more advanced cases. External Constants, Enums, and Blocks. It's harder to contrive examples for these, so let's just go over them briefly. If you'd like to skip to Swift in Objective-C, you can skip ahead to around [insert timestamp here] -- (if possible)

External Constants

In ColorfulLabel.h

FOUNDATION_EXTERN NSString * const CLSomeExternalConstant;

In ColorfulLabel.m

NSString * const CLSomeExternalConstant = @"CLSomeExternalConstant";

Now, if you want to use it in Swift, just use it, again because ColorfulLabel.h is already imported in our bridging header, we don't need to add anything!

println("Constant: \(CLSomeExternalConstant)")

ENUMS

Let's look at a fairly straightforward enumeration in ColorfulLabel.h

typedef NS_ENUM(NSUInteger, SomeEnum) {
    SomeEnumOne,
    SomeEnumTwo,
    SomeEnumThree
};

Swift is going to modify this slightly, so let's go and look at how it handles it. In Objective-C we would use the full names, but Swift adapts this definition to incorporate dot notation. In Swift, we will use SomeEnum like this:

SomeEnum.One
SomeEnum.Two

Because we declared our enum values with the name as a prefix (standard Objective-c practice) Swift can smartly adjust to compensate. If we need the raw numeric values, we simply call toRaw() like we would on any other Swift enum.

println("SomeEnum: \(SomeEnum.One.toRaw())")

Now let's look at a block. A fairly common use case is for blocks to be used as completions for methods. Let's use this for our example.

In ColorfulLabel.h

- (void) someMethodWithCompletionBlock:(void(^)(NSString *someString))completion;

We're not going to do much in this method except call the block, so let's add that to our implementation.

- (void) someMethodWithCompletionBlock:(void (^)(NSString *))completion {
    completion(@"String From Objective-C!");
}

Let's see how Swift encorporates this. Autocomplete isn't much help, so its best to know what's going to happen. Swift is going to convert our block to a closure. When we call the method, it could look something like this:

    colorfulLabel.someMethodWithCompletionBlock({ (someString: String!) in
        println("SomeBlock: \(someString)")
        })

If you're unfamiliar with closure syntax, you can check out the other lesson on the channel. You'll notice that swift also adapted the argument to an implicitly unwrapped optional. This is again a compensation for Objective-C.

If we put this method call somewhere like viewDidLoad() we should see the output in our console.

Great, everything's functioning properly. I think we've touched on some of the core class compatability for Objective-C, now let's look at how things work when using Swift in Objective-C. For lack of a better idea, let's consolidate some of our current label's code into a Swift class.

For our purposes, we'll consider Swift classes as two main types, those that derive from an NSObject, and those that do not. Remember that "NSObject is the root class of most Objective-C class hierarchies," so any of its descendents will also inherit its functionality. If you're using anything related to Cocoa, you're probably using a decendant of NSObject. With swift, a subclass is no longer required and objects can be declared in pure swift without a parent class. This changes compatability somewhat, so we'll touch on that as well. First let's talk about subclassing a cocoa object. Let's again add a new 'Cocoa Class', but this time, let's name it ColorfulLabelHelper, make it a subclass of NSObject, and set the language to Swift.

We should now have a class, ColorLabelHelper.swift, let's add some functionality. Why don't we start by encapsulating some of the color code. Our class should look like this:

import UIKit

class ColorfulLabelHelper: NSObject {
    
    let availableColors = [
        UIColor.yellowColor(),
        UIColor.redColor(),
        UIColor.blueColor(),
        UIColor.greenColor(),
        UIColor.cyanColor()
    ]
    
    func randomColor() -> UIColor {
        return availableColors[Int(arc4random_uniform(UInt32(availableColors.count)))]
    }
    
}

Now we will need to update some of our other classes to use this functionality. Let's start in ColorfulLabel.h. We're going to add our new class as a property so we'll need to add this line at the top of our class, above @interface.

@class ColorfulLabelHelper;

Now, we can add our property like any other class. Let's name our property 'helper'.

@property (strong, nonatomic) ColorfulLabelHelper *helper;

Now let's move to ColorfulLabel.m and incorporate our new functionality. First thing we need to do here is import the global swift header. This file is created for you automatically, and you shouldn't be alarmed if you don't see it. We won't really make any edits in the file itself. It should be named <#YourProjectModuleName#>-Swift.h, and it should be imported in your .m file only.

#import "ObjectiveSwift-Swift.h"

As usual, don't be alarmed if your autocorrect isn't kicking in. Just make sure to double check your spelling. Now we should be able to use our class. Let's lazy load our helper property so you can see how it looks in comparison to our lazy loaded swift property, particularly if you're unfamiliar with the concept. In ColorfulLabel.m:

- (ColorfulLabelHelper *) helper {
    if (!_helper) {
        _helper = [[ColorfulLabelHelper alloc]init];
    }
    return _helper;
}

As you can see, nothing explicitly marks this code as "lazy loading". It does however serve the same purpose. Our helper property isn't initialized until we need it. Now let's incorporate our helper into the colorChange method. When we're done, it should look like this.

- (void) changeColor {
    self.backgroundColor = [self.helper randomColor];
}

We can build and run the code like this! You just used a swift class in objective-c! We no longer have a need for the availableColors property, so let's remove it from the class, and remove its use in ViewController.swift. Ok, that's the basics, now let's look at some more advanced use cases. Let's look at a method that takes an argument by moving our greeting code to this swift class.

    func greetingWithName(name: String) -> String {
        return "Hello " + name + "!"
    }

Now let's utilize that in our ColorfulLabel.m in the sayHelloToName method. It should look like this:

- (void) sayHelloToName:(NSString *)name withTextColor:(UIColor *)textColor {
    self.text = [self.helper greetingWithName:name];
    self.textColor = textColor;
}

On my system, sometimes it showed this code as an error until I built and ran the project. Hopefully this will be resolved by the time Xcode exits beta. If you're getting an error constantly, try building and running your project with cmd + r to make sure there is actually a problem. Let's build and run anyways to make sure everything works.

That seems to be functioning properly, so let's add one more bit of functionality to our ColorfulLabelHelper.swift that incorporates a closure as an argument so we can see how Objective-C handles that. As you've likely noticed, contriving useful examples for this stuff can be difficult, so for this one, let's be straightforward and make it purely demonstrative. Here's our swift method declaration in ColorfulLabelHelper.swift:

    func methodWithClosureAsArgument(closure: (someString: String, someInt: Int) -> Void) {
        closure(someString: "Hello Closure", someInt: 4)
    }

Now let's call this method from our Objective-C code. You'll have to add this somewhere in ColorfulLabel.m. For lack of a better place, let's stick it in the ColorfulLabel.m init method. This way we can just see whether or not the code is functional. Here's the method call:

        [self.helper methodWithClosureAsArgument:^(NSString *someString, int someInt) {
            for (int i = 0; i < someInt; i++) {
                NSLog(@"SomeString: %@", someString);
            }
        }];

As you can see, we call our swift closure argument as a block in objective-c. For this particular purpose, its fairly useless, but one can imagine the use cases for asyncronous calls etc. Swift has a lot of additional language features not present in Objective-C which means a few things can't be used with cross compatability from swift to objc. These are:

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

Because we can't do enums or typealiases, we'll consider that a fairly comprehensive primer on swift to objc compatability. There is however the other use case I mentioned earlier when our class isn't a descendant of an Objective-C class. Swift classes aren't required to be subclasses of something else which means they might not inherit Objective-C compatability if the class isn't a subclass of an Objective-C object.

Let's remove the subclass code from our swift class and see what happens. Change this:

class ColorfulLabelHelper: NSObject {

to this:

class ColorfulLabelHelper {

If we try to build & run this, we'll probably get a few errors ... and we do. Let's talk about the first of these problems, which is that objective-c can't recognize any of the code. This is because we need to help objective-c find the code by adding an @objc prefix explicitly. We can do this by changing this:

class ColorfulLabelHelper {

to this:

@objc class ColorfulLabelHelper {

Now let's try and build again and see what happens. Most of our errors went away, but we're still stuck with one. "No known class method for selector 'alloc'". Remember that our swift class is no longer a descendant of NSObject and because of this, it no longer has access to its methods including alloc. If we want to create an instance, we'll need to write our own class function. Let's add this to our ColorfulLabelHelper.swift class.

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

Now let's swap out alloc]init] for our new method. Our objective-c lazy load should now look like this.

- (ColorfulLabelHelper *) helper {
    if (!_helper) {
        _helper = [ColorfulLabelHelper newInstance];
    }
    return _helper;
}

That's it, we should now be able to use our pure swift class normally in Objective-C. While this lesson probably isn't the best example of app design, it did give us an opportunity to tackle some cool swift & objective-c interoperability. Have fun experimenting on your own :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment