Tag Archives: Objective-C

Developing for Multiple Platforms with Xcode 6

I’ve been working on an app that targets both iOS and OS X devices, mainly to teach myself to code in Objective C, but also to scratch an itch in the best open source tradition.  In Xcode I’ve had two projects and been syncing code between them using copy and paste.  This is not a great experience so I looked around for more elegant solutions.  The one I settled on was inspired by Session 233: Sharing code between iOS and OS X from the 2014 WWDC.  This involves creating a single project with multiple targets aimed at different platforms and devices.

I was bothered by the fact that despite following what was done in the session I couldn’t get my project to look the same, eventually after much experimenting I came up with the following process.  The caveat is that this works for me, there are quite possibly better ways to achieve the same result, which I’d love to hear!

Create the project

Create a Project

The first thing you need to do is create a new project in Xcode, I usually start from the iOS Single View Application template, but it doesn’t actually matter.

SS 02

On the above screen the key change is in setting the project’s name.  If you chose an iOS application name the project MyApp-iOS, substituting MyApp for your own project name.  If you chose an OS X application  name the project MyApp-OSX.  Click next and finish creating the project by selecting a save location and enabling Git for version control if needed.

Rename the project

SS 03.1 SS 03.2

In the project navigator select the top item, the project itself.  Then move across to the file inspector on the righthand side and rename the project, dropping the suffix, to MyApp.

SS 04.1

The window shown above pops up offering to rename the targets, however as we will be adding targets for other platforms later we want to prevent Xcode from being over helpful by deselecting the targets:

SS 04.2

Clicking on rename might pop up another window where Xcode offers to turn on snapshots, I normally click enable but it doesn’t affect what we are trying to achieve here.

Adding a New Target

SS 06SS 07

The next step is to add a new target to build your application for a different Apple platform.  Click the + button at the bottom of the window, below the existing targets.  In the window that opens select a different platform – in my case that’s an OS X Cocoa Application.  Click next and name the target using the same convention as when you created the project: MyApp-OSX.  Once you’ve added the additional target your project should look something like:

SS 08

Adding Common Code

The whole point of this exercise to create common code that can be used in both targets.  To do this create a group in the Project Navigator by right clicking on the project and selecting New Group.  I normally name this MyApp-Common.  Right clicking on the MyApp-Common group select New File and choose to create a new Objective C class.

SS 09

The two things you need to take care with are: making sure you don’t create a class that inherits from a platform specific class and that you remember to add the class to both targets, as shown in the screenshot above.  You should then end up with a project that looks like this:

SS 10

The final task is to rename the products of the two targets, if you compile and then install or distribute you will end up with applications named MyApp-iOS and MyApp-OSX which isn’t what users expect to see.  Renaming the product is a two step process.  First, for each non-test target, you need to select the target, select Build Settings and search for Product Name.  Once you’ve got the Product Name setting you can change the value to the name you want for your application:

SS 11

Renaming the product in this way has an unexpected side effect – tests will no longer build and run.  This is due to the linker being unable to link the test classes to the application classes as it’s still looking for the old application name (details here).  To fix this you need to change the value for Test Host in the same way as you did for Product Name.  Do this for both test targets, replacing all occurrences of MyApp-iOS and MyApp-OSX with MyApp (or whatever name you gave to your product in the previous step).

SS 12

At this point you should be able to build, install, and run both targets.  As I said at the start, I’m sure there’s either things I’m missing or a better way to do this entirely – I will be looking into creating a framework that builds for both iOS and OSX, and then including the framework in a multi-platform application project.

Advertisements

More on OS X Dynamic Linking

My recent blog post on using a third party library within an Objective-C project had one fatal flaw – it didn’t actually work!

The problem was that even though I had copied the PROJ.4 library into my project, linked against that version of the library, and copied it into my application, the application was still looking for the library in it’s original location – /opt/local/lib.

After much Googling, and a fair amount of head scratching it turns out that dynamic libraries on OS X have an interesting feature where they hard code the path to the library at link time.  That doesn’t sound like a problem, however the path the application hard codes for the library isn’t set by one of the many Build Settings within your Xcode project rather it’s hard coded into the library when the library is built!

Helpfully Apple provide some command line tools to resolve this issue.  The first is otool which we can use to inspect this hard coded path/name in the PROJ.4 library:

