How to add a macOS target to your iOS App

To write cross-platform applications, it can be beneficial to have a single project with several target architectures. For example, we may want a macOS App inside a project that started out as iOS, and vice versa. Or we may want a different version of our app, perhaps a free one with less features, and an expensive one with more, based on the same code.

That’s where Xcode Targets come in. A Target is something that defines several build settings about an app so that when we press that popular button in Xcode, it knows what to do so we can see the built app in full colour. Trust me, there’s a lot going on under the hood – if you’ve ever tried to compile from the command line, you know how super helpful that button is. But I digress…

In this example I’ll show you how we can add a macOS Target to an iOS App’s Project. This will allow us to run and build either an iOS or a macOS version from common code.

Let’s begin. I’m using Xcode 8.3.3 for this by the way.

Adding the Target

In a standard Xcode Project for iOS, we already have a single target. Click on the blue project bar and select it from the list next to the File Inspector. It’s the one with the yellow icon:

Notice the difference between the Project Settings and the Target Settings here: click on the blue bar at the top to see the project settings (far fewer) and on the app icon to see the Target Settings (far more). The latter contains what Xcode needs to create an app for iOS.

Notice further down we have options to add and remove targets using the plus and minus icons. Click the plus icon and a dialogue will come up, asking us what we’d like to add.

There’s a plethora of options here, including Targets. This dialogue also lets us add In-App Purchases, Today Widgets, additional classes, and so forth. To stick with our example, let’s add a macOS Application. Either a Cocoa App or a Command Line Tool will work fine.

Now we have a new Target in our list, as well as a new group in our file inspector with items that are unique to our new version of the app. For us that’s a new main.m file (our iOS Target has it’s own set of files).

To run either version, we can now choose at the top left of Xcode:

With this feature we can pick which target (scheme) we’d like to run, and which device we’d like to use. So our iOS scheme will still show us the choices from our simulator, and our macOS scheme will show us only “My Mac” (because other machines aren’t usually connected to your local machine).

Select the latter and run your app. Everything should work fine, giving you a friendly “Hello World” in the log output.

Common File Classes

The idea behind writing apps like this is that we have a common set of files that both apps can use. In an ideal world, not much needs to be re-written, we only need to add functionality for the second platform.

In my case I have a test class with a singleton, all it does is return an NSString. I’ve amended my macOS Target’s main.m class to incorporate this common class:

#import <Foundation/Foundation.h>
#import "Test.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // insert code here...
        NSString *text = [Test sayHello];
        NSLog(@"Hello, World from main!\n%@", text);
    }
    return 0;
}

In an ideal world, I should now see the previous “Hello World” message, as well as my returned string. I don’t see any compiler errors, which leads me to believe everything should be hunky-dory.

Until I try to run the app again, when I discover a nasty compiler error and build failure without much explanation.

It would go a long way for Xcode to tell us what’s wrong, but I guess we can’t have everything. What Xcode is trying to say is that we’re using classes that the compiler cannot see, because they were not explicitly specified in our new Target.

Not to worry, this is easily fixable: select your macOS Target, head over to the Build Phases tab and open the little Compile Sources triangle. Lick the plus icon to add all common files that you’ve used in your new Target.

Do the same for any assets, Frameworks and Libraries you may have used. In short, anything that your app needs must be defined in the target.

Try to run the app again, hopefully with much success this time.





Leave a Reply