Build IOS and Cocoa version of module in same project?

I'd like to have one project that has my (swift) source code that lets me build the module for both IOS and Cocoa (i.e. two different targets) in the same project. Then I want my Cocoa app (a separate project) to link against the Cocoa version of the library, while my iPad app links against the IOS version of the library.


How can I have two targets in the same project that create modules of the same name?


That is, in both the Cocoa App and the IOS App, i want to be able to say


import MyLib


I don't want the module names to depend on the architecture (because imagine that I several layers of library).

Is there a way to specify what the module name should be, independent of the target name? otherwise i don't see how to do this because i can't use the same target name twice in the same project.

Accepted Reply

Yes, it can be done, though it's a little bit messy.


— Start by creating two framework targets, one for OS X and one for iOS. Call them, for example, MyLibOSX and MyLibIOS. Note that this is going to create two subfolders using those names. (Subfolders of the main project folder, I mean.)


— In the Finder, create a third subfolder called MyLib.


— In Xcode, create a new group called MyLib, and associate it with the MyLib subfolder. (Select the MyLib group. In the File inspector, click on the tiny folder icon under the Location popup, then choose MyLib in the Open dialog that results.)


— If you have existing files for the frameworks, move them into the MyLib subfolder. If the files are already in your Xcode project, they will have turned red. Fix that by deleting the references and re-adding, or by dragging them into the MyLib group and re-associating them. (Select all of the files, then in the File inspector, click on the tiny folder icon, then choose MyLib in the Open dialog.) Use the File inspector to ensure that the files are included in both framework targets.


Similarly, if you add new files, add them in the MyLib group (which will cause the files to be created in the MyLib subfolder), and make sure you put them in both targets.


— Go to your build settings for each framework target in turn. You have 3 settings to consider:


1. Module name. When you click on this, you'll see that it's actually set to be the same as the target name. Replace what's there with module name MyLib. This is the essential step, because it's the module name that you put in an "import" statement, so you want it to be the same in each framework target.


2. Bundle ID. If you don't like the bundle IDs that Xcode chose, you can retype them with the bundle IDs you want. (I don't like having uppercase letters, because that can cause confusion later, so I usually choose my own.)


3. Product Name. This isn't technically important, since it's going to be the dylib file name that a user never sees, but if you want you can change it to something else.


— When you created the framework targets, Xcode should have defaulted to adding them the app target for the correct platform. If this didn't happen (if the app targets didn't exist at the time, or you have multiple targets for the same platform), go to each app target's Info pane, and click the "+" sign under the Embedded Binaries list. You'll get a dialog where you can add the appropriate framework to the app target. There'll be 2 MyLib entries in the list. In Xcode 7.2, it'll tell you which platform each is for. In earlier Xcode versions it didn't, so it's easier to make sure this step is done *before* you start renaming modules.


(Optional)


If you want, you can consolidate the files in the MyLibOSX and MyLibIOS subfolders into the MyLib subfolder. This also has a couple of steps:


— In Xcode, rename the 2 info.plist files so that their names don't conflict.


— In the Finder, move all 4 files to the common MyLib subfolder.


— In Xcode, again the files will have turned red. Re-associate them like before by clicking the tiny folder icon, and choosing the MyLib subfolder.


— Go to the Info pane for each target. In Xcode 7.2, you should see a button near the top to re-choose the info.plist files. Do so in each target. (In older Xcodes, you must go to the build settings and do this manually.)