$ otool -L libproj.0.dylib 
libproj.0.dylib:
	/opt/local/lib/libproj.0.dylib (compatibility version 8.0.0, current version 8.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

The first line of output gives us the name, which is also the path the linker will use, for the library.  This means that whenever we link an application against this library it will always look for it as /opt/local/lib/libproj.0.dylib no matter where we actually install it.

The other tool Apple provides, install_name_tool, allows us to modify the name/path of the library:

$ install_name_tool -id "@executable_path/../Frameworks/libproj.0.dylib" libproj.0.dylib

What this command does is replace the existing name/path with @executable_path/../Frameworks/libproj.0.dylib. The @executable_path keyword tells the system that it should look for the library relative to the executable, in this case one folder up, then in the Frameworks folder.

We can check that the change has taken:

$ otool -L libproj.0.dylib 
libproj.0.dylib:
	@executable_path/../Frameworks/libproj.0.dylib (compatibility version 8.0.0, current version 8.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

With the library updated cleaning the project and then rebuilding creates an application that works as expected.  I’ve updated the project on GitHub to include this fix and some clean up of the coordinate conversion code.

So the slightly updated sequence for adding a third party library to an application is:

  1. Copy the library into the project.
  2. Switch to the command line and update the library path/name using install_name_tool.
  3. Add a Copy Files build phase so that the library is copied into the application bundle.
  4. Update the Header Search Path in the build settings so that the header files for the library can be found by Xcode.

Your application should now work properly on any OS X system.  For more detail about linking and @executable_path take a look at this blog post by Mike Ash, his other blogs posts are good as well!

Using the PROJ.4 Library in an OS X Objective-C Application

I’ve been tinkering with programming in Objective-C to create both Mac and iOS applications and one of the areas I kept meaning to look into was how to use libraries written in C for Unix or Linux.  This post describes how I created a simple application for OS X Mavericks that uses the PROJ.4 library to convert coordinates from WGS84 to OSGB.

The first step was to get the PROJ.4 library.  There are a number of systems that provide prebuilt open source software for the Mac, I chose to go with MacPorts.  To install the core MacPorts system I followed the simple instructions.

Once MacPorts was installed I checked to see whether the system was working by listing information about the PROJ.4 library:

iMac:~ kms$ port info proj
proj @4.8.0 (gis)
Variants:             universal

Description:          PROJ.4 is a library for converting data between
                      cartographic projections.
Homepage:             http://trac.osgeo.org/proj/

Platforms:            darwin
License:              MIT
Maintainers:          seanasy@gmail.com, openmaintainer@macports.org

With the system working I could then install PROJ.4:

iMac:~ kms$ sudo port install proj
--->  Fetching archive for proj
--->  Attempting to fetch proj-4.8.0_0.darwin_13.x86_64.tbz2 from http://mse.uk.packages.macports.org/sites/packages.macports.org/proj
--->  Attempting to fetch proj-4.8.0_0.darwin_13.x86_64.tbz2.rmd160 from http://mse.uk.packages.macports.org/sites/packages.macports.org/proj
--->  Installing proj @4.8.0_0
--->  Activating proj @4.8.0_0
--->  Cleaning proj
--->  Updating database of binaries: 100.0%
--->  Scanning binaries for linking errors: 100.0%
--->  No broken files found.

And looking in /opt/local/bin and /opt/local/lib I can see PROJ.4 has been installed:

iMac:~ kms$ ls /opt/local/bin
cs2cs		invgeod		port		portmirror
daemondo	invproj		portf		proj
geod		nad2bin		portindex
iMac:~ kms$ ls /opt/local/lib
libproj.0.dylib	libproj.a	libproj.dylib	pkgconfig

Now the installation of PROJ.4 was complete I turned my attention to Xcode 5.  In Xcode I created a new OS X project of type Application/Cocoa Application.  I didn’t change any of the values during project creation, if you’re following along just make sure you’ve not selected a Document Based application and you’ve not enabled Core Data.  Once I had the project created there were four steps I had undertake: add the PROJ.4 dynamic library file to my project; configure my project to link my application to the library during build; configure the project to copy the dynamic library file into the Frameworks folder of my app bundle; and configure my project so that it can see the PROJ.4 header file proj_api.h.

To add the dynamic library file to the project control-click on the Frameworks group in the Project Navigator and select Add Files to… from the popup menu.  Navigate to /opt/local/lib and select ;libproj.dylib, make sure that Copy Items into destinations group’s folder is selected and add to your project target (you could add it to your test target as well if you want).  Then click Add.

Step 2, configure the project to link to the PROJ.4 library, isn’t necessary with Xcode 5.  The step of adding the library to the project configures the linking automagically.  For completeness, linking is configured by selecting the project in the Project Navigator, then selecting the target for which you want to configure the build.  Click on Build Phases and you should see Link Binary with Libraries, clicking the disclosure triangle you should see the PROJ.4 library along with the Cocoa Framework.  If you don’t see the PROJ.4 library you can add it by clicking on the + button.

The next configuration step was to add a Copy File phase to the build so that the PROJ.4 library is included in my App Bundle.  In the Build Phases page of the project target I clicked the + sign on the top left, just below General, to add a New Copy Files Build Phase.  Clicking on the disclosure triangle of the Copy Files phase I set the Destination to Frameworks, left Subpath blank, and Copy only when installing unselected.  Finally I clicked the + to add libproj.0.dylib to the copy files build phase.

The last task was to add /opt/local/include as a header search path to the projects Build Settings so that Xcode would be able to see the PROJ.4 header file, proj_api.h. In Build Settings I made sure that All was selected rather than Basic and I scrolled down to the Search Paths section.  In this section I expanded the Header Search paths and added /opt/local/include to both the Debug and Release build schemes.  With this done I could switch to AppDelegate.m and add the line #include "proj_api.h" with no errors (and autocomplete works).

With all the configuration done I could build and run the project with no errors, not that the application did anything at this stage.

Rather than explain how to create a simple Cocoa application with a couple of NSTextFields for the WGS84 latitude and longitude; an NSButton to trigger the conversion; and another NSTextField to display the OSGB coordinates I uploaded my project to GitHub.

The code I added is limited to two functions in AppDelegate.m:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{

    if (!(pjWGS84 = pj_init_plus("+proj=longlat +ellps=WGS84 +no_defs"))) {
        NSLog(@"Could not initialise WGS84");
        exit(1);
    }
    if (!(pjOSGB = pj_init_plus("+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +datum=OSGB36 +units=m +no_defs"))) {
        NSLog(@"Could not initialise OSGB");
        exit(1);
    }
}

And:

- (IBAction)convertToOSGB:(id)sender {
    double x, y;
    int e;

    x = DEG_TO_RAD * self.longitude.doubleValue;
    y = DEG_TO_RAD * self.latitude.doubleValue;

    if ((e = pj_transform(pjWGS84, pjOSGB, 1, 0, &x, &y, NULL)) != 0) {
        [self.osgbRef setStringValue:@"Transform Error"];
    } else {
        [self.osgbRef setStringValue:[NSString stringWithFormat:@"%d %d", @(x).intValue, @(y).intValue]];
    }
}