This post shows how to fix one of the biggest time-wasting aspects of Xcode: the default project. Every time you start writing a new app, you first typically waste 15-30 minutes “un-****ing” Apple’s defaults. The defaults are terrible.
e.g. they are effectively unusable with Apple’s own SVN integration (Apple clearly doesn’t use SVN internally), unless you are the only person working on your project (“no teams allowed”). But the good news is … most of this can be fixed!
Step 1: Find the project templates
…usually somewhere buried inside /Library/Application Support/Developer/Shared/XCode
Step 2: Copy *all* project templates into your home directory
IF YOU DO NOT DO THIS, APPLE *WILL* DELETE YOUR CHANGES (every time you upgrade XCode, it sees your changes, and says to itself: “Oh. How cute! Something for me to destroy!” and promptly overwrites them)
Copy everything in:
/Users/[your user name]/Library/Application Support/Developer/Shared/XCode/[your company name]/*
replacing the bits in square brackets appropriately.
Step 3: Start un-f***ing the defaults
Now for the painful bit. Apple’s system for project templates is very basic, and what they’ve done is to create a separate template for EVERY combination of project type, platform (iPhone, iPad, and “both”), and tickbox options (with core data / without core data, etc).
You have to make the same changes in every single one of those projects. This is hours of work. But, once done … you are free of having to re-do this work on every new project you start.
Example fixes I like to do:
NOTE: Apple’s version of SVN requires that you split your project up into as many XCode groups as possible; only one person in an entire team is “allowed” to add/remove/rename files inside a single group. Anything else, and SVN trashes your XCode project file.
So, not only does organizing your project increase productivity (it’s quicker and easier to navigate), it means SVN will crash less often. Hooray!
1. Create a Resources directory
Apple – amazingly – didn’t bother to do this. They create a Resources group by default, that dumps all your (usually hundreds) of resources and images into the root directory.
So, delete the group, go to Finder (right-click on the Project itself in Groups&Files, and select “reveal in finder”), create a Resources directory, then drag/drop the directory into XCode.
Result: it appears *exactly the same* on screen (sigh), but at least your image files will now get organized into their own neat directory.
2. Create a Libraries directory
Most apps use lots of libraries. Apple refuses to allow Frameworks on iPhone, so we’re forced to use Libraries instead. Framworks are neatly bundled into a single item inside XCode; Libraries are a mess of files. Since Apple gives us no choice, let’s at least make it a little less painful.
As for Resources directory above, create a Libraries directory. Anything you drag-drop into XCode’s Libraries group will be copied into the Libraries sub-directory.
3. Fix the ObjectiveC Linker bug
Apple’s linker has a minor bug that causes most libraries to fail to run; if you use any 3rd party library that was written in ObjectiveC (that is: nearly all of them on iPhone), you will probably hit this bug.
Every one of your targets MUST include this in the build settings:
“Other Linker Flags” : “-ObjC -all_load”
…hopefully Apple will fix this in XCode 4 – or just make it the default! For now, you know what to do: put it in as a new default :).
4. Create 4 (or more) extra Targets
XCode has a *LOT* of critical showstopper bugs that Apple has been slow to fix, especially in the area of “building your app and running it”. I frequently encounter these on other people’s projects. There is only one sane workaround for this, and that’s to do what XCode’s designers intended – but which Apple doesn’t help you with – make LOTS of Targets.
In XCode, a “Target” means:
A version of the app built to run on a specific piece of hardware, or with a specific code-signing
The App Store requires a unique code-signing.
AdHoc copies require a unique code-signing.
Running your app in “test mode” on a device … requires a unique code-signing.
Running your app in the Simulator … is a different platform.
Running your app on the iPad … is a different platform.
So that’s 5 unique targets right there. They ALL REQUIRE separate Targets. If you read Apple’s docs closely, you’ll see that the Developer Portal does – quietly – tell you to do this. Many people just constantly – manually – edit their build files, changing them back and forth. Nightmare!
Worse, XCode has several bugs where changing build files gets ignored, either for an amount of time (say 10 seconds) or until a specific event (say, rebooting OS X. Yes, really!).
So, it’s imperative that you set up separate Targets in XCode, and that you use them. Apple provides a control that does a fast switch between different Targets, so it’s literally just two clicks to switch at any time.
Each target also has – potentially – unique:
– libraries that it includes
– compiler flags
…but by default, all the above are AUTOMATICALLY added to all Targets, so life is still easy.
Here’s the Targets that I find I need on every single iPhone app I write:
- appname App Store: this is the one that is configured for uploading to the App Store, and is codesigned for it
- appname Sim Local: ONLY compiles for the Simulator. Uses Compiler flags to use local resources rather than the internet. i.e. accesses our local Staging server for web fetches, so we can use test data (and so testing is much much faster). Anyone who writes internet-based apps and doesn’t use a Staging server is rather foolish.
- appname Sim LIVE: as for above, except it connects to the live servers. Useful for doing fast testing against Live data; I usually use this for checking reported bugs (it’s faster than constant synching to the device)
- appname iPhone: ONLY compiles for the iPhone
- appname AdHoc: creates an AdHoc copy. NB: this actually requires several extra files (entitlements, iTunesArtwork) as well as using a different codesigner
…although I haven’t adapted that list yet for iPad development. With universal binaries, I’m not sure how many of those I can merge, and how many I’ll have to duplicate.
5. Replace Apple’s crappy source-code templates
Do you like having stupid, fake “copyright” notices at the top of your source files? Using C-style doc comments no less (i.e. incompatible with all source-code-documentation systems)? No, me neither. If they were Doxygen, Javadoc, or similar comments they might at least be useful, but Apple didn’t bother with that.
So, again, you have to fix it yourself. This is similar to steps 1 and 2 above, but you take DIFFERENT files and you put them in a DIFFERENT folder. See here for a simple guide on how to do that. Once you’ve made the copies, you can manually edit them and remove the cruft.
6. Set the defaults for NIB files
This is a bit trickier. If you edit the template for new NIB files, and hit save … Interface Builder corrupts the file.
You have to do this:
- duplicate the template file
- Edit the template file
- Save it
- Go to a command line, and run “diff NEWFILE OLDFILE”
- Open the old template file in TextEdit, and manually insert the SPECIFIC changes you made, while ignoring the changes that you didn’t make
If you don’t do this, weird stuff happens, like new NIB files getting confused about who their “File’s Owner” is, and putting “<<NAMEOFOFILESOWNER>>” or similar rubbish in there.
There’s a few things to note, where XCode’s badly-written build code can collapse. No reason why it should do this, but it does.
Don’t move plist files out of the root
XCode (like all OS X applications) “knows” when a file is renamed or moved. It updates everything correctly EXCEPT the app’s main plist file. Since every target has a unique plist file, you need to keep all of them in the root, or the build stage will crash.
Give each Target a unique “Product Name”
XCode has poor exec code, and it gets “confused” if you have two targets that both output applications with the same filename. It seems to work, then it will occasionally mis-compile / mis-build – or even refuse to run at all.
The easy workaround is to edit the build-settings and give a different suffix to each Product Name – e.g. if the default is “appname” then make the AdHoc target be “appname AH”.