Updating for Modern iOS Design – A Case Study

TL;DR — Check out the pictures and videos on this page, and you’ll see how I adapted the basic circular menu of Click version 1 to fit the new iOS 7 design aesthetic, not just in visual terms, but also in interaction style: clean and almost chrome free, with more color, more seamless transitions, new inertial/physics-y interactions and direct manipulation transitions.

 

Hello Again!

As you can probably imagine, a LOT has happened in my life in the last almost two years since I’ve posted on this blog (and therefore also on iDevBlogADay)! And most of it has not been iOS development related, so I guess that’s my excuse for such an incredibly long time between posts. But, I have been working when I have the time, and I just released a new version of my iPhone app called Click Metronome. The original version was the topic of many of my posts here on this blog, especially about the design of the circular control. For this post, I thought it might be interesting to explore some of the design changes I made to Click when releasing version 2. The principles behind the design – and the features of the app – are largely the same, but the actual look and feel is completely different. So first, how about a look at a screenshot from version 1, and then the promo video for the new version for iOS 7+. Can you guess which is which? ūüôā

 

Screenshot 2014.08.02 13.38.42

 

Circular Menu

At the time, I was really happy with how Click turned out.¬†Then – and now – there’s a lot of metronome apps out there, but they seem to either be too simple and missing key features, or feature-rich but with a screen that’s completely full of buttons. So, one of my main goals for Click was to offer all the main features people would want – wide tempo range, variety of time signatures, accent options, etc. – and make them quickly accessible, but without cluttering up the screen with tons and tons of buttons. So, after much iteration, I came up with this menu system that showed all the current settings around a small circle. Then, the user taps an item and all the options for that setting are revealed in the circle, ready to be set by rotating. Here’s that menu in action in version 1 and version 2 (watch in slo-mo):

 

Click1MenuSmall

Click2MenuTapSmall

 

Version 1 Problems and Fixes:

I felt like I accomplished the goals I had set out, and watching people as they used the app, they always eventually figured out how to use it. But, it was never as intuitive and easy as I had hoped. As I got feedback from others, and as I thought more and more about the app myself, I came up with a whole list of problems with the implementation of the menu:

Continue reading “Updating for Modern iOS Design – A Case Study”

Advertisements

Circular Scrolling + Inertia

Why Inertia?

I’ve talked a lot on this blog about the circular scroll-view/table-view thing I created for my metronome app, and while I’m very pleased with how the whole thing turned out, I was still annoyed that I hadn’t figured out how to do one thing: give it inertia like UIScrollView. A commenter on one of the posts¬†even asked me about inertia, and I basically just flat out said I’d like to, but it would be too hard! But after that, it just kept bugging me; there had to be a way. Inertial scrolling on iOS – and now on OS X too – just feels so nice. It’s one of those things that many people probably don’t even think about or notice until it’s gone. After feeling the smooth scrolling and realistic deceleration of UIScrollViews, my little rotary control just feels like garbage. I mean, come on, you give it a little flick and it stops rotating immediately when you lift your finger!? Lame! Frankly, my control just doesn’t feel quite¬†completely at home on iOS yet, and I want to change that.

Maybe UIScrollView Can Help!

I got really exciting after watching the WWDC 2012 video for Session 223: Enhancing User Experience with Scroll Views. In the presentation, they give an excellent demo of how to leverage the physics of UIScrollView even when your app uses OpenGL. The UIScrollView is not used for any drawing, but by passing touches through it, listening for the scrollViewDidScroll, and reading the contentOffset, you can enjoy all the benefits of UIScrollView like inertia and edge bounce without actually drawing your content in the scroll view itself. Maybe, I thought, I can use that same trick on my circular scroller. Using the same techniques from the video, I was able to get up and running with the same circular action as well as a bit of UIScrollView provided inertia, but it just did not feel right at all. The geometry of linear scrolling and deceleration just don’t line up with what I need in terms of angular rotation.

Eureka!

