Wednesday, 21 November 2012

BVUnderlineButton - iOS Control for web-link style button

My client requested a web-link style button for a forgot password button on the log-in screen, which seemed on the surface to be a simple thing to implement.

After looking into it I was surprised to learn that there's no built-in way to underline text in a UI element on iPhone - you have to simply draw it yourself.

Searching the web I found some sample code in varying states of usability. By sticking these together and putting a couple more features in I came up with CVUnderlineButton.

It's available on GitHub at https://github.com/benvium/BVUnderlineButton

Simple example button


Here's the README for the control.


BVUnderlineButton

Simple UIButton subclass that draws a button with the title underlined.
Based on code originally found at http://davidjhinson.wordpress.com/2009/11/26/underline-text-on-the-iphone/ with all the fixes from the comments and some further tweaks.
This code requires ARC.

Usage

Add BVUnderlineButton.m and BVUnderlineButton.h to your project.
If you're using a Storyboard or XIB files:
  • drag a UIButton to your stage, set the 'Type' (under Attributes Inspector / Button) to Custom
  • Set the Class (under Identity Inspector / Custom Class) to BVUnderlineButton.
If you're using code, just create the button as you would a regular Custom style UIButton, e.g.
#import "BVUnderlineButton.h"

BVUnderlineButton *button = [BVUnderlineButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self 
       action:@selector(aMethod:)
forControlEvents:UIControlEventTouchDown];
[button setTitle:@"underlined" forState:UIControlStateNormal];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[view addSubview:button];
Adjusting the underline position

Depending on the font you may wish to adjust the vertical position of the underline using the underlinePosition property. The default is -2 pixels.
If you're using a Storyboard or XIB files:
  • Select the BVUnderlineButton instance on your stage.
  • Open the 'Identity Inspector' and click the + under 'User Defined Runtime Attributes'
  • Type 'underlinePosition' for keyPath, choose 'Number' for type, and type in the new value as a floating-point number (e.g. 1)
If you're using code, just set the underlinePosition property.
button.underlinePosition = 1;

Wednesday, 14 November 2012

RestKit: Could not find an object mapping for keyPath: ' '

RestKit is an excellent Objective-C framework to make dealing with downloading and uploading data to and from a REST-based server considerably simpler than coding it all yourself.

When I started out I found it pretty straightforward until I hit a REST command that returned a JSON array at the root object, where I ran into the dreaded Could not find an object mapping for keyPath: ' ' error.

Here's an example REST response that caused this error:

[ { "name":"foo"}, { "name":"bar"} ]

It seems restkit expects there to be a root level object e.g. 

{ "data": [ { "name":"foo"}, { "name":"bar"} ] }

If your server doesn't do this, and you can't change it, then the automatic mapping system doesn't seem to work.

The solution is pretty simple (manually tell the system which mapping to use) but I took me a while to figure out, so I thought it might help others if I posted it here.




Wednesday, 19 September 2012

Making apps work on iPhone 5 screen size

Here's a series of gotchas I found in attempting to convert my apps to work correctly on the new taller screen of the iPhone 5.

You need to add a new splash screen at the iPhone 5 size.

This is easy. Just create a new splash screen that's exactly 640x1136 pixels in size.
Call it Default-568h@2x.png and include it in your app.

The existence of this file is the magic key that tells iOS6 that your app is ready for iPhone 5. Without this file your app will always appear bordered.

Use window.rootViewController to add your initial view

First up, if you're building your initial views in code make sure you add your initial view to the window with window.rootViewController rather than the old way of simply adding the view as a subview.
If you don't do this, autorotating will not work at all for your app under iOS 6.

Here's an example:

navigationController = [[UINavigationController alloc] initWithRootViewController: myRootVC];
//[window addSubview:[navigationController view]]; Don't do this!
window.rootViewController = navigationController; // Do this instead!

Use viewDidLayoutSubviews rather than viewDidLoad to set up widget sizes relative to the window

If you lay out your UI in Interface Builder, and set the struts and springs properly, you might expect that inside your viewDidLoad method, your widgets will be auto-laid out to the new taller size.
They won't be! Everything will be exactly the size that you set it to be in Interface Builder at that stage.
As a result you should make sure you don't depend on the size of a widget in the viewDidLoad method. I hit this when I wanted to programatically create a view half the height of the screen.

Of course, by the time the user sees the screen everything is the right size. The issue is that this resizing takes place after the viewDidLoad - but where?