At this point you should be good to go. Note that if your framework code is exclusively Swift, you need to add at least one .swift file to the framework targets if you want them to build. (Otherwise you'll get a weird error message about bridging headers.)


This seems like a lot of steps, overall. However, if you're methodical about it, it's not fundamentally hard.

Replies

You can specify the module name manually for a target in the Build Settings under Product Module Name (PRODUCT_MODULE_NAME).


https://www.dropbox.com/s/ogkftke1jbrz05t/Screenshot%202016-01-14%2020.28.28.png?dl=0

Yes, the module name is the "Product Name" in the Packaging section of the Build Settings, and which you can set independently of the target name. All of our frameworks have an OS X Framework target and an iOS Framework target in the same project file. You can check out the framework project configs here if you need more details: https://github.com/omnigroup/OmniGroup

Yes, it can be done, though it's a little bit messy.


— Start by creating two framework targets, one for OS X and one for iOS. Call them, for example, MyLibOSX and MyLibIOS. Note that this is going to create two subfolders using those names. (Subfolders of the main project folder, I mean.)


— In the Finder, create a third subfolder called MyLib.


— In Xcode, create a new group called MyLib, and associate it with the MyLib subfolder. (Select the MyLib group. In the File inspector, click on the tiny folder icon under the Location popup, then choose MyLib in the Open dialog that results.)


— If you have existing files for the frameworks, move them into the MyLib subfolder. If the files are already in your Xcode project, they will have turned red. Fix that by deleting the references and re-adding, or by dragging them into the MyLib group and re-associating them. (Select all of the files, then in the File inspector, click on the tiny folder icon, then choose MyLib in the Open dialog.) Use the File inspector to ensure that the files are included in both framework targets.


Similarly, if you add new files, add them in the MyLib group (which will cause the files to be created in the MyLib subfolder), and make sure you put them in both targets.


— Go to your build settings for each framework target in turn. You have 3 settings to consider:


1. Module name. When you click on this, you'll see that it's actually set to be the same as the target name. Replace what's there with module name MyLib. This is the essential step, because it's the module name that you put in an "import" statement, so you want it to be the same in each framework target.


2. Bundle ID. If you don't like the bundle IDs that Xcode chose, you can retype them with the bundle IDs you want. (I don't like having uppercase letters, because that can cause confusion later, so I usually choose my own.)


3. Product Name. This isn't technically important, since it's going to be the dylib file name that a user never sees, but if you want you can change it to something else.


— When you created the framework targets, Xcode should have defaulted to adding them the app target for the correct platform. If this didn't happen (if the app targets didn't exist at the time, or you have multiple targets for the same platform), go to each app target's Info pane, and click the "+" sign under the Embedded Binaries list. You'll get a dialog where you can add the appropriate framework to the app target. There'll be 2 MyLib entries in the list. In Xcode 7.2, it'll tell you which platform each is for. In earlier Xcode versions it didn't, so it's easier to make sure this step is done *before* you start renaming modules.


(Optional)


If you want, you can consolidate the files in the MyLibOSX and MyLibIOS subfolders into the MyLib subfolder. This also has a couple of steps:


— In Xcode, rename the 2 info.plist files so that their names don't conflict.


— In the Finder, move all 4 files to the common MyLib subfolder.


— In Xcode, again the files will have turned red. Re-associate them like before by clicking the tiny folder icon, and choosing the MyLib subfolder.


— Go to the Info pane for each target. In Xcode 7.2, you should see a button near the top to re-choose the info.plist files. Do so in each target. (In older Xcodes, you must go to the build settings and do this manually.)


