OSX and iOS shared swift module - ios

I have created swift based Cocoa Touch Framework project named TestLib then I have added new target for Cocoa Framework named TestLibOSX. When I use the framework on iOS app it seems to be working without any issue, but when I create OSX console application it XCode is complaining that it couldn't find TestLibOSX module. Am I missing something ?
P.S This is not the same :)
EDIT: Seems this must be possible since I can see Lister is implemented that way.

If you wish to create a single dynamic framework binary, here are the steps you can follow (as outlined in http://colemancda.github.io/programming/2015/02/11/universal-ios-osx-framework/):
1. Change the project's valid architectures and supported platforms.
This should change your framework's and test unit's valid architectures and supported platforms as well. If not, then manually change them to inherit from the project's build settings.
Base SDK: I recommend OS X, but it will work with iOS too. Note that with with iOS as the base SDK, "My Mac" target is separated into 3 different targets.
Supported Platforms: macosx iphoneos iphonesimulator
Valid Architectures: arm64 armv7 armv7s i386 x86_64
2. Change the search paths for the Unit Test bundle
Runpath Search Paths: $(inherited) #executable_path/Frameworks #loader_path/Frameworks #executable_path/../Frameworks #loader_path/../Frameworks
Framework Search Paths: $(SDKROOT) $(inherited)
This will allow you to import it as import MyFramework instead of
#if os(iOS)
import MyFramework
#else
import MyFrameworkOSX
#endif

Alright, universal frameworks are really a pain. Not only for iOS and OSX but also just within iOS since you need 2 frameworks: one for the simulator and one for the devices. The way you handle iOS is with an "Aggregate" target. I believe the same avenue could be exploited to integrate an OS X target.
When you build a framework, you get a .framework (lego block) folder. In there, there are 2 very important things to be had:
The "Executable" file
The "Modules" folder
In both of these places, you have to have implementation to support all the architectures you desire to support. If you where to build your frameworks separately and wanted to fuse them together, you would have to do the following:
Take both executables and merge them with a "lipo" command.
Make sure that all the files in both "Modules" folder are move
together.
In practical terms, it's a mess so here's what I do!
First I build the frameworks. They can be on different targets in the
same project but usually, the same name helps. For iOS only, theres only one framework so one target suffices.
Then I add an "Aggregate" target, it will help create multiple builds for different architectures.
Clicking on the project name and then the Aggregate target, I go to "Build Phases" and click on the little plus to add a "New Run Script Phase".
There I copy paste the code in this GIST.
Finally, changing your scheme to the Aggregate target and building it will do all the merging you need and you end up with a .framework that is universal.
The script is rather straightforward and with a little dabbling, I'm sure it can be tweaked to add OSX support to it. I hope this points you in the right direction :)
Contents of the script:
#!/bin/sh
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
# Step 1. Build Device and Simulator versions
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
# Step 2. Copy the framework structure (from iphoneos build) to the universal folder
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"
# Step 3. Copy Swift modules (from iphonesimulator build) to the copied framework directory
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/." "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
# Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"
# Step 5. Convenience step to copy the framework to the project's directory
cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"
# Step 6. Convenience step to open the project's directory in Finder
open "${PROJECT_DIR}"

Related

Linking third party frameworks to my own framework, distributed using Carthage

We have several different proprietary API provided to us as Frameworks that offer similar functionality. The API is of course not identical, so we want to build a wrapper facade Framework around these, so our apps do not need to care which of the API they are using.
As a start, I have created a framework that uses one of these APIs and has unit tests that verifies that it works as expected. We have added the third party framework binary as a part of the project, and the Framework search path is like this:
$(inherited)
$(PROJECT_DIR)/Carthage/Build/iOS
$(PROJECT_DIR)
All non-recursive. The Cartage path is here because we use Quick and Nimble for our unit tests. I have the framework in question stored at the project root.
We want to distribute our framework using Carthage, and later also as a Cocoapod.
The problem is that when I try to build our framework by the command: carthage build --no-skip-current --platform iOS, I get the error:
*** Building scheme "Nimble-iOS" in Nimble.xcodeproj
*** Building scheme "Quick-iOS" in Quick.xcworkspace
*** Building scheme "XXX" in XXX.xcodeproj
Build Failed
Task failed with exit code 65:
/usr/bin/xcrun xcodebuild -project /Users/andre/Development/yyy/xxx/XXX.xcodeproj -scheme XXX -configuration Release -sdk iphonesimulator -destination platform=iOS\ Simulator,id=AE9E3D9D-BE2C-4AE1-AB37-C6BE8948067B -destination-timeout 3 ONLY_ACTIVE_ARCH=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES build (launched in /Users/andre/Development/yyy/XXX)
And the log contains this:
iphonesimulator/XXX.build/Objects-normal/i386/XXXLockService.o
/Users/andre/Development/yyy/XXX/XXX/LockServiceFactory.swift:7:71: error: 'PinServiceAccessImpl' is unavailable: cannot find Swift declaration for this class
case .xxx: return XXXLockService(lockService: PinServiceAccessImpl.getInstance())
^~~~~~~~~~~~~~~~~~~~
PinServiceAccess.PinServiceAccessImpl:2:12: note: 'PinServiceAccessImpl' has been explicitly marked unavailable here
open class PinServiceAccessImpl : NSObject {
When using a framework from an app, the documentation states that we should add the framework's as an embedded binary, but this option is not available when the project is a framework.
What puzzles me is that the unit tests seems to find the PinServiceAccessImpl.getInstance()!
I guess there are some details about linking that I don't know about that makes the build fails. Does anyone have an idea of what we are missing out?
The problem got solved when updating the deployment target to 11.0.

Xcode 7 builds i386 instead of arm binary for Release-iphoneos

I moved from XCode 6 to XCode 7 and without any changes to the source, or project, or anything my Archive builds started to fail.
After research on the error that lipo produced:
lipo:.../Release-iphoneos/libSDWebImage.a and .../Release-iphonesimulator/libSDWebImage.a have the same architectures (i386) and can't be in the same fat output file
I found that the following:
In XCode 6 the lipo -info returns Architectures in the fat file: .../Release-iphoneos/libSDWebImage.a are: armv7 arm64 and Architectures in the fat file: .../Release-iphonesimulator/libSDWebImage.a are: i386 x86_64 which is correct. I have arm for iphone device and i386 for iphone simulator.
In XCode 7 these two files are the same, and have the i386 architecture! So framework scripts that uses lipo to join these two .a files into one fails.
Why XCode 7 suddenly stopped building my SDWebImage framework for arm? The project settings are unchanged, the library is the same, the scheme has Archive set to Release. Please help.
iMac:~ lukasz$ lipo -info /Users/lukasz/Library/Developer/Xcode/DerivedData/…-etcsjmgakpylpmgchumhnsqpyrev/Build/Intermediates/ArchiveIntermediates/adhoc-stage/BuildProductsPath/Release-iphoneos/libSDWebImage.a
input file /Users/lukasz/Library/Developer/Xcode/DerivedData/…-etcsjmgakpylpmgchumhnsqpyrev/Build/Intermediates/ArchiveIntermediates/adhoc-stage/BuildProductsPath/Release-iphoneos/libSDWebImage.a is not a fat file
Non-fat file: /Users/lukasz/Library/Developer/Xcode/DerivedData/…-etcsjmgakpylpmgchumhnsqpyrev/Build/Intermediates/ArchiveIntermediates/adhoc-stage/BuildProductsPath/Release-iphoneos/libSDWebImage.a is architecture: i386
iMac:~ lukasz$ lipo -info /Users/lukasz/Library/Developer/Xcode/DerivedData/…-etcsjmgakpylpmgchumhnsqpyrev/Build/Intermediates/ArchiveIntermediates/adhoc-stage/BuildProductsPath/Release-iphonesimulator/libSDWebImage.a
input file /Users/lukasz/Library/Developer/Xcode/DerivedData/…-etcsjmgakpylpmgchumhnsqpyrev/Build/Intermediates/ArchiveIntermediates/adhoc-stage/BuildProductsPath/Release-iphonesimulator/libSDWebImage.a is not a fat file
Non-fat file: /Users/lukasz/Library/Developer/Xcode/DerivedData/…-etcsjmgakpylpmgchumhnsqpyrev/Build/Intermediates/ArchiveIntermediates/adhoc-stage/BuildProductsPath/Release-iphonesimulator/libSDWebImage.a is architecture: i386
I ran into the same problem trying to build a multi-architecture framework on Xcode 7. It seems like you are building a static library, which is different, but could be related. I'm assuming you are using xcodebuild command (in an Aggregate target run script?) to build your library for different SDKs and then doing lipo at the end to join all of them.
The problem for me was that the framework/library being built is located in the build/UninstalledProducts folder, and what lives in the BUILD_DIR are symlinks to that. So most likely the libraries in your Release-iphoneos and Release-iphonesimulator are aliases to the same one, hence you see that they have the same architecture (i386 in your case).
To avoid this, navigate to the 'Build Settings' of your static library target in Xcode and ensure the following under 'Deployment':
Deployment Location is NO for release
Deployment Postprocessing is NO for release
You should see that the build no longer outputs UninstalledProducts folder and that all libraries/frameworks built in the BUILD_DIR are unique files, which should now have the correct architectures. You can then do whatever you like with them using lipo. You might have to delete your DerivedData before attempting the above.
For me, the fix was to set the 'Skip install' from Yes no No (which is the default). So insure that in addition to other two options mentioned by Andrew Wei. +1 for great analysis dude.

Error importing own Swift Cocoa Touch Framework in Xcode 7

I have problems importing my own Swift Cocoa Touch Framework in an Swift app.
The Framework contains a Swift File, the Header File and the Info.plist.
The Project, in which I created the Framework contains also a test app as another target (Single View Application).
The testing in the Test App worked fine. I imported the Framework in my Swift Test App with
import MyUtilities
and it worked. But when I copy the .framework file to another Projekt and add it to "Embedded Binaries" and "Linked Frameworks and Libraries", I get an error when importing it.
Cannot load underlying module for 'MyUtilities'
I use the .framework file in the debug-iphoneos folder after building the framework with my iPhone selected and also build and run my framework with my iPhone selected in the new project.
I also tried it with a script from the internet, which should create a universal.framework file for both, simulator and device. It showed the same error.
The weird thing is, that I get the error above, but Xcode says Build Succeeded and also runs the app on my iPhone and lets me use code of my framework... So practically the import works, but I don't get completition and cannot see the documentation of my framework... Probably it's a bug of Xcode...
So to my question: Does anybody knows, how to fix that, so I see code completition in Xcode again and get rid of this error?
Here is the script, I downloaded to export the framework for universal use:
#CONFIG=${CONFIGURATION}
CONFIG=Release
# Step 1. Make sure the output directory exists
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIG}-universal
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
# Step 2. Build Device and Simulator versions
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIG} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIG} -sdk iphonesimulator -arch x86_64 -arch i386 BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
# Step 3. Copy the framework structure to the universal folder. Subsequently copy the files of the swiftmodule (in Modules directory) of -iphonesimulator to the swiftmodule of universal framework directory.
cp -R "${BUILD_DIR}/${CONFIG}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"
cp -R "${BUILD_DIR}/${CONFIG}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
# Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIG}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIG}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"
Here are a few screenshots of my Project in which I coded the framework:
The script, which exports the universal framework:
The dependencies of the test app:
No error, when I use my framework in the test app:
And from the new project, in which I tried to import the framework:
The framework is correctly linked:
The error when trying to import:
The Output message when running the code from my framework:
I think the problem is that you are not enabling access to the header files of the framework. I just struggled with this issue for quite a while. In Build Settings, I think you need to refer to the framework. If you are building your framework in your project that is using the framework, this should work:
Now for the coup de grace: Under Build Settings, search for Framework Search Paths. And add in:
${TARGET_BUILD_DIR}/YourFrameworkName.framework
Look at How Do I Create a Development Framework In iOS Including Swift? for more details.

