Friday, October 4, 2013

Using Xcode-select and bash aliases

Here is how I have set up Xcode 4 and 5 on my Macs and how I switch between them.

In my Applications folder, my main Xcode is always the latest. This makes for easy updates and is where Apple expects to find Xcode.

In a sub folder of Applications called "Xcode 4" I have a copy of Xcode 4 that I downloaded from Apple's download site [requires a login].

I use the xcode-select command line utility to switch between them.

The basic commands look like this:

To switch to Xcode 5 (my main Xcode)
sudo xcode-select --switch /Applications/

To switch to Xcode 4 (the Xcode in the sub-folder)
sudo xcode-select --switch /Applications/Xcode\ 4/

This is a lot of typing.

So, I have made some aliases in my bash profile. They look like this.
alias xc5="xcode-select --switch /Applications/"
alias xc4="xcode-select --switch /Applications/Xcode\ 4/"

Now I should be able to just type sudo xc5 and sudo xc4 from the to switch back and forth. However, this will not work and throws an error. I also need to make an alias for sudo like this:
alias sudo="sudo "

Notice the extra space? That is the magic part. So, with these three aliases, I can jump to the command line and switch over to a different Xcode. Then when I open the version of Xcode I want to use, all of the build tools, etc will work correctly.

Friday, September 28, 2012

Test Device Tango

