Porting IconApp to iOS with Core Graphics

posted by crafterm, 13 May 2011

In this post I discuss several points regarding a port of an AppKit based NSView drawRect implementation under iOS with Core Graphics.

IconApp is an example app written by my friend Matt Gallagher as part of an excellent blog post demonstrating some advanced drawing techniques on the Mac using AppKit classes such as NSBezierPath and NSGradient, etc.

The shape drawn is a detailed icon, with complex radial and linear gradients, providing a blue background over a floral heart unicode character. A final piece of polish places a further gradient over the top of the character to give it a nice reflection similar to icons found in the dock, or on iOS.

Matt’s post discusses the actual construction of the icon and how breaking the draw down into various layers helps build a complex image out of smaller simpler constructs.

With iOS having an equivelent UIBezierPath class but an absence of NSGradient, I decided to scratch the itch of porting the drawing code to iOS, but at a deeper level with Core Graphics directly.

The code for the iOS version is available on GitHub if you’re interested in taking a look.

Generally speaking the code translated across to Core Graphics quite easily, and I could almost see the underlying Core Graphics calls used to implement the higher level AppKit classes. It was also much easier to write the core graphics code having a pre-designed target shape with all the colours, offsets, etc, available.

A few noticeable areas of difference:

C API

The obvious one – Core Graphics is a C API rather than AppKit’s Objective-C API, and works with the current graphics context directly as the first parameter to almost all method calls.

AppKit & Core Graphics Gradients

NSGradient draws a radial gradient when sent drawInRect:relativeCenterPosition: and a linear gradient when sent drawInBezierPath:angle:. Core Graphics splits these two operations into separate CGContextDrawLinearGradient and CGContextDrawRadialGradient methods but the parameters you provide differ. eg:

Core Graphics

void CGContextDrawLinearGradient(
   CGContextRef context,
   CGGradientRef gradient,
   CGPoint startPoint,
   CGPoint endPoint,
   CGGradientDrawingOptions options
);

AppKit NSGradient class

- (void)drawInRect:(NSRect)rect angle:(CGFloat)angle

I found it useful, particularly with the non-rectangular vertical linear gloss gradient to use CGContextGetClipBoundingBox to obtain a CGRect containing the clipping area, and hence ease finding the highest and lowest point of the path to use as the start and end point.

Colours

You can use UIColor to obtain a CGColorRef value, however a number of the CGContext* methods also accept ColorComponent values, eg:

CGGradientRef CGGradientCreateWithColors(
   CGColorSpaceRef space,
   CFArrayRef colors,
   const CGFloat locations[]
);

CGGradientRef CGGradientCreateWithColorComponents(
   CGColorSpaceRef space,
   const CGFloat components[],
   const CGFloat locations[],
   size_t count
);

components is an array of float values dependent on the colour space in use, eg. in the case of device gray colourspace where colours are defined as a white value and alpha, components is an array of pairs:

colourspace = CGColorSpaceCreateDeviceGray();
CGFloat glossLocations[]  = { 0.0, 0.5, 1.0 };
CGFloat glossComponents[] = { 1.0, 0.85, 1.0, 0.50, 1.0, 0.05 };
gradient = CGGradientCreateWithColorComponents(colourspace, glossComponents, glossLocations, 3);

but in the case of RGB colourspace where 4 numbers are required (red, green, blue and alpha), components expects an array of quads:

colourspace = CGColorSpaceCreateDeviceRGB();
CGFloat tComponents[] = { 0.0, 0.68, 1.00, 0.75,
                          0.0, 0.45, 0.62, 0.55,
                          0.0, 0.45, 0.62, 0.00 };
CGFloat tGlocations[] = { 0.0, 0.25, 0.40 };
gradient = CGGradientCreateWithColorComponents(colourspace, tComponents, tGlocations, 3);

The number of pairs, quads, etc, is specified by the last parameter identifying the location offsets for each gradient colour.

Drawing Text

Core Graphics can draw text to the screen with CGContextShowText and friends, however by default it expects MacRoman is the encoding unless you change the font type and then use Core Text or similar to convert string characters to glyphs. For the moment I’ve used NSString drawAtPoint:WithFont but will be adventuring down the Core Text path soon.

Summary

Complex constructs can certainly be drawn using Core Graphics, and by breaking each part of the final image down into layers of paths, fills, gradients, strokes, etc, you can build up quite intruiging and interesting graphics.

AppKit has some advanced Objective-C API’s for drawing, but you can certainly achieve similar results by delving into Core Graphics under iOS.

If you’re interested in the details the code for the full project is available up on GitHub.

Credits

Thanks again to Matt for an inspiring blog post, and with some help understanding the final gloss gradient!

Thanks to Nathan de Vries for reviewing the Core Graphics code and sending in a few fixes and scoping changes.

Captivate - iOS Development Reflections

posted by crafterm, 24 January 2011

Recently, Captivate, an iPad application I’ve been working on with fellow developers Justin French and Gareth Townsend was accepted into the iTunes AppStore!

Captivate is an entertainment/photography application for browsing photos on Flickr. Photos are grouped by popular and common tag names which the user can browse and select. Within each tag exists up to 3 clusters of content covering the most popular streams of photos for that tag. Each photo can be viewed similar to Photos.app, and with photo and mapping information displayed when available.