Cocoa Touch Framework fails to debug on simulator in embedding project

I've got a Cocoa Touch framework built with XCode 6 targetted towards iOS >= iOS8.
This framework's target architecture settings are default, meaning that I haven't changed anything.
The architectures are set to standard (which doesn't include x86_64, more on that later).
The framework itself contains both Swift and Objective-C code, so building it using the static library workaround from Ray Wenderlich won't work.
Now, if I create a new project and add the framework project to it, the project builds for both the device and simulator, which is fine.
However, if I take the .framework file and add it to a different project just like you'd add any other framework, the project won't build for the simulator. Well, it does build, but it crashes because it can't find the relevant classes. It works fine on the device and archiving works just as expected as well.
The framework project itself already gives me a warning;
"Apple Mach-O Linker Warning - Directory not found for option ....(Debug-ophoneos)".
Any help would be highly appreciated!
I have finally found the solution to this issue.
As it turns out, XCode no longer creates fat binaries out of the box. No idea what Apple's reasoning behind this might be, too me it just seems like sometimes the guys responsible for XCode like to make fun of the developers using their product...
Anyways, you can find the definitive guide as to how to create a fat binary for simulator and all iOS devices (yes, you even have to lipo different architectures in order to get a framework that works on newer and older devices): https://kodmunki.wordpress.com/2015/03/04/cocoa-touch-frameworks-for-ios8-remix/
In short;
Create a Cocoa Touch Framework
Set the Architectures to arm64, armv7, and armv7s
Set "Build Active Architecture" to "NO"
Set "Valid Architectures" to arm64, armv1, and armv7s
Add the following script to the framework's build Scheme as an Archive Post-action;
set -e
DEVICE_BIN="${OBJROOT}/UninstalledProducts/${TARGET_NAME}.framework"
SIMULATOR_BIN="${SYMROOT}/../../../../Products/Debug- iphonesimulator/${TARGET_NAME}.framework"
ARCHIVE_PATH="${SRCROOT}/_Archive"
rm -rf "${ARCHIVE_PATH}"
mkdir "${ARCHIVE_PATH}"
if [ "${CONFIGURATION}" = "Release" ]; then
if [ -d "${DEVICE_BIN}" ]; then
DEVICE_PATH="${ARCHIVE_PATH}/Release"
mkdir "${DEVICE_PATH}"
cp -r "${DEVICE_BIN}" "${DEVICE_PATH}"
fi
if [ -d "${SIMULATOR_BIN}" ]; then
SIMULATOR_PATH="${ARCHIVE_PATH}/Debug"
mkdir "${SIMULATOR_PATH}"
cp -r "${DEVICE_BIN}" "${SIMULATOR_PATH}"
lipo -create "${DEVICE_BIN}/${TARGET_NAME}" "${SIMULATOR_BIN}/${TARGET_NAME}" -output "${SIMULATOR_PATH}/${TARGET_NAME}.framework/${TARGET_NAME}"
fi
fi
exit 0;
This will create an _Archive directory in your project's directory where you can find the frameworks for both debug and release.
Important: As of today (May 22nd 2015) you'll have to build the project with the simulator first, and then archive with a device. Otherwise you won't get a universal binary!
This post has been created in order to avoid dead link errors, for updates regarding the packaging process, please ALWAYS try the steps posted on the kodmunki website I've linked above first as the steps in this post might have been outdated already!

How to Build with OmniGroup Frameworks

I'm trying to incorporate OmniGroup's frameworks into a project. I'm very new to building with these kinds of dependcies so I've been fumbling my way through this. What I've done so far it.
Created new Xcode project for iOS (iPad) using the Single View Application template.
Edited the Schema and unchecked Parallelize Build and Find Implicit Dependencies.
Added the 'Configurations' directory from the OmniGroup repository to my project.
Created a lib directory in my project's repository.
Copied the OmniBase directory to my lib directory.
Created an Omni Frameworks group in Xcode.
Dragged the lib/OmniBase/OmniBase.xcodeproj file to the Omni Frameworks group.
In my project's Build Phases tab I added 'libOmniBase.a' in the Link Binary With Libraries section.
⌘B to build
Yes, I know OmniBase is useless by itself. I'm just trying to build the minimum amount first and add on from there. But I can't get OmniBase to build. I get one error...
Ld /Users/mluton/Library/Developer/Xcode/DerivedData/CoreTextWithOmni2-dzbwnmvzfbuyhzadnmwrvmfmzyoa/Build/Products/Debug-iphoneos/CoreTextWithOmni2.app/CoreTextWithOmni2 normal armv7
cd /Users/mluton/dev/ios-recipes/CoreTextWithOmni2
setenv IPHONEOS_DEPLOYMENT_TARGET 6.1
setenv PATH "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -arch armv7 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk -L/Users/mluton/Library/Developer/Xcode/DerivedData/CoreTextWithOmni2-dzbwnmvzfbuyhzadnmwrvmfmzyoa/Build/Products/Debug-iphoneos -F/Users/mluton/Library/Developer/Xcode/DerivedData/CoreTextWithOmni2-dzbwnmvzfbuyhzadnmwrvmfmzyoa/Build/Products/Debug-iphoneos -filelist /Users/mluton/Library/Developer/Xcode/DerivedData/CoreTextWithOmni2-dzbwnmvzfbuyhzadnmwrvmfmzyoa/Build/Intermediates/CoreTextWithOmni2.build/Debug-iphoneos/CoreTextWithOmni2.build/Objects-normal/armv7/CoreTextWithOmni2.LinkFileList -dead_strip -fobjc-arc -fobjc-link-runtime -miphoneos-version-min=6.1 /Users/mluton/Library/Developer/Xcode/DerivedData/CoreTextWithOmni2-dzbwnmvzfbuyhzadnmwrvmfmzyoa/Build/Products/Debug-iphoneos/OmniBase/OmniBase -framework UIKit -framework Foundation -framework CoreGraphics -o /Users/mluton/Library/Developer/Xcode/DerivedData/CoreTextWithOmni2-dzbwnmvzfbuyhzadnmwrvmfmzyoa/Build/Products/Debug-iphoneos/CoreTextWithOmni2.app/CoreTextWithOmni2
clang: error: no such file or directory: '/Users/mluton/Library/Developer/Xcode/DerivedData/CoreTextWithOmni2-dzbwnmvzfbuyhzadnmwrvmfmzyoa/Build/Products/Debug-iphoneos/OmniBase/OmniBase'
I've tried comparing my project to the TextEdit example workspace but I haven't run across any solutions yet. I know there's probably some step or setting I'm missing. Hopefully, this is obvious to someone reading this.
After examining this some more I finally got it to build. Here's the additional steps involved.
Copied the Script directory from the OmniGroup repository to my project.
Added a Build Phase. Run shell script Scripts/CopyLibraryResources. Ordered my Build Phases the same as in the TextEdit example. Target Dependencies, Compile Sources, Link Binary with Libraries, Copy Bundle Resources and Copy Library Resources which is the shell script build phase I added in the previous step.
Converted my project to a workspace.
Add my original project and the FixStringsFile project to the workspace.
Edit the scheme. Under build add FixStringsFile as a dependency. Made sure it was at the top of the list. Add any other library (in this case OmniBaseTouch) as a build dependency as well. Place them after FixStringsFile and Before the application target.
⌘B to build. No Issues!
PS: In both projects I edited Configurations/Target-Mac-Common.xcconfig and commented out OMNI_MAC_CODE_SIGN_IDENTITY = Mac Developer: since I'm not in the Mac Developer program.

Resources