With the release of iPhone 5 and Apple dropping support for arm v6 (so, effectively dropping support for anything that won't run iOS 4.3) we have to shuffle some things around.

The iPhone 3G and iPod Touch that were our lowest end devices will now be repurposed as music only devices or recycled. We considered selling them on eBay, but that seems cruel since they won't really work with apps.

We acquired an iPad 1 to use as the lowest end test device for iPads. It is running 5.1.1 and apparently will never get another meaningful upgrade from Apple. This means that we don't have an actual iPad that runs 4.3 and will have to rely on the simulator if any bugs appear. It is tempting at this point to set the minimum for all iPad projects to 5.1.1 since we don't trust the simulator for testing. We have an iPad "3" running iOS 6 as the other test device.

We are running iOS 6 on an iPhone 3GS which will become the main device to test performance. If an app runs well on that, it will be fine. We have an iPhone 4 with 5.1.1 installed to use to test any 5.x issues. In a few days, we will add an iPhone 5 with iOS 6 to round out the test devices.

As always, we keep an eye on David Smith's Version Stats to help guide our decisions about what to support. For all new projects, we are setting a minimum iOS level of 5.0 unless the client wants to target iOS 6 features. If Apple ever wanted to take a page from Google and show the versions of devices that have recently hit the App store, we would not complain.

Monday, September 17, 2012

Referencing a UIViewController After Embedding

The UIStoryboard is a timesaver. However, as with everything else, there are always little tricks that befuddle us all. Today, I had a regular modal segue that went from one UIViewController to a second UIViewController. When the segue fired, I set a delegate reference. Everything worked wonderfully!
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    if ([[segue destinationViewController] respondsToSelector:@selector(delegate)])
        [[segue destinationViewController] setDelegate:self];
However, I now decided that I needed to embed one of my UIViewControllers in a UINavigationController so that I could browse some more data in that part of my app. However, now, the code above did not assign my delegate to my UIViewController. The reason for this is that the [[segue destinationViewController]] was not pointing to my UIViewController. It is now pointing to the UINavigationController.

To fix this, I took advantage of two things:
- segue's can have names
- you can cast an object

Here is new code that will reach through the UINavigationController to my UIViewController and set the delegate.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    if ([[segue identifier] isEqualToString:@"detailSegue"]) {
        UINavigationController *tempController = [segue destinationViewController];
        if ([[tempController topViewController] respondsToSelector:@selector(delegate)]) {
                   [(DMTW_ArticleDetailViewController*)[tempController topViewController] setDelegate:self];

    if ([[segue destinationViewController] respondsToSelector:@selector(delegate)])
        [[segue destinationViewController] setDelegate:self];

We can see here that for all of the other segues we will just set the delegate like always. However with the segue of "detailSegue" we will do something different. We will first cast the [segue destinationViewController] to be a UINavigationController and then we will check the topViewController to see if it responds to "delegate". If it does we will set the delegate.

Notice, we are being a little lazy with this line.
 [(DMTW_ArticleDetailViewController*)[tempController topViewControllersetDelegate:self];
We probably should have kept the same two line format as we did for setting tempController as a UINavigationController and done something like
DMTW_ArticleDetailViewController *tempArticleController = [tempController topViewController];
But, where's the fun in that? If you are running into a similar issue, hope this helps clear it up for you.

Monday, December 5, 2011

Setting Up a Folder Action

The Automator application on OS X gives a good drag-and-drop way to have the computer take over some repetitive tasks for us. Things that Automator can be good for are:

  •  combine a bunch of PDFs into a single PDF
  • rename a whole bunch of files
  • run a script (bash/ruby/php)
  • manipulate some images (resize or watermark)

If you think you will actively be working with files (dragging them around) then set up an Application automation. If some other task might be creating the files you are starting with then you might want to set up a folder action. In other systems the folder action is called a "hot folder". This quick tutorial is going to set up a folder action to take any file or set of files I dump into the folder and then add a timestamp to the filename and move the item to a folder on my hard drive called "Finished".

First we will open Automator and select a New workflow from the File menu. We are going to make a workflow of type "Folder Action" so we select the proper icon and click the "Choose" button.

By choosing a folder action, we are asking Automator to watch the folder at all times, and whenever we add any file, group of files or folders to the folder, we want the workflow to begin.

The first thing Automator will want is an answer to "Folder Action Receives files and folders added to". We need to choose what folder is going to have this action attached to it. For the purposes of this demo, we are going to create a new folder called "StampTasks" and place it on the desktop.

The next step is to add the timestamp to each file that gets dropped into the folder. In the Automator, there is a library. The library has two types of actions: those supplied by the system and those supplied by third party applications. Pixelmator gets a shout out here for some of the really useful automator actions they have supplied.

For this tutorial, we are just going to use the action in "Files and Folders" called "Rename Finder Items". We do this by just dragging the Action from the list of actions to the main workflow window. When we first drag the action, we will get a dialogue box asking us if we want to work on the original files or if we want to make duplicates of the files and work on those.
For my purposes, I don't mind working on the originals, so I click Don't Add for this tutorial. Now we are presented with the second step in our workflow. For renaming purposes, Automator provides us with a few different choices for how we want to rename our files. 

Today we are just going to "Add Date or Time" and using the drop down menus I am going to insert the current date after the name of my files.

Now we need another action to move these files out of the folder and place them into their final resting place. So I use the "Move Finder Items" and then navigate to the folder I want to place the items into. "Move Finder Items" can be a little confusing, but basically it is saying "take the file that you did an operation on in the previous step and move it".

Finally, just to show off we can add a "Show Growl Notification" (this action is under Utilities if you have Growl installed). Here is what the final workflow looks like.

At this point you may be excited to run your new folder action but you will see that the "Run" button gives you an error. This is because the workflow starts when you drop a file into the folder, so we need to save the workflow first (NB: with Lion, I have gotten lazy about saving things, but if you don't explicitly save, Lion will happily not save a workflow and it doesn't give you the "Save As..." or other indication that it didn't save). It just won't run your workflow and you will say bad words.

If you have created the workflow and saved it, you should now be able to drop a file into the "StampTasks" folder and wait a few seconds and see it appear in the "Finished" folder with a Growl message. Here is a link to a zip copy of the workflow I created for this tutorial. In general, Automator will place files in the ~/Library/Workflows/Applications directory. (NB: In Lion I notice that the Library folder for the user is not invisible, so you may need to use "Go to Folder" from the "Go" menu in Finder to get at it.

The biggest issue with using the Automator is that debug info and errors are hard to work with. For instance, using the working tutorial above, delete the "Finished" folder. Then when you run the workflow you will get no error, but it won't run either.

Working with Scripts:
Any bash or ruby script can be run in automator using the Run Shell Script command. By default you can run shell scripts in your favorite flavor of sh as well as perl, python or ruby. The shell scripts should be expecting to get the files or text they are to work with just as if they were set up to get things from the command line. The shell script will be run once for each file in the folder.

Tuesday, November 8, 2011

Devices for Testing iOS Software

For the next few months at least it's going to be a pain to test in iOS. Recently I had to test an app on 4 different devices and it behaved differently on all four. So, here is the set-up I used to try to get the best coverage.

Old Devices
The oldest devices I'm willing to support (or that my clients ask to support) are running iOS 3.1.3. These are second generation iPod touches as well as iPhone 3G devices. Thankfully, these are the last devices are the last ones that don't do OS signing with Apple, so it is pretty trivial to upgrade them to 4.2.1 (the highest OS they support) and then back to 3.1.3 (a common OS because it runs faster). For maximum coverage, I have one device at one OS version and one at the other. Switching OS versions is easy but time consuming. I have an iPod Touch and an iPhone 3G for this testing. I decide which one to set at which version depending on if I need the camera for my app or not. These devices require armv6 code.

Less Old Devices
Apple is currently selling iOS 5 installed on iPhone 3GS, iPhone 4 and iPhone 4S. All of these devices are armv7 So, I'm treating this hardware as interchangeable for now. I have an iPhone 4 running 4.3.5 and I have an iPhone 3GS and and iPhone 4 both running iOS 5. Apple has made it really hard to upgrade/downgrade without jailbreaking, so I have to have big stickers on my 4.3.5 device to make sure I never upgrade it by accident.

I have an iPad (1) and and iPad2 that are both running iOS 5. Because the iPad is a newish device, I have assumed that everyone has upgraded or is upgrading soon away from iOS 3.2. I test with 4.3 in the simulator and test on the devices in 5.

What else?
In my experience, the biggest change as far as the OS breaking things happened between iOS 4.2 and 4.3 which corresponds to the 3G devices and the 3GS and above. Apple made lots of changes to the video, and graphics libraries during that time. Because the 3G and the older iPod Touches only go up to 4.2.1 (and even then don't support multi-tasking) we need to keep testing with those devices for a while. If an application doesn't require the new features in the newer OS'es then it seems like a poor decision to cut out the older devices, those people still have iTunes accounts and can still give us money.

My Wish List
Google has a really nice chart of what OS versions in what volume are hitting its App Store each week. This can help a developer make good guesses about what OS should be the minimum. Apple should be able to provide this data. I wish they would. It's one thing for me to gather data about my current customers (which I can do using Flurry or similar analytics) but that information is only about my small part of the customer base, not about the entire population.

Tuesday, October 25, 2011

iOS 5 Support for dismissModalViewControllerAnimated

Fixed an odd bug today and submitted v1.3 of Caroline's Cat Game to the store. Turns out that calling dismissModalViewControllerAnimated: for the [self parentViewController] basically stopped doing anything. When Apple makes changes that cause crashes that is bad, but at least we get some kind of alert. This just makes a button stop doing anything. No crash, no error, no nothing.

Anyway, if you are here because your calls to parentViewController have stopped working, here is the code we are now using. It appears that we need to use "presentingViewController" as well as a new call to dismissViewcontrollerAnimated. This all seems to be related to storyboards in iOS 5. Here is a link to the updated UIViewController documentation that talks about parentViewController and presentingViewController and the new dismissViewControllerAnimated. Right here below, is how we have set up our button to respond to being clicked. In the old days (last month) the method just had the   [[self parentViewControllerdismissModalViewControllerAnimated:YES] line but we have to add a little more now.

    if ([[self parentViewController] respondsToSelector:@selector(dismissModalViewControllerAnimated:)]){
        [[self parentViewController] dismissModalViewControllerAnimated:YES];
    } else {
        [[self presentingViewController] dismissViewControllerAnimated:YES completion:nil];

We have wrapped the whole call into an if statement so that we can still use the old way when on an older phone. The respondsToSelector: test is a good way to deal with using old and new code together. Trying to test for a specific version of iOS by name is pretty brittle; always use the respondsTo method instead.

Friday, October 21, 2011

Setting Up Xcode to Support Old iPhones

The iPhones from v1 to v3G all use the armv6 architecture
The iPhone 3GS and above all use the armv7 architecture (this is why it is not a really big deal that Apple has decided to continue to sell the iPhone 3GS)

The phones that run the armv6 are mostly on iOS 3.1.3. It is possible to pus 4.2.1 on an iPhone 3 but it runs so….very…..slowly….that most people have downgraded back to 3.1.3 or else bought a new phone. The 3GS runs iOS 4.x and 5.x pretty well so there is no reason for people with a 3GS or above not to be on 4x or 5x. The 3GS had some issues with iOS 4.0 but those speed issues have been addressed and any 3GS you purchase today will have IOS 5 on it.

As is their general theme, Apple hates supporting old things so the defaults in Xcode 4 (and 4.2 which is the current version) assume that everyone is running iOS5 and that they have a 3GS or better device. That is a reason the simulator only has options for 4.3 and 5.0 software. If you read this article from Apple Insider you will get a nice graphic showing how hard Apple is pushing iOS 5 and on what devices it runs.

To make your Xcode compile code for an old iPhone you have to do two things:
In Build Settings for your target, change the "Compiler for C/C++/Objective-C to LLVM GCC 4.2 (this is not the default)
In Build Settings for your target, change the "Architectures" from "Standard (arm7)" to "armv6 armv7"

You should also check these two settings since Xcode is going to want to have the Deployment Target be the latest version of the OS by default.
In Build Settings for your target, make sure that "iOS Deployment Target" is set to "iOS 3.0" or whatever the minimum you will support is
In Build Settings for your target, make sure that "Base SDK" is set to "Latest iOS (iOS 5.0)"

Now, you should be able to install and compile things on the old phones. The above settings are for ALL projects you create where you want to support older devices.

Update: Others are having this problem and posting similar fixes. I think I like this fix for the armv6 issue over at Retro Dreamer better.