Integrating with Objective-C

In this article written by Kyle Begeman author of the book Swift 2 Cookbook, we will cover the following recipes:

  • Porting your code from one language to another
  • Replacing the user interface classes
  • Upgrading the app delegate

Introduction

Swift 2 is out, and we can see that it is going to replace Objective-C on iOS development sooner or later, however how should you migrate your Objective-C app? Is it necessary to rewrite everything again?

Of course you don't have to rewrite a whole application in Swift from scratch, you can gradually migrate it. Imagine a four years app developed by 10 developers, it would take a long time to be rewritten.

Actually, you've already seen that some of the codes we've used in this book have some kind of "old Objective-C fashion". The reason is that not even Apple computers could migrate the whole Objective-C code into Swift.

(For more resources related to this topic, see here.)

Porting your code from one language to another

In the previous recipe we learned how to add a new code into an existing Objective-C project, however you shouldn't only add new code but also, as far as possible, you should migrate your old code to the new Swift language.

If you would like to keep your application core on Objective-C that's ok, but remember that new features are going to be added on Swift and it will be difficult keeping two languages on the same project.

In this recipe we are going to port part of the code, which is written in Objective-C to Swift.

Getting ready

Make a copy of the previous recipe, if you are using any version control it's a good time for committing your changes.

How to do it…

  1. Open the project and add a new file called Setup.swift, here we are going to add a new class with the same name (Setup):
class Setup {

    class func generate() -> [Car]{

        var result = [Car]()

        for distance in [1.2, 0.5, 5.0] {

            var car = Car()

            car.distance = Float(distance)

            result.append(car)

        }

        var car = Car()

        car.distance = 4

        var van = Van()

        van.distance = 3.8

        result += [car, van]

        return result

    }

}
  1. Now that we have this car array generator we can call it on the viewDidLoad method replacing the previous code:
- (void)viewDidLoad {

    [super viewDidLoad];

    vehicles = [Setup generate];

    [self->tableView reloadData];

}
  1. Again press play and check that the application is still working.

How it works…

The reason we had to create a class instead of creating a function is that you can only export to Objective-C classes, protocols, properties, and subscripts. Bear that in mind in case of developing with the two languages.

If you would like to export a class to Objective-C you have two choices, the first one is inheriting from NSObject and the other one is adding the @objc attribute before your class, protocol, property, or subscript.

If you paid attention, our method returns a Swift array but it was converted to an NSArray, but as you might know, they are different kinds of array. Firstly, because Swift arrays are mutable and NSArray are not, and the other reason is that their methods are different.

Can we use NSArray in Swift? The answer is yes, but I would recommend avoiding it, imagine once finished migrating to Swift your code still follows the old way, it would be another migration.

There's more…

Migrating from Objective-C is something that you should do with care, don't try to change the whole application at once, remember that some Swift objects behave differently from Objective-C, for example, dictionaries in Swift have the key and the value types specified but in Objective-C they can be of any type.

Replacing the user interface classes

At this moment you know how to migrate the model part of an application, however in real life we also have to replace the graphical classes. Doing it is not complicated but it could be a bit full of details.

Getting ready

Continuing with the previous recipe, make a copy of it or just commit the changes you have and let's continue with our migration.

How to do it…

  1. First create a new file called MainViewController.swift and start importing the UIKit:
import UIKit
  1. The next step is creating a class called MainViewController, this class must inherit from UIViewController and implement the protocols UITableViewDataSource and UITableViewDelegate:
class
MainViewController:UIViewController,UITableViewDataSource,
UITableViewDelegate {
  1.  Then, add the attributes we had in the previous view controller, keep the same name you have used before:
 private var vehicles = [Car]()

    @IBOutlet var tableView:UITableView!
  1. Next, we need to implement the methods, let's start with the table view data source methods:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{

        return vehicles.count

    }

   

    func tableView(tableView: UITableView,
    cellForRowAtIndexPath indexPath: NSIndexPath) ->
    UITableViewCell{

        var cell:UITableViewCell? =
        self.tableView.dequeueReusableCellWithIdentifier
        ("vehiclecell")

        if cell == nil {

            cell = UITableViewCell(style: .Subtitle,
            reuseIdentifier: "vehiclecell")

        }

        var currentCar = self.vehicles[indexPath.row]

        cell!.textLabel?.numberOfLines = 1

        cell!.textLabel?.text = "Distance
        \(currentCar.distance * 1000) meters"

        var detailText = "Pax: \(currentCar.pax) Fare:
        \(currentCar.fare)"

        if  currentCar is Van{

            detailText += ", Volume: \( (currentCar as
            Van).capacity)"

        }

        cell!.detailTextLabel?.text = detailText

        cell!.imageView?.image = currentCar.image

        return cell!

    }

Pay attention that this conversion is not 100% equivalent, the fare for example isn't going to be shown with two digits of precision, there is an explanation later of why we are not going to fix this now.

  1.  The next step is adding the event, in this case we have to do the action done when the user selects a car:
 func tableView(tableView: UITableView,
    willSelectRowAtIndexPath indexPath: NSIndexPath) ->
    NSIndexPath? {

        var currentCar = self.vehicles[indexPath.row]

        var time = currentCar.distance / 50.0 * 60.0

        UIAlertView(title: "Car booked", message: "The car
        will arrive in \(time) minutes", delegate: nil,
        cancelButtonTitle: "OK").show()

        return indexPath

    }