At this point you should be good to go. Note that if your framework code is exclusively Swift, you need to add at least one .swift file to the framework targets if you want them to build. (Otherwise you'll get a weird error message about bridging headers.)


This seems like a lot of steps, overall. However, if you're methodical about it, it's not fundamentally hard.

Thanks. What I got wrong the first time I tried this was I made my ios target by duplicating the Cocoa target, and then changing the deployment. But this left the two targets using the same output build directory, which is what screwed me up. By simply creating a brand new target in the proper way it worked.


Thanks for the description of how to do the MyLib group so that both targets get files when you add into MyLib --- that was exactly my next question!!


One followup: I now have an ios app project which uses my library. But now I want to run the app project both on a real ios device, and in the simulator. I figured out how to make the library project build itself *three* ways: Cocoa, ios and ios-simulator.


Back in the app project, i now have two targets: one for running on the device, one for running on the simulator. But every time I switch between the two targets, I am forced to change the scheme. Is there no way way in xcode to make the scheme "stick" to the target? I want to either pick target "App" or "App (siim)" and not have to keep also changing the active scheme from device to simulator. Is that doable?

Doing what you want across *workspace* boundaries is a pain, and if you have two open projects — one for the app, one for the frameworks — then you have two workspaces. That's because if you don't create any workspaces explicitly, Xcode embeds one in the project for you.


(This can be a little confusing. If you create a workspace manually, the workspace conceptually contains the project(s) inside it. If you just have a project, the project contains the workspace. But the relationship is conceptually the same in both cases. A workspace contains one or more projects, whether the workspace is implicit or explicit.)


So, the easiest approach is to dump all of your targets (app and framework) into the same project. If you don't want to do that (for example, if the frameworks are in a different source control repository), then add the framework *project* into the app project, or create a workspace containing both projects.


(Either way, you'll end up with both projects in the same workspace, whether it looks like a workspace or not.)


In those circumstances, when you build the app, Xcode figures out the framework dependency automatically, and builds the framework for the platform of the main app. You don't, therefore, need different targets for simulator and device, just different targets for different platforms (iOS vs OS X). Does that make sense? It's a bit hard to explain without getting tangled up.


(If Apple would allow multi-platform frameworks, that is iOS and OS X in the same framework, then you'd only need one framework target, which would be automatically rebuilt for the current app scheme, without all this mucking around. But that's the horse you rode in on. 🙂 )

Thanks for this information! I was looking to do this at some point in my project, so I thought I'd test it out this morning.


After following your steps, one issue still remains for me. In my header file (MyLib.h) all of my #import statements (such as #import <MyLib/Globals.swift>) now fail with the error '<filename>.swift' file not found. I tried changing the path of the import statements to MyLib/MyLib/filename.swift, and a couple of others, but can't seem to get those to compile without error.


MyLib.h was one of the files that I migrated into the new merged MyLib folder along with all other source files.


Any ideas?

Thanks.

I'm not sure what the answer is here. If you're using an #import statement, then you're talking about using the framework in Obj-C, and in that case I don't understand why you're referencing 'something.swift'. Do you mean the bridging header 'ProductName/ModuleName-Swift.h'?


The answer is going to depend on whether you're mixing Obj-C and Swift code in the framework itself, or whether you're crossing language boundaries between the client and the framework. Ideally, if you're not mixing languages in the same target, you should be able to reference your framework via 'import MyLib' in Swift, and '@import MyLib' in Obj-C, avoiding #import completely. (When I did this, it was a pure Swift app, so I didn't have to deal with Obj-C issues.)


The other problem I ran into was that if the first framework I created was called MyLib, I couldn't rename MyLib.h to (say) MyLibMac.h. There appears to be some invisible build setting that remembers the original name — at least I couldn't find a build setting for it. I was only trying to rename the file for consistency with MyLibIOS.h or MyLibTVOS.h, but I couldn't find a way to do it. It's possible that some aspect of this may be affecting you.

Thanks. You make a good point.


The .h file I was referring to is the default file that is added to a Swift framework project (which my project is) when created. Initially, all it contained was an #import <Cocoa/Cocoa.h>, a couple of FOUNDATION_EXPORT lines representing the framework versions and a comment stating: "In this header, you should import all the public headers of your framework using statements like #import <MyLib/PublicHeader.h>"


Now that you mentioned Obj-C, I realize that I am likely using this header file incorrectly by importing my Swift files into it. I've removed all of my #import statements and my project is now error free and functioning as expected.


However, it still stands that prior to merging my two targets into one product name, there were no errors with this header file at all. After the merge, those #import statements couldn't find the files. So, I think you may be right about that "invisible setting" you mentioned. In any case, I don't think it will bother me for a while. It may come back into play if and when the time comes for me to deploy my framework as a stand-alone framework file.


Thanks again!