The previous article I wrote about Creating a Keyboard Show Hide Button garnered a comment from Moosc asking for a couple of tutorials he’d like to see.
Looking through the 4 screen shots that were linked, the MSNBC color wheel really caught my eye. From the screenshot I figured there were at least a couple of ways I could go about making a control like that, which got me wondering – How did MSNBC make that for their app?
This tutorial is going to explore how to make the Shows section of the pinwheel. I choose Shows because it’s got a set of disabled buttons which will involve writing code with two difference behaviours (enabled & disabled), whereas the Topics section could likely be done with identical code. Specifically in this post you’ll learn:
- How to find clues about how an app is built
- What type of business decisions go into building an app
- How to use arrays to simplify your code
- How to make an animation without using animations
- How to create a non-rectangular button
Looking for Clues
The first step in the process is to extract the images from the app. Download the MSNBC application on iTunes and use the appcrush script to extract the images. Instructions on using appcrush can be found here.
I have no idea how this app is being built, but I have my suspicions. My hypothesis is that each piece of the pinwheel is a separate image (including text) and that the white image that shows up on touch is also an image that they simply rotate to fit the particular angle of that pinwheel.
So from that hypothesis I’m scanning through that image directory looking for those images. I’d expect to find 12 images for the Topics pinwheel and 7 images for the Shows pinwheel, plus either 1 to 5 images for the greyed out section – that will be dependent on how good their programmers are! :P
The first image I hit is business_rest.png
which confirms my suspicion that there are going to be 11 other images for each topic on the Topic pinwheel.
business_rest.png
entertainment_rest.png
health_rest.png
politics_rest.png
selectall_rest.png
sports_rest.png
techscience_rest.png
topstories_rest.png
travel_rest.png
usnews_rest.png
weather_rest.png
worldnews_rest.png
Through the first couple of images I didn’t notice the _press.png image that was paired with each topic. In fact when I saw it on business_press.png
I thought it was a moon. I’ve placed it on a black background to show you what I mean:
But after going through all the images it became very clear that each of the above topic images have an associated _press.png
for when the image is touched.
Moving onto the Shows pinwheel we get a list of 7 images:
dateline_rest.png
hardball_rest.png
meetthepress_rest.png
morningjoe_rest.png
nightlynews_rest.png
rachelmaddow_rest.png
todayshow_rest.png
And 5 disabled images for the bottom of the Shows pinwheel:
shows_disabled0.png
shows_disabled1.png
shows_disabled2.png
shows_disabled3.png
shows_disabled4.png
After seeing all these images, I now know that my hypothesis is correct and that I can begin to think about layering these images together to create the pinwheel.
What type of Business Decisions go into an App?
Now I made the comment earlier that the above could be a single image depending upon how good the MSNBC programmers were, and I think I need to clarify that statement.
You see when programming for clients it’s always a trade off between simple and complex. Sometimes you can go with the simple solution to a problem, and other times you have to rely on complex. In this case, there are two options:
One option (simple) is to use serperate images for each piece of the pinwheel. This allows the developers to very quickly create a working version, with little hassle. The trade off comes when the images need to change, this means a graphic designer and a programmer need to be involved. New images need to be cut and then moved into the application. Not a huge hassle but it’s there. The other potential drawback to this is that if the Shows ever change and Rachel Maddow no longer has a TV show, or they add another TV show to their line up they then have to make an app update. Now I say potential drawback because sometimes app updates are a good thing. They give you another chance to try and hook a user that isn’t engaged into your application when your update shows up in their iTunes.
The second option (complex) is to create each pinwheel item programmatically, including touch animations. The benefits of a solution like this is that any changes for the pinwheel can be done in code and if programmed correct with little time and effort to accomplish. The drawback to this scenario is that it would take a larger amount of time and effort to create such a solution.
At the intersection of those two solutions is where programmers frequently get caught. They want to go with the more programmatic approach because in the long run it is less work and also gives them something challenging to work on (I know that’s what I was hoping for when I ran through the images.) But the business side would rather take the less expensive and quicker solution because they’re able to get something working and functional out to their customers faster. Neither option is right or wrong, it largely depends upon who’s making the decisions and which trade offs are willing to be made. And in the case of this tutorial: we’ll go with option 1!
Background
Let’s start by writing code to add the background image to the view under viewDidLoad
:
UIImageView *background = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:@"radial_shows_bckg.png"]];
[self.view addSubview:background];
Running the app at this point we get a shell with a background image. But ewww, what an ugly grey status bar. Changing that to black can be done in the xib or programmatically:
[[UIApplication sharedApplication]
setStatusBarStyle:UIStatusBarStyleBlackOpaque];
And while this works in app, it doesn’t change the status bar when the app launches and so you get an ugly grey to black transition. That’s because this setting can also be added to the app’s plist:
When done within the plist, the value is going to be applied across the entire application so you don’t actually need to programmatically set the status bar colour.
Adding a pinwheel piece
The next step is adding a container to hold the pinwheel images. The container is going to be a view 306×306 pixels, based on the image size. Now obviously it’s pretty hard to position a transparent view correctly, so let’s add an image to that view so we have a point of reference:
UIView *pinWheel = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 306, 306)];
UIImageView *dateline = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:@"dateline_rest.png"]];
[pinWheel addSubview:dateline];
[self.view addSubview:pinWheel];
This code has to go after the background image code so that it gets layered on top of the background image.
Because the pinwheel images are not the full iphone width and height it’s easier to use the center of the view as our point of reference for positioning the view. We can set that up with the below code. The - 0.75
x-offset and + 29.5
y-offset allow the image to align the MSNBC app:
CGPoint center = CGPointMake(
(CGRectGetWidth([self.view bounds]) / 2.0) - 0.75,
(CGRectGetHeight([self.view bounds]) / 2.0) + 29.5
);
[pinWheel setCenter:center];
Adding many Pinwheel pieces using an Array
Let’s add the next pinwheel piece:
UIImageView *hardball = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:@"hardball_rest.png"]];
[pinWheel addSubview:hardball];
And another:
UIImageView *meetthepress = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:@"meetthepress_rest.png"]];
[pinWheel addSubview:meetthepress];
And another… Now that’s going to be a total pain to do that for 12 images. And I know I don’t want to be a copy paste coder, so let’s break that into a nice method:
- (void)addImageToView:(UIView *)view forImageName:(NSString *)name {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 306, 306);
[button setImage:[UIImage imageNamed:name]
forState:UIControlStateNormal];
[view addSubview:button];
}
And now we can create an array to loop through the image names and create the pinwheel for us.
NSArray *shows = [NSArray arrayWithObjects:@"dateline_rest.png", @"hardball_rest.png", @"meetthepress_rest.png", @"morningjoe_rest.png", @"nightlynews_rest.png", @"rachelmaddow_rest.png", @"shows_disabled0.png", @"shows_disabled1.png", @"shows_disabled2.png", @"shows_disabled3.png", @"shows_disabled4.png", @"todayshow_rest.png", nil];
for (NSString *show in shows) {
[self addImageToView:pinWheel forImageName:show];
}
Responding to Touches
The next step is to respond to when a user touches an individual pinwheel. Which is where we hit a roadblock. iOS handles regularly shaped buttons really well, but irregular buttons poorly. iOS draws the button as a rectangle and to create a non-rectangle button you’d apply a transparency to the image. But the problem is that that transparency still receives touch events as if it weren’t transparent. In our case we layered all images on top of each other, so the result would be that the last image that was added is going to get the touch event regardless of where the user touches, because a user would still be touching the transparency.
Thankfully this is a solved problem. The best implementation I found was from Ole Begemann.
Follow the instructions on GitHub to import the OBShapedButton
library, then update the addButtonToView
method and change the instantiation from UIButton
to OBShapedButton
:
UIButton *button = [OBShapedButton buttonWithType:UIButtonTypeCustom];
That’s a clean code change for some major functionality. Big hat tip to Ole Begemann!
Now that we have a functioning touch event the next step is to have that touch respond like the MSNBC app. Now this app is unique in that it does a double highlight, here’s a quick video depicting that action:
The first step is to disable the highlighted image that UIButton
does automatically. We can do that by setting adjustsImageWhenHighlighted = false;
Then we need to add a handler so that when our button is touched we fire the highlight code:
[button addTarget:self action:@selector(buttonTouched:)
forControlEvents:UIControlEventTouchUpInside];
Now I don’t profess to be an expert in iOS animations so there is likely a better way to do this (post it in the comments if you know of one!), but I found that by using some delayed timers we can create the touch animation quite easily. The buttonTouched
code will call a blink method twice. The first call will execute immediately, and the second call we’ll execute using a delay. That code looks like this:
[self blink:sender];
[self performSelector:@selector(blink:) withObject:sender afterDelay:0.35];
The blink method is simply a show and hide. The show method is executed immediately, and the hide method is executed after a delay:
- (void)blink:(UIButton *)sender {
UIImageView *highlight = [self show:sender];
[self performSelector:@selector(hide:) withObject:highlight
afterDelay:0.20];
}
The rational for returning the highlight image in the show method is so that we can pass that same image to the hide method, allowing us to call removeFromSuperview
in the hide method without more code to find an image reference. The first time I attempted this code, I used a property which did work, however if a second touch occurred before the animation was finished the highlight wouldn’t disappear.
Finally in order to make sure the proper highlight image appears on each pinwheel we’ll use setTag
on the button to associate a button with an array index. We’ll then use this value to index into an array holding highlight image names. This means that the addButtonToView
method has to change to accommodate an index value:
- (void)addButtonToView:(UIView *)view forImageName:(NSString *)name
withIndex:(int)index;
And to use that we have to change to a traditional for loop to set the show pinwheel images:
NSArray *shows = [NSArray arrayWithObjects:@"dateline_rest.png", @"hardball_rest.png", @"meetthepress_rest.png", @"morningjoe_rest.png", @"nightlynews_rest.png", @"rachelmaddow_rest.png", @"todayshow_rest.png", @"shows_disabled0.png", @"shows_disabled1.png", @"shows_disabled2.png", @"shows_disabled3.png", @"shows_disabled4.png", nil];
for (int i = 0; i < [shows count]; i++) { NSString *show = [shows objectAtIndex:i]; [self addButtonToView:pinWheel forImageName:show withIndex:i]; }
Disabling the Disabled Buttons
Within the Shows section there are 5 buttons that are disabled. We can code that up easily by setting userInteractionEnabled = false
. This also means that we need to break up the shows array by those pinwheels that are enabled and disabled. Then we can add another parameter to the addImageToView
method which will set userInteractionEnabled
:
- (void)addButtonToView:(UIView *)view forImageName:(NSString *)name withIndex:(int)index setEnabledTo:(BOOL)enabled {
// button creation & setup code
[button setUserInteractionEnabled:enabled];
[view addSubview:button];
}
And the enabled/disabled arrays are going to look like this:
NSArray *shows = [NSArray arrayWithObjects:@"dateline_rest.png", @"hardball_rest.png", @"meetthepress_rest.png", @"morningjoe_rest.png", @"nightlynews_rest.png", @"rachelmaddow_rest.png", @"todayshow_rest.png", nil];
for (int i = 0; i < [shows count]; i++) {
NSString *show = [shows objectAtIndex:i];
[self addButtonToView:pinWheel forImageName:show
withIndex:i setEnabledTo:true];
}
NSArray *disabledButtons = [NSArray arrayWithObjects:@"shows_disabled0.png", @"shows_disabled1.png", @"shows_disabled2.png", @"shows_disabled3.png", @"shows_disabled4.png", nil];
for (NSString *disabledButton in disabledButtons) {
[self addButtonToView:pinWheel forImageName:show
withIndex:-1 setEnabledTo:false];
}
As we conclude, we end up with the following: a nice background, some pinwheel buttons, and some disabled buttons. As well you've likely picked up some new skills: How to figure out how an app is built, how to use arrays to simplify your code, how to make an animation without using animations, and how to create a non-rectangular button.
As always I've uploaded the sample code to GitHub so that you can play with it on your own. Leave a comment if you've got an app you'd like to see reverse engineered, or if you've got a better way to do the highlighting animation.
Leave a Reply