The video technique didn’t work out quite like I had hoped, but I still took away one key idea: implement the inertia in the touch handler, not in the circular view itself. Seeing as how no one has seen the details of my implementation, that might not make sense to anyone but me right now, but I’ll try to explain. At its most basic, my control has two parts: the transparent view which interprets the touches as circular movements (a gesture recognizer, if you will, although it’s not an actual¬†UIGestureRecognizer subclass) and the display view that receives notifications of those movements and rotates, swapping elements in and out sort of like UITableView cells. I had already implemented a bit of animation in the view component that allowed it to center on a certain cell, and I kept trying to think how I could make this also work with a longer rotation, and I was running into a lot of problems with the cell swapping during the rotation animation.¬†But, if I implement the inertia on the control side and use the same delegate call for rotation that comes from an actual touch OR from the inertia, the view side doesn’t even know the difference. And it actually worked pretty well without much modification at all!

Check out the Repo

So, here’s the real/final point of this post: I’d love to get this whole rotation control thing (or most of it, at least) out into the wild as an open source component, but it’s been hard finding the time. It’s a fairly complex API, and I don’t feel like I can just release it without some more clarity and focus, as well as a nice sample project. So, I’m going to try to get it out there piece by piece if possible. I’ve put up a little bare bones project on GitHub – my first! – to test the waters. Let me know what you think, and of course, feel free to fork it and make it better. Right now it’s just a simple version of the rotation control view (RDDRotationControlSurface) which is hooked up to a UIViewController delegate that spins a UIImageView based on the input. Wow, that sounds way more complicated than it is – it’s just spinning a little picture of a very nice-looking German (or Austrian; can’t remember where this picture was taken) man in a cool getup. Don’t ask me why I chose this picture; it was there, and I went with it!

PS: You may be wondering why I didn’t go with a UIGestureRecognizer subclass for this. As far as I can tell, the gesture recognizer API would not work very well with something like this which needs to continue sending updates even after all touches have stopped. So, in the end, I’d still end up with a UIView subclass of some kind. Doesn’t mean GR couldn’t help here, but I just didn’t go that route.

UIKit and GCD

Graphics Bottlenecks

Creating a responsive user interface is one of the most important considerations for a mobile developer, and the smooth scrolling and quick responsiveness of iOS has been one of its hallmarks since day 1. — I’ve gotta be honest here; I still find myself every now and then finding great amusement in just flicking around a simple web view or scrolling some text on my phone. It just feels so right! — Keeping a smooth flow and one to one correspondence between user touch and visual display is crucial for maintaining the illusion and feeling that one is directly interacting with the objects on the screen. One key consideration to make this happen is do not block the main thread. If you are doing¬†anything that might take a significant amount of time, you must do this on a background thread.

With iOS 4.0 and the introduction of blocks and Grand Central Dispatch, it became much easier to complete tasks in the background asynchronously without having to dive into the implementation details of threads and such. If you haven’t yet tried out GCD, take a look at the docs, or check out this tutorial to get you up and running quickly. It’s great for parsing data, downloading from the network, etc. off the main thread, and GCD makes it very easy to write code that will call back into the main thread once the background process completes. What it doesn’t work well for is anything to do with UIKit or the user interface. All drawing and touch interaction takes place, by design, on the main thread. So, what do you do if your drawing itself is taking too long and blocking the main thread? I’m sure there were people much cleverer than me who found some ways to get around it and do some drawing in the background, but basically up until iOS 4, UIKit was not thread-safe¬†at all. If your drawing is too complicated and blocks, then you need to optimize it or simplify it. However, the release notes for iOS 4.0 contain the following short section:

Drawing to a graphics context in UIKit is now thread-safe. Specifically:

  • The routines used to access and manipulate the graphics context can now correctly handle contexts residing on different threads.
  • String and image drawing is now thread-safe.
  • Using color and font objects in multiple threads is now safe to do.

This was not something I had really been interested in or concerned myself with until I ran into just such a problem recently. I created a custom subclass of UILabel which adds a colored, blurred shadow to the label to give it a glow effect. But, this drawing took drastically longer than the regular string drawing. For example, for the drawing that happens at app startup, using regular UILabels takes 104 milliseconds total in drawRect:. To draw the exact same strings with shadows takes 1297 milliseconds! So, you can imagine what this does to frame rates when there are multiple labels being updated rapidly during an already CPU intensive section of the code.