Social networking is also inbuilt allowing you to post photos you like to Twitter, Facebook, Tumbler, or simply navigate through to the photographers Flickr page direct.

The application was in development for several months, this article explores some of the discoveries I learned or were reinforced to me during that journey.

Teamwork

“The whole is more than the sum of its parts” – Aristotle

Having a team means you can divide things up and gain focus with a common vision. The productivity you gain from this focus is far higher than if you had to deal with everything yourself.

The skillset our team had included code writing and UI/UX design, which helped immensely as I was able to focus purely on development, while others could delve into the UI/UX we’d designed and provide feedback to ensure the feel of the application fit within the iPad experience.

There was also a lot more than just code to write, Captivate has 5 sets of API keys, Flickr & Facebook App Garden submissions, a dedicated website, Twitter account, plus the usual AppStore ad-hoc and app store submission process requirements including description text, screenshots, crafted tags, etc. Now released, there’s also support email and ongoing maintenance.

Tweak iteratively

We designed the look of the application upfront with screen and UI flow (back of the napkin style) sketches, an application design statement, following Apple’s recommend process.

Once into implementation though, we ran the app on the device together to check the feel of what had been built extremely often (at least every day, sometimes every few commits).

I found this particularly important as we caught quite a few little things in the design & implementation early on that were only visible once there was something to use – not just features that did or didn’t work, but nuances such as animation timings, wifi/3g network latency, UIKit scrolling and drawing performance, etc.

Running the code often allowed us to notice these things sooner rather than later, and nip them in the bud while the code that introduced them was fresh in the mind.

UIScrollView Journey

UIScrollView is worthy of its own Bachelor’s Degree – it’s ubiquitous, almost in all iOS applications, but its a tricky class to master. In our application it is everywhere, particularly when viewing/swiping full screen photos images a la Photos.app.

Captivate’s development taught me a lot about UIScrollView, particularly how complex things can become when you’re supporting swipe & pinch gestures, rotation and subview auto-resizing, and asynchronous loading across infinite content, all at the same time.

Apple does have some examples and WWDC video material in this area that are quite helpful, however even those don’t cover all these aspects in one application – there’s naunces and trickyness everywhere.

The journey was so intense, that after learning what we needed for Captivate, I shared the knoweldge as part of a presentation to our local Cocoaheads chapter, which was recorded and is available online.

Learning how to wield UIScrollView like a Jedi is a worthwhile pursuit, and one I recommend all iOS developers follow. It will save you mountains of time and stress down the track when you attempt to use it on a time dependent project.

Local Network

During development of any iOS application, you’ll inevitably have to implement some feature you’ve never done before (after all these are the things that distinguish our applications out from the crowd), or you’ll hit the preverbial wall debugging an issue and need some inspiration.

This is where having a network of developers really helps, as it’s more likely someone from this group will have done something similar, or at least have experience with the API, an open source library or debugging technique required.

Find your network, go to your local Cocoaheads (or start one if one doesn’t exist), attend WWDC, hack nights, and even related technology events such as Railscamp – apart from having an incrediblely awesome time and learning so much from all the knowledge being shared, you’ll meet some sensational people you can bounce ideas off, learn from and even potentially work with – not to mention these people will probably become good friends for years to come.

In our case, having local experts such as Matt, Sean and Luke really helped with bouncing ideas, learning special debugging techniques and the intricacies of various iOS frameworks!

Open Source Libraries & Tools

Coming from a Ruby on Rails background, using open source frameworks in an iOS app came as second nature – there’s a wealth of excellent code out there that can save you days if not months of work in development and/or debugging. Try to keep on track by focusing on the business logic you app does, and if you find yourself going off on a tangent building something that doesn’t directly relate to your features – step back and see if it’s been solved already.

In our case, we leveraged several well known iOS open source frameworks that really gave us a step up in terms of focus on what specific features our application offered, such as ObjectiveFlickr, ShareKit, asi-http-request to name a few.

During Captivate’s development I also learnt a lot about Apple’s toolset and how beneficial they can be. Instruments, Shark, Xcode, etc, are all really useful, as are some of the smaller non-descript tools such as symbolicatecrash to help cross reference crash logs. Git and GitHub was also a winner as its been on many other projects in the past.

Summary

The list could go on but I’ll leave it there for this post. Captivate is up on the AppStore now, and we’ve just started with some of the features we’ll be implementing over the next few months – we’d love to hear any feedback and any ideas you might have about how to improve it.

Melbourne Cocoaheads - UIScrollView Giggles & Glory

posted by crafterm, 30 October 2010

A few weeks ago I presented at the local Cocoaheads here in Melbourne, about UIScrollView’s, and the various trials & tribulations I’ve had working with them in the past.

The talk was structured with a story first about how UIScrollViews have mystified us over the years, then a set of 10 demos, which were contained within a single ‘photo viewer’ style project, using git branches to successively add a feature that exhibited some scroll view issue, and then add the fix with a discussion.

A friend of mine Oliver recorded the talk on his iPhone 4, and has since made it available on YouTube. The quality is great considering its an iPhone 4, but please understand its not quite the level you’d see from WWDC.

I really enjoyed giving the talk. The aim was to help anyone venturing into similar grounds with UIScrollView to recognize those issues and be armed with solutions to them. Enjoy.