As you can see, we need only do one more step to complete our code, in this case it's the view didLoad. Pay attention that another difference from Objective-C and Swift is that in Swift you have to specify that you are overloading an existing method:

  override func viewDidLoad() {

        super.viewDidLoad()

        vehicles = Setup.generate()

        self.tableView.reloadData()

    }

} // end of class
      1. Our code is complete, but of course our application is still using the old code. To complete this operation, click on the storyboard, if the document outline isn't being displayed, click on the Editor menu and then on Show Document Outline:

      2. Now that you can see the document outline, click on View Controller that appears with a yellow circle with a square inside:

      3. Then on the right-hand side, click on the identity inspector, next go to the custom class and change the value of the class from ViewController to MainViewController.

      4. After that, press play and check that your application is running, select a car and check that it is working. Be sure that it is working with your new Swift class by paying attention on the fare value, which in this case isn't shown with two digits of precision.

      5. Is everything done? I would say no, it's a good time to commit your changes. Lastly, delete the original Objective-C files, because you won't need them anymore.

How it works…

As you can see, it's not so hard replacing an old view controller with a Swift one, the first thing you need to do is create a new view controller class with its protocols. Keep the same names you had on your old code for attributes and methods that are linked as IBActions, it will make the switch very straightforward otherwise you will have to link again.

Bear in mind that you need to be sure that your changes are applied and that they are working, but sometimes it is a good idea to have something different, otherwise your application can be using the old Objective-C and you didn't realize it.

Try to modernize our code using the Swift way instead of the old Objective-C style, for example, nowadays it's preferable using interpolation rather than using stringWithFormat.

We also learned that you don't need to relink any action or outlet if you keep the same name. If you want to change the name of anything you might first keep its original name, test your app, and after that you can refactor it following the traditional factoring steps.

Don't delete the original Objective-C files until you are sure that the equivalent Swift file is working on every functionality.

There's more…

This application had only one view controller, however applications usually have more than one view controller. In this case, the best way you can update them is one by one instead of all of them at the same time.

Upgrading the app delegate

As you know there is an object that controls the events of an application, which is called application delegate. Usually you shouldn't have much code here, but a few of them you might have. For example, you may deactivate the camera or the GPS requests when your application goes to the background and reactivate them when the app returns active.

Certainly it is a good idea to update this file even if you don't have any new code on it, so it won't be a problem in the future.

Getting ready

If you are using the version control system, commit your changes from the last recipe or if you prefer just copy your application.

How to do it…

  1. Open the previous application recipe and create a new Swift file called ApplicationDelegate.swift, then you can create a class with the same name.
  2. As in our previous class we didn't have any code on the application delegate, so we can differentiate it by printing on the log console. So add this traditional application delegate on your Swift file:
class ApplicationDelegate: UIResponder,
UIApplicationDelegate {

    var window: UIWindow?

   

    func application(application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [NSObject:
    AnyObject]?) -> Bool {

        print("didFinishLaunchingWithOptions")

        return true

    }

   

    func applicationWillResignActive(application:
    UIApplication) {

        print("applicationWillResignActive")

    }

   

    func applicationDidEnterBackground(application:
    UIApplication) {

        print("applicationDidEnterBackground")

    }

   

    func applicationWillEnterForeground(application:
    UIApplication) {

        print("applicationWillEnterForeground")

    }

   

    func applicationDidBecomeActive(application:
    UIApplication) {

        print("applicationDidBecomeActive")

    }

   

    func applicationWillTerminate(application:
    UIApplication) {

        print("applicationWillTerminate")

    }

}
  1. Now go to your project navigator and expand the Supporting Files group, after that click on the main.m file.

  2. In this file we are going to import the magic file, the Swift header file:
    #import "Chapter_8_Vehicles-Swift.h"
  3. After that we have to specify that the application delegate is the new class we have, so replace the AppDelegate class on the UIApplicationMain call with ApplicationDelegate. Your main function should be like this:
    int main(int argc, char * argv[]) {
    
        @autoreleasepool {
    
            return UIApplicationMain(argc, argv, nil,
            NSStringFromClass([ApplicationDelegate class]));
    
        }
    
    }
  4. It's time to press play and check whether the application is working or not. Press the home button or the combination shift + command + H if you are using the simulator and again open your application. Have a look that you have some messages on your log console.

  5. Now that you are sure that your Swift code is working, remove the original app delegate and its importation on the main.m. Test your app just in case.
  6. You could consider that we had finished this part, but actually we still have another step to do: removing the main.m file. Now it is very easy, just click on the ApplicationDelegate.swift file and before the class declaration add the attribute @UIApplicationMain, then right click on the main.h and choose to delete it. Test it and your application is done.

How it works…

The application delegate class has always been specified at the starting of an application. In Objective-C, it follows the C start point, which is a function called main. In iOS, you can specify the class that you want to use as an application delegate.

If you program for OS X the procedure is different, you have to go to your nib file and change its class name to the new one.

Why did we have to change the main function and then eliminate it? The reason is that you should avoid massive changes, if something goes wrong you won't know the step where you failed, so probably you will have to rollback everything again. If you do your migration step by step ensuring that it is still working, it means that in case of finding an error, it will be easier to solve it.

Avoid doing massive changes on your project, changing step by step will be easier to solve issues.

There's more…

In this recipe, we learned the last steps of how to migrate an app from Objective-C to Swift code, however we have to remember that programming is not only about applications, you can also have a framework. In the next recipe, we are going to learn how to create your own framework compatible with Swift and Objective-C.

Summary

This article shows you how Swift and Objective-C can live together and give you a step-by-step guide on how to migrate your Objective-C app to Swift.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Swift 2 Cookbook

Explore Title