Multi is fun threading!

Since I already know ahead of time exactly what strings I need to display during this particular bottleneck, it would be nice to be able to draw all the labels at once in the background and cache them for later. My first approach was, Continue reading “UIKit and GCD”

Photoshop Layer Comps

Just a quick Photoshop tip today, but it’s something I’ve been making extensive use of the last few weeks, so I thought I’d share. If you happened to read my last post and/or watch the video, you would have seen that in my new metronome app, I’m handling interface rotation in a somewhat different way than most apps. Rather than using the standard system autorotations – using the usual springs and struts in Interface Builder or the UIView’s autoresizingMask property – I’m leaving the basic layout of the controls the same and just rotating the contents. It’s kind of hard to describe, so if that doesn’t make sense, skip to about the 10:00 mark on this video.

Here’s the gist of the code to make this happen:

  • In the main ViewController’s shouldAutorotate method I only return YES for ¬†UIInterfaceOrientationLandscapeRight, the same orientation the app launches in. Meaning, the view controller will not do any auto-rotating once it’s loaded into its initial state.
  • I’ve registered for the UIDeviceOrientationDidChangeNotification. Even though the View Controller will not do anything automatically when the device is rotated, the system will still generate these notifications when the orientation changes.
  • When I receive this notification, I pass the message along, and the individual views apply whatever sort of rotation transform they need to in order to remain “right side up.”
  • If the Status Bar is visible, you can also programmatically set its orientation with:[[UIApplication sharedApplication] setStatusBarOrientation:(UIInterfaceOrientation)orientation animated:YES];

What this means from a design perspective, is that the UIImageViews themselves, which contain the main interface chrome, do NOT rotate at all. So, here on the right is what the main control frame looks in the launch orientation – notice the shadows, gradients, etc. all use the “canonical” iOS 90 degree light source.

Let’s say the user then rotates to LandscapeLeft – my subviews will rotate themselves, but the image will stay exactly the same. The image on the left is the same, but rotated 180 degrees. It’s strange how much different – and more noticeable – the light/shadow/gradient effects are when they’re flipped around the wrong way!

So, in order to maintain the right look, what I need to do is create separate images for each orientation and load these in as part of my custom rotation handling. Here’s where Photoshop layer comps come in. What they let you do is take snapshots of certain aspects of your document state and then reload them with one click. For example, in my case, I’ve set up one Layer Comp for each of the four orientations I’ll support. Here’s the workflow:

  • Setup the document for the current orientation. In the case of LandscapeRight, that means 90 degree light sources for all drop shadows, gradients that go light to dark from top to bottom, etc.
  • In the Layer Comps window – add it to the toolbar from the Window menu if you don’t see it – select New Layer Comp from the pulldown menu.
  • In the dialogue box that opens, give your comp a name, select which parts of the document state you want to be saved as part of the snapshot, and add any helpful comments you might have.¬†
  • For this particular case, I’ve told the Layer Comp to only save the Layer Styles of the document’s layers.
  • Repeat the process for each orientation, setting the light sources, gradients, etc. on the Layer Styles, and then saving it as a new Layer Comp.

By using vector/smart objects and layer styles – you are doing that aren’t you? – the exact same set of objects and layers is used for every orientation. I’m free to adjust the positioning, size, and shape of the objects, and then, when it comes time to export for each orientation, I just click through the four Layer Comps one by one, and all my light and shadow effects are applied instantly to all objects.¬†It takes a bit of work to setup, but once it’s ready, it saves huge amounts of time over going to each object individually and resetting the properties every time I want to make a change in the design and re-export for each orientation. For things like the “Tap,” “+,” and “-” labels, and for different button states, I also have a set of Layer Comps which control layer visibility. So, for example, if I need to re-export the image for the “pressed” tap button – I hit the Layer Comp for the orientation I want, which loads the correct layer styles, then hit the “Tap Button Pressed” layer comp which won’t affect the layer styles, but will hide the normal Tap button layers and show the pressed ones. Two clicks and I’m ready to export. So, that’s how I’ve been using Layer Comps in my particular case to speed up my design workflow – hopefully it gives you some ideas for how you might be able to use them in your own workflow!