The answer is to use the viewDidLayoutSubviews method which is called after the automatic layout has finished. Override this method and insert your custom sizing.

- (void) viewDidLayoutSubviews
{
    // do clever layout logic here. super call not needed.
}
Automatically loading iPhone 5-sized images where required

You'll be familiar with the way that [UIImage imageNamed:] automatically loads @2x versions of images when running on a retina device.  Unfortunately, imageNamed: will NOT automatically load -586h@2x versions of images when running on an iPhone 5.

Sometimes this doesn't matter, for example icons and non-full screen graphics are probably the same on iPhone 4 & 5. However, if you have full-screen background images, or full-width / height background images for toolbars etc you will have problems. Your 480-high images will most likely get stretched (and will probably look horrid as a result).

You can manually check the screen size and load the right image like this:

UIImage* myImage;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
if ([UIScreen mainScreen].scale == 2.f && screenHeight == 568.0f) {
   myImage = [UIImage imageNamed:@"myImage-568h@2x.png"];
} else {
   myImage = [UIImage imageNamed:@"myImage.png"];
}  

Or better you can 'swizzle' the imageNamed function itself, and make it automatically pick up the right image (if available).
Here's an example I found on github to do exactly this (I did not write this code, copyright is with the author).



currentTime not working with the HTML5 Audio tag.. a solution!

Today I've been using the HTML5 Audio tag (in fact actually just the JavaScript Audio() object). I ran into a number of issues where clearly-documented functions simply didn't work.

Here's some sample code, with XXXXs marking what wasn't working for me (in any browser).



Now it turns out the reason these functions didn't work is not that browsers suck and this whole HTML5 is hugely overrated (my initial reaction), but was in fact that the server serving the MP3 files did not support streaming. Of course if would have been nice if somehow the audio object would warn me about this rather than sitting there doing nothing, but you can't have everything.

Anyway, now the question was how do I make my server support streaming? Do I need to sign up for some kind of premium service? Or spend weeks implementing a streaming sever?

Thankfully not. It turns out you 'simply' need to make your server support HTTP Range requests. If your server uses PHP, you're in luck. I've got a complete working example which I can share with you right now.

Note I did not write this code, I found it here and added it to gist for posterity.

Simply add this code to your PHP server, and when you're about to serve an MP3 (or OGG or whatever) call this function.



For reference, I call the function like this (where $fullPath is the absolute path to my MP3 Resource).

smartReadFile($fullPath, basename($fullPath), 'audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3');

Tuesday, 21 August 2012

OSX Terminal: alt-left jump word ctrl-left go to start and more

By default OSX Terminal doesn't let you use many standard OSX text-editing shortcuts. For example, CMD or Control Left normally jumps to the start of the line. Alt-left jumps left one word. Also forward delete doesn't work.

Happily it's possible, though slightly confusing, to add these shortcuts back.

Here's how:
  • Open Terminal Preferences
  • In the Settings Pane, select Keyboard


  • Click the + button



  • Now let's add those shortcuts back.
For jump-to-start-of-line:
  • Key: Cursor Left
  • Modifier: Control
  • Action: send string to shell: (the default)
  • Click in the empty white box and press CTRL-A. \001 will appear
For jump-to-end-of-line:
  • Key: Cursor Right
  • Modifier: Control
  • Action: send string to shell: (the default)
  • Click in the empty white box and press CTRL-E. \005 will appear
To re-activate forward delete
  • Key: forward delete
  • Modifier: none
  • Action: send string to shell: (the default)
  • Click in the empty white box and press CTRL-D. \004 will appear
To move forwards one word at a time
  • Key: cursor left
  • Modifier: option
  • Action: send string to shell: (the default)
  • Click in the empty white box and press Escape+b. \033b will appear
To move backwards one word at a time
  • Key: cursor right
  • Modifier: option
  • Action: send string to shell: (the default)
  • Click in the empty white box and press Escape+f. \033f  will appear.


I find adding these shortcuts in makes terminal much more usable. 


Friday, 17 August 2012

Editing a Phonegap app directly on your device without reinstalling or rebuilding

How often have you built a large Phonegap app, copied it to your device, played with it for a while and then discovered a tiny bug? Something that you just know is just a single line, or even a single character change...

Normally to test your fix you'd need to make the change in your IDE, and re-build and re-upload the app to your device using Xcode. This can take ages if your app has a lot of content.

