Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to port a module in Objective-C to Swift?

After experimenting with a few little Swift programs, I decided my next step was to port a single module in an Objective-C program into Swift to see what steps were required. I had a number of issues, so I thought I'd post my process and results here in case others might find it useful.

I also created a table to help me remember the different conversions. Unfortunately, StackOverflow doesn't support tables, so I posted these conversions as a Github gist here.

Although Apple will undoubtedly provide an Xcode Refactor to convert from Objective-C to Swift, converting one manually is a great way to get familiar with the differences between the two languages. There is so much 'muscle memory' involved in a language you know well, and this is a great way to get familiar with the new syntax. As promised by Apple, it turns out the languages share so many common ideas, that it's mostly a mechanical process (as opposed to porting from, say C++ or even traditional C).

Note that this process uses none of the exciting new features of Swift, it only gets the code straight across. I should mention that moving to Swift will restrict any backwards compatability to iOS 7 or OS X 10.9. I also ran into a couple of issues (with workarounds below) that I'm sure are just due to the first beta release status of the project, so may not be required in future versions.

I chose iPhoneCoreDataRecipes and picked a module that didn’t rely on a lot of others: IngredientDetailViewController. If you'd like to follow along, check out my "answer" below.

Hope this is of use.

like image 788
mackworth Avatar asked Jan 01 '26 08:01

mackworth


1 Answers

0) Download a copy of the project here and open Recipes.xcodeproj in Xcode version 6.

1) Choose File>New File…>iOS Source>Swift File> IngredientDetailViewController (Folder: Classes, Group: Recipe View Controllers)

2) Reply Yes to “Would you like to configure an Objective-C bridging header?”

3) Copy the first three lines below from Recipes_Prefix.pch and the next three from IngredientDetailViewController.m into Recipes-Bridging-Header.h. If you do further files, obviously don't duplicate lines, and remove any files that you've converted to Swift. I haven't found any where that documents the need for the Cocoa lines, given that they're imported in the swift file, but ...

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "Recipe.h"
#import "Ingredient.h"
#import "EditingTableViewCell.h"

4) Copy/paste the text from both the IngredientDetailViewController.h file and the IngredientDetailViewController.m files into IngredientDetailViewController.swift.

5) Delete both IngredientDetailViewController.h and .m files from project.

6) Do a global Find-and-Replace from #import "IngredientDetailViewController.h" to #import "Recipes-Swift.h" (Only one conversion in this case, and again for further files, don't duplicate this line in your Objective-C modules.)

7) Check the Project>Targets>Recipes>Build Settings Runpath Search Paths. If it shows $(inherited), remove this line or you'll get an error on launch about "no image found"

8) Convert Objective-C syntax in IngredientDetailViewController.swift to Swift. See the GitHub Gist mentioned above the substitutions required, or below for my converted version.

9) You may need to update the IB links. Do a Find>Find in Files on IngredientDetailViewController and select the one in Interface Builder. Open the Identity Inspector in the right-hand column. Select IngredientDetailViewController in the Class field, type xxx or something and tab.

10) Build and Run. Note that after going into a recipe, you must tap Edit and then the info button of an ingredient to activate IngredientDetailViewController

12) Congrats on building your first mixed Swift/Objective-C program!

Here's my cut at this particular module:

``

class IngredientDetailViewController: UITableViewController {

    var recipe: Recipe!
    var ingredient: Ingredient! {
    willSet {

        if let newIngredient = newValue {
            self.ingredientStr = newIngredient.name
            self.amountStr = newIngredient.amount
        } else {
            self.ingredientStr = ""
            self.amountStr = ""
        }
    }
    }
    init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
        super.init(nibName:nibNameOrNil, bundle: nibBundleOrNil?)
    }
    init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
    }
    init(style: UITableViewStyle) {
        super.init(style: style)
    }

    // MARK: table's data source
    var ingredientStr: String?
    var amountStr: String?

    // view tags for each UITextField
    let kIngredientFieldTag =    1
    let kAmountFieldTag  = 2



    override func viewDidLoad () {

        super.viewDidLoad()

        self.title = "Ingredient"

        self.tableView.allowsSelection = false
        self.tableView.allowsSelectionDuringEditing = false
    }


    override  func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        return 2
    }

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

        let IngredientsCellIdentifier = "IngredientsCell"
        let cell = tableView.dequeueReusableCellWithIdentifier(IngredientsCellIdentifier, forIndexPath: indexPath ) as EditingTableViewCell
        if (indexPath.row == 0) {
            // cell ingredient name

            cell.label.text = "Ingredient"
            cell.textField.text = self.ingredientStr
            cell.textField.placeholder = "Name"
            cell.textField.tag = kIngredientFieldTag
        }
        else if (indexPath.row == 1) {

            // cell ingredient amount
            cell.label.text = "Amount"
            cell.textField.text = self.amountStr
            cell.textField.placeholder = "Amount"
            cell.textField.tag = kAmountFieldTag
        }

        return cell
    }



    @IBAction func  save (sender: AnyObject!) {

        if let context = self.recipe.managedObjectContext  {
            if (!self.ingredient) {
                self.ingredient = NSEntityDescription.insertNewObjectForEntityForName("Ingredient",
                    inManagedObjectContext:context) as Ingredient
                self.recipe.addIngredientsObject(self.ingredient)
                self.ingredient.displayOrder = self.recipe.ingredients.count
            }

            // update the ingredient from the values in the text fields
            let cell = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow:0, inSection:0)) as EditingTableViewCell
            self.ingredient.name = cell.textField.text


            // save the managed object context
            var error: NSError? = nil
            if !context.save( &error)  {
                /*
                Replace this implementation with code to handle the error appropriately.

                abort() causes the application to generate a crash log and terminate.
                You should not use this function in a shipping application, although it may be
                useful during development. If it is not possible to recover from the error, display
                an alert panel that instructs the user to quit the application by pressing the Home button.
                */
                println("Unresolved error \(error), \(error!.userInfo)")
                abort()
            }

        }
        // if there isn't an ingredient object, create and configure one

        self.parentViewController.dismissViewControllerAnimated(true, completion:nil)
    }

    @IBAction func cancel(sender: AnyObject!) {

        self.parentViewController.dismissViewControllerAnimated(true, completion:nil)
    }

    func textFieldDidEndEditing(textField:UITextField) {

        // editing has ended in one of our text fields, assign it's text to the right
        // ivar based on the view tag
        //
        switch (textField.tag)
            {
        case kIngredientFieldTag:
            self.ingredientStr = textField.text

        case kAmountFieldTag:
            self.amountStr = textField.text
        default:
            break
        }
    }
}
like image 100
mackworth Avatar answered Jan 04 '26 03:01

mackworth



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!