Monday, April 11, 2011

Compiling Static Framework for Use in Simulator and on iOS Device

After creating a static framework as described in the previous post it appears that the framework bundle that is viewable in the project pane of Xcode is only for the iphoneos version and not the iphonesimulator version even if the target is specified as the simulator.  Xcode still creates the iphonesimulator version when the simulator target is selected, however it is not viewable in Xcode.  In order to use the simulator version you will need to go to the filesystem.  Go to the same directory as the iphoneos version's parent directory and a directory for the simulator will also be there.  For example, the iphoneos version will be named something like Debug-iphoneos and the simulator will be named something like Debug-iphonesimulator.  Under those directories will be a directory containing your framework named <your product name>.framework, drag and drop that directory into your frameworks group in Xcode to use it.

Friday, April 8, 2011

Creating a Static Framework in Xcode 4

For iOS there is no template in Xcode to build a framework for iOS.  In order to get around this, you can create a Cocoa Static Library project and modify it so that you can develop and compile a static iOS Framework.  Using the post at "http://www.cocoanetics.com/2010/05/making-your-own-iphone-frameworks-in-xcode/" as a basis for writing this tutorial I was able to get it working and used it in a another project by following the steps outlined in this post.  This is the first library I've ever tried to create outside of Java so please feel free to chime in with any comments or corrections.

Create the Xcode project for the framework(skip if you already have an existing project):
- Open Xcode and create a new project (File->New->Project)
- Under the "Framework & Library" section under iOS select "Cocoa Touch Static Library".
- Under "Product Name" enter the name you want to call your framework.
- Click "Next" and "Create".

Add a Bundle target:
- Click the "Add Target" button at the bottom of the Xcode window.
- Select the "Bundle" template under Mac OS X.
- Give it the name of your framework.
- Click "Finish".

Set the Build Settings:
- Change the following properties to the values indicated below...
- Base SDK (replace the value with the appropriate iOS SDK version for your framework).
- Build Active Architecture Only = No
- Architecture and Valid Architectures = $(ARCHS_STANDARD_32_BIT)
- Mac OS X Deployment Target = Compiler Default
- Targeted Device Family (choose the appropriate iOS device i.e. iPhone/iPad)
- Dead Code Stripping = No
- Link With Standard Libraries = No
- Mach-O Type = Relocatable Object File
- Wrapper Extension = framework
- Generate Debug Symbols = No

Modify Info.plist file:
- Under your frameworks "Supporting Files" folder should be a file named <framework>-Info.plist.  Modify the "Bundle OS Type code to "FMWK"

Modify the Prefix.pch file:
- The generated Prefix.pch file under "Supporting Files" contains a reference to the Cocoa framework header file.  Remove that reference from the file.

Remove unnecessary frameworks from your project:
- Under the "Frameworks" folder delete frameworks such as Cocoa.framework and AppKit.framework that your project does not rely on.

Add frameworks required by your framework:
- If the project was created from a Cocoa template, the Cocoa framework is setup by default and not UIKIt which is required for iOS projects.
- Drag and drop any required frameworks such as UIKit under the "Frameworks" folder in your project pane.  In the dialog box that comes up uncheck "Copy items into destination group's folder (if needed)".
- UIKit should be in a folder similar to the following - /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/System/Library/Frameworks/UIKit.framework

Add and develop your framework code files.

Add a new build phase for "Copy Headers":
- Select your framework target.
- Click the "Build Phases" tab.
- Click the "Add Build Phase" button and select "Add Copy Headers" from the pull-down menu.
- Under "Compile Sources" your source files should already be there, if not add them.
- Under "Link Binary With Libraries" remove any frameworks that might be present.
- Under "Copy Headers" add your .h  files to the section with the appropriate scope.  They can be "Public", "Private", or "Project".  You can drag them from the "Project" section to the "Public" or "Private" sections or from the project file pane.

Create Target for Universal Framework
-------------------------------------------------
- Create an "Aggregate" target named "<Framework Name> Universal Framework".
- Update the "Build Settings" by changing "SDKROOT" to "iphoneos".
- Click "Add Build Phase" and then "Add Run Script".  Copy the script below into the script area in "Run Script".  Be sure to update the "FMK_NAME" property to the name of your framework.

# Sets the target folders and the final framework product.
FMK_NAME=MyFramework
FMK_VERSION=A

# Install dir will be the final output to the framework.
# The following line create it in the root folder of the current project.
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework

# Working dir will be deleted after the framework creation.
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework

# Building both architectures.
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator

# Cleaning the oldest.
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi

# Creates and renews the final product folder.
mkdir -p "${INSTALL_DIR}"
mkdir -p "${INSTALL_DIR}/Versions"
mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}"
mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources"
mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}/Headers"

# Creates the internal links.
# It MUST uses relative path, otherwise will not work when the folder is copied/moved.
ln -s "${FMK_VERSION}" "${INSTALL_DIR}/Versions/Current"
ln -s "Versions/Current/Headers" "${INSTALL_DIR}/Headers"
ln -s "Versions/Current/Resources" "${INSTALL_DIR}/Resources"
ln -s "Versions/Current/${FMK_NAME}" "${INSTALL_DIR}/${FMK_NAME}"

# Copies the headers and resources files to the final product folder.
cp -R "${DEVICE_DIR}/Headers/" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Headers/"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/"

# Removes the binary and header from the resources folder.
rm -r "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/Headers" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/${FMK_NAME}"

# Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/Versions/${FMK_VERSION}/${FMK_NAME}"

rm -r "${WRK_DIR}"

Run the "MyApp Universal Framework" target to build the framework.  The framework bundle should now be under the "Products" folder of your project.  To use it in another project simply drag and drop it from your framework project to a folder in your project.

Hope this was helpful!

Unable to Deploy App to iOS Device

While testing an application on the iPad 2 I experienced several crashes.  All of a sudden I was no longer able to deploy to the device and received the following error in the debugger console within Xcode:

Couldn't register COMPANY.MyApp with the bootstrap server. Error: unknown error code.
This generally means that another instance of this process was already running or is hung in the debugger.warning: Unable to read symbols for /Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.3.1 (8G4)/Symbols/Developer/usr/lib/libXcodeDebuggerSupport.dylib (file not found).
(gdb)

Tried restarting the machine running Xcode with no success.  Then tried restarting the iPad which seemed to do the trick.  Pretty standard procedure to try to fix issues that seem to have no apparent reason for occurring.
 

Wednesday, April 6, 2011

Updating Cell Data in UITableView

Today I created a subclass of UITableView which would display a list of RSS feed items.  After creating a custom parser with the necessary callbacks to inform the UITableView subclass after parsing each item and when the feed was finished being parsed, the table needed to be updated to display the RSS items that were pulled from the XML feed.  I struggled with this a bit and tried calling setNeedsDisplay at first thinking that it would cause the view to repaint along with the new data, but that didn't work.  So I hopped onto Google and found that when the underlying data for the table changes a call to the reloadData method on the UITableView instance is required.  After doing this everything worked as expected.  Of course afterwards this all made perfect sense, but these kinds of moments are sure to occur frequently as I learn the iOS frameworks.