If you're using Phonegap build, it can take even longer as you'll have to wait for the whole app to build and then download again.  This can be particularly painful if your one line change takes a few goes to get right (especially anything UI or animation-related).

So what's the solution? Well, it's actually possible to connect your iPhone to your Mac, edit your JS file (or any other file) and re-save, and run the app again.

I was surprised to discover that this is possible - I'd assumed the code signing verification system built into the iDevice would notice that the bundle had changed and refuse to run but it doesn't. At least not in my experience.

Here's how to do it (note commercial software required, though the demo does actually work. I don't work for them and am not paid!)


  • A new drive will appear. Open it.
  • Inside you'll see a bundle, e.g. com.example.myapp.
  • Right-click the bundle and choose 'view package contents'

  • Open the www folder (or whichever folder your app uses for PhoneGap content)

  • Open the JS file you want to change in your favourite text editor (we use Sublime Text 2.app)
  • Save changes. This writes it straight back to the device!
The above instructions are for Mac, but I suspect pretty much the same process will work for PC too.

Now you can re-start your app and the new code should be live! 

This technique works for HTML, JavaScript, CSS and other text files. It most likely won't work with PNG files as the app build process compresses them into a proprietary Apple format (you can convert, but it is fiddly). I haven't tried JPG, MP4, files etc. Your milage may vary.

Dealing with Cacheing issues

Note that quite regularly you'll find the iDevice still runs the old version of your JS file. This is due to the UIWebView caching the JS file.

The easier way to fix this is to force the iDevice to reload the Javascript file by changing the link to the JS file.   To do this, edit your main HTML file using the technique above.


Change the link from (for example)

<script src="myfile.js" type="application/javascript"></script>

To:

<script src="myfile.js?_cache=123" type="application/javascript"></script>

Each time you change the myfile.js, also update the _cache=123 number and you'll always get the latest version. _cache doesn't actually mean anything, you can choose whatever you want.

A kaleidoscope built using (almost) pure CSS3

Here's a kaleidoscope made using pure CSS3. Ok, almost pure CSS.
The only bit of JavaScript is used to kick off the animations. I could have done this is CSS too, but ran out of time.
The way it works is there's a 500px square div containing a triangular -webkit-mask-image (defined by the SVG file). This mask image ensures that regardless of the size and position of the background image, you only ever see a triangular shaped section. You can think of it as a 'cutout'.
This div is duplicated, flipped and rotated eight times to create the effect. We then animate the resulting 'scope by simply moving the position of the background image.
I think it's quite neat. Unfortunately this fairly simple page actually crashes iPad and iPhone devices running (at least) 5.1.1 after a while. Very frustrating!

View Demo

Source Code

Wednesday, 25 July 2012

The correct way to use your own NSError objects in your app

Thanks to Mike Abdullah @mikeabdullah for pointing out the 'deliberate' (ahem) mistake in this post the first time around. Oops.

Thursday, 28 June 2012

Take an iPhone or iPad Screenshot from the Command-line


Here's another tip using the great libimobiledevice library http://www.libimobiledevice.org/, originally built to manage iOS devices on Linux.

Taking app screenshots is a bit of a pain. We tend to do the following:

  • Run the app
  • Press Home+Power together to take a photo,
  • Go to the photo library
  • Find the photos
  • Email them to ourselves.

Using this method you can type a Terminal command, and have the screen appear directly on your computer. Much easier.
Here's how:

Connect your device, open up Terminal.app and type:

idevicescreenshot

It'll save a .TIFF file to the current directory. Easy!

Tuesday, 26 June 2012

View the log output of any app on iPhone or iPad


Here's another tip using the great libimobiledevice library http://www.libimobiledevice.org/, originally built to manage iOS devices on Linux.

This method shows you how you can view the log output (e.g. what you see in Xcode when running in debug mode) on any app, whether it's an AppStore build or an AdHoc build.

Some apps are more talkative than others - it all depends on how careful the developers were to remove log messages in release builds.

I find this most useful for debugging issues in release versions of our apps. If a user reports a sequence of actions causes a crash, you can try this out while watching the log from your app.

Here's how:


Connect your device, open up Terminal.app and type:

idevicesyslog

Up pops a real-time display of the device's system log.

With it being a console app, you can filter the log using unix commands, such as grep


For instance, see all log messages from a particular app:

idevicesyslog | grep myappname



Install iPhone / iPad / iOS App IPA without iTunes or Xcode

Using the great libimobiledevice library http://www.libimobiledevice.org/, originally built to manage iOS devices on Linux, it's possible to do a bunch of cool things, including installing an app directly on a connected device without needing to use iTunes or Xcode.

Here's how:

Now connect your iDevice, open Terminal.app and type:

ideviceinstaller -i myApp.ipa

Note: Your IPA will need to have a valid code signature and mobileprovision file for the install to succed. This system is NOT a way around iOSs security!

Saturday, 21 April 2012

AppCode Delegate Pattern Live Template

Here's a Live Template to generate a common delegate pattern in AppCode, JetBrain's Objective-C IDE.

Thursday, 19 April 2012

Script to automatically create retina and non-retina versions of images

I often get sets of files from graphic designers for iOS apps. Ensuring that the standard and @2x versions of all the files are present and correct is a bit of a pain, for the artist and for myself. The number of times there's been a mispelling in one of the images has been.. well .. many.
Instead I now ask them to send only the retina versions of the images, without the @2x tag in the filename (this has caused confusion).

I then run this script on the whole folder of files, and out pops a folder containing all the files properly renamed and resized. I use the incredible useful 'sips' command-line application that comes with OSX.

The fiddly bit of this script was working out the new size of non-retina images. Yes, they're half the size, but sips doesn't support percentages.
Credit to (person on superuser.com who I'll find eventually) for the neat sed command to extract the pixel size from the output of sips.
Note: clearly the file extension part can be done better. But it works for me right now.

Thursday, 5 April 2012

Get version number of live app on App Store in Phonegap app

A nice feature you might want to add to your iOS app is to notify the user when a new version is available on the App Store. Of course the user can go to the App Store manually and install the update, but we've noticed quite a few users don't do this very often. Some carefully-judged prompting is bound to help.

It turns out there's an API that Apple run that can return, in a handy JSON or JSONP format, the metadata for a particular app on the app store. This includes both the version number of the app, and even better, the 'what's new in this version' text.

You can use this information on your app's startup to check if a new version is available, and tell the user what's in it, and then finally, link them to the update page for the app.

Here's some bare-bones bits of example code to get you going. You'll need to know your app's Apple ID (e.g. 387022138). You can find this in the iTunes Connect interface). I'm also assuming that you're using jQuery or Zepto in your app.

The below also works in the browser.

// Alert the version number and release notes of an app.

    $.ajax({
        url:"http://itunes.apple.com/lookup?id=<APPLE ID HERE>&callback=?",
        success:function(data) {
            if (data && data.results && data.results.length) {
                alert(data.results[0].version);
                alert(data.results[0].releaseNotes);
            }
        }
    });

Here's the full list of data you might want to use:
Screengrab showing properties of the response from the API call. Click to view full size.


To open iTunes looking at your App's update page open the following URL:

itms-apps://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftwareUpdate?id=<APPLE ID HERE>&mt=8









Tuesday, 28 February 2012

How to link to unreleased App Store apps


For a client I'm making an app that comes in 'full' and 'lite' versions. The client wanted to have a button in the lite version that opens the full version in the App Store on the device.  Now this was a little bit tricky because both the full and lite versions were in progress and hadn't been released yet. They didn't even have names.. I needed to release both apps at the same time so testing the link worked seemed impossible.

The solution was to have the 'upgrade' button link to simply open an html page on a site I own in Safari on the device. Doing this meant I could alter what the link actually did at any time.

Once the client had set up the apps in iTune Connect, I asked for the Apple ID of the full version of the apps (shown in iTunes connect on the App Info Page).

I then updated the HTML page that was opened by the app to contain the following (replacing the 123456789 with the real app ID of course).

<script type="text/javascript">
window.location = "itms-apps://itunes.apple.com/app/id123456789?mt=8"
</script>

Now when that button is pressed, Safari flashes up briefly (but doesn't actually display the page) and then the App Store app opens and displays the app. Voila!

Ah, I hear you say. Can't I just use the app name rather than the Apple ID? Well, yes you can. Here's a load of different ways of linking to apps on the app store.

For me though, these options weren't so good as my solution because:
  • There a fairly fiddly way the actual name is converted to a url-friendly name. Easy to get wrong.
  • My app names weren't finalized at the time of writing anyway.
  • If the client changes the name of the app, the link will break.