Tim Ross

Software Engineer - iOS, Ruby/Rails, .NET

Managers vs Controllers

A conversation on Twitter last week got me thinking about how I name my classes, particularly the difference between a Manager and a Controller class.

For me, a Manager is the go-to-guy for a particular aspect of functionality or data. For example, a FileManager handles anything related to accessing the file system. A Manager class contains only class methods, or is implemented as a Singleton, as no separate instances are usually required.

1
[MyFileManager fileExistsAtPath:myPath]; // Class method

or

1
[[MyFileManager sharedManager] fileExistsAtPath:myPath]; // Singleton

A Controller class co-ordinates a process. For example, a PurchaseController controls the series of actions in purchasing a product. A ViewController co-ordinates a series of actions for interacting with a View.

1
2
3
4
MyPurchaseController *purchaseController = [[MyPurchaseController alloc] initWithDelegate:self];
[purchaseController purchaseProduct:productId]; // Begins purchase process
...
[purchaseController cancelPurchase]; // Cancels process

Everyone has their own naming preferences, and yours might be different. It’s not really the names themselves that’s important, but that the naming is consistent throughout the application. Remember, it’s all about communicating intent to yourself and others who use your code.

More Fun With UIAppearance

In my last post I discussed using UIAppearance with UISS to style iOS apps. Today I discovered a few more tricks with UIAppearance.

UIKit only exposes a limited set of elements that can be styled with UIAppearance. But what if we want to style something that’s not exposed? Well, it turns out you can define your own UIAppearance proxies by appending a setter method declaration with UI_APPEARANCE_SELECTOR.

Say, for example, I want to use UIAppearance to set rounded corners on a UIView. I can create a setCornerRadius method on a UIView subclass that’s appended with UI_APPEARANCE_SELECTOR:

1
2
3
4
5
@interface MyView : UIView

- (void)setCornerRadius:(CGFloat)cornerRadius UI_APPEARANCE_SELECTOR;

@end

UIView itself doesn’t have a corderRadius property, so the implementation needs to set the value on the underlying CALayer:

1
2
3
4
5
6
7
@implementation MyView

- (void)setCornerRadius:(CGFloat)cornerRadius {
    self.layer.cornerRadius = cornerRadius;
}

@end

Now I can set the new corderRadius style on all instances of MyView with UIAppearance:

1
[[MyView appearance] setCornerRadius:3];

Or, with UISS I can use JSON to set the style:

1
2
3
4
5
{
  "MyView": {
      "cornerRadius": 3
  }
}

Now, this is pretty neat, but what if I want to expose cornerRadius on every UIView in the application, not just the subclass? Well, it turns out you can create a category on UIView that defines custom UIAppearance methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface UIView (Appearance)

- (void)setCornerRadius:(CGFloat)cornerRadius UI_APPEARANCE_SELECTOR;
- (void)setBorderColor:(UIColor *)borderColor UI_APPEARANCE_SELECTOR;
- (void)setBorderWidth:(CGFloat)borderWidth UI_APPEARANCE_SELECTOR;

@end

@implementation UIView (Appearance)

- (void)setCornerRadius:(CGFloat)cornerRadius {
    self.layer.cornerRadius = cornerRadius;
}

- (void)setBorderColor:(UIColor *)borderColor {
    self.layer.borderColor = borderColor.CGColor;
}

- (void)setBorderWidth:(CGFloat)borderWidth {
    self.layer.borderWidth = borderWidth;
}

@end

So now every UIView in the application can have the cornerRadius, borderColor and borderWidth elements styled:

1
2
3
4
5
6
7
{
  "UIView": {
      "cornerRadius": 3,
      "borderColor": [26, 26, 26],
      "borderWidth": 1
  }
}

Custom UIAppearance methods enable more elements to be exposed for styling. UISS makes this code more readable by allowing styles to be defined using JSON.

Improving Readability of UIAppearance Code With UISS

UIAppearance is a convenient way to define styles for UIKit classes throughout your application. For example, instead of setting the font on every UILabel, you can define it once using a UIAppearance proxy:

1
[[UILabel appearance] setFont:[UIFont fontWithName:@"Avenir-Medium" size:11]];

Of course this is Objective-C, so the UIAppearance code is verbose and can be quite difficult to read. If you work with a designer who might like to tweak these values, the code can appear daunting. Luckily there’s a quick and simple way to make UIAppearance code easier to read (and write!).

Introducing UISS

UISS, developed by Robert Wijas, is an iOS library build on top of UIAppearance that provides a convenient way to define styles using JSON.

Let’s look at a more complex example of styles defined in Objective-C using UIAppearance:

1
2
3
4
5
6
7
[[UIButton appearance] setTitleColor:[UIColor colorWithRed:204.f/255.f green:204.f/255.f blue:204.f/255.f alpha:1] forState:UIControlStateNormal];
[[UIButton appearance] setTitleColor:[UIColor colorWithRed:160.f/255.f green:160.f/255.f blue:160.f/255.f alpha:1] forState:UIControlStateHighlighted];
[[UIButton appearance] setTitleShadowColor:[UIColor colorWithWhite:0 alpha:0.7] forState:UIControlStateNormal];
[[UIButton appearance] setBackgroundImage:[[UIImage imageNamed:@"ButtonGrey"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)] forState:UIControlStateNormal];
[[UIButton appearance] setBackgroundImage:[[UIImage imageNamed:@"ButtonGreyTap"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)] forState:UIControlStateHighlighted];
[[UILabel appearanceWhenContainedIn:[UIButton class], nil] setFont:[UIFont fontWithName:@"Avenir-Medium" size:11]];
[[UILabel appearanceWhenContainedIn:[UIButton class], nil] setShadowOffset:CGSizeMake(0, 1)];

Now here’s the equivalent styles defined in JSON using UISS:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "UIButton": {
      "titleColor:normal": [204, 204, 204],
      "titleColor:highlighted": [160, 160, 160],
      "titleShadowColor:normal": ["black", 0.7],
      "backgroundImage:normal": ["ButtonGrey", 10, 10, 10, 10],
      "backgroundImage:highlighted": ["ButtonGreyTap", 10, 10, 10, 10],
      "UILabel": {
          "font": ["Avenir-Medium", 11],
          "shadowOffset": [0, 1]
      }
  }
}

The UISS code is not only easier to read, but also more pleasurable to write!

UISS includes logging and a console for troubleshooting style problems. It can even generate the equivalent UIAppearance code from the JSON files, which is handy if you want to see exactly what’s going on.

Styles can even be loaded from a remote server to enable live style updates in your app.

I was able to get up and running with UISS quickly and found it greatly improved the readability of my UIAppearance definitions. UISS is available on GitHub or as a Cocoapod.

To Segue or Not to Segue

iOS 5 introduced Storyboards, which allow you to visually define the flow of an app. The transitions between view controllers in a Storyboard are called “Segues”.

Initially, the benefits of using segues seem clear:

  • Spend less time writing boring transition code.
  • Code is cleaner and not littered with transitions.
  • You get a visual representation of how the app fits together.

However, after using segues for a while I’ve found there are a few downsides:

  • The visual layout is restricted. Segues always exit from right of a controller and enter from the left. For any moderately complex app, you can end up with a storyboard that resembles spaghetti.
  • I almost always need to pass data to the controller I’m transitioning to. This requires dropping back to the code to intercept the segue, find the controller, then set its properties.
  • I often find I end up writing quite lengthy and obscure code to intercept the segue.

Here’s a comparison of transitioning using segues versus a traditional approach. In this code I’m handling tapping a detail accessory on a row in a table view, then transitioning to a view controller.

First, I’ll use a segue:

In theory I should just be able to connect a segue from the detail accessory to the next view controller and I’m done! Not quite… I also need to assign some data on the controller I’m transitioning to. So I need to intercept the segue by implementing the prepareForSegue method, then check the segue identifier to ensure I am handling the correct segue:

1
2
3
4
5
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"ContactDetailSegue"]) {
        ContactDetailViewController *controller = segue.destinationViewController;
    }
}

So I have the controller and now I need to assign some data to it. But how do I know which row was tapped? There is nothing in UITableView that stores the selected state for an accessory view. So I’ll need an instance variable to store the row index and set this in the tableView:accessoryButtonTappedForRowWithIndexPath: method:

1
2
3
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
    self.selectedDetailIndexPath = indexPath;
}

Now I can use this to assign data to the controller I am transitioning to:

1
2
3
4
5
6
7
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"ContactDetailSegue"]) {
        ContactDetailViewController *controller = segue.destinationViewController;
        Contact *contact = [self.contacts objectAtIndex:self.selectedDetailIndexPath.row];
        controller.contact = contact;
    }
}

But hang on! The tableView:accessoryButtonTappedForRowWithIndexPath: method gets called after prepareForSegue, which means my selectedDetailIndexPath variable is not yet set. So now I need to disconnect the automatic segue from the detail accessory and instead create a manual segue between the two controllers.

Next, I need to call performSegueWithIdentifier to manually trigger the segue in the tableView:accessoryButtonTappedForRowWithIndexPath: method

1
2
3
4
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
    self.selectedDetailIndexPath = indexPath;
    [self performSegueWithIdentifier:@"ContactDetailSegue" sender:self];
}

Phew, that ended up being quite complicated.

Now, let’s look at the “traditional” way of coding a transition without segues using pushViewController:

1
2
3
4
5
6
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
    ContactDetailViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"ContactDetailViewController"];
    Contact *contact = [self.contacts objectAtIndex:indexPath.row];
    controller.contact = contact;
    [self.navigationController pushViewController:controller animated:YES];
}

That replaces all the previous code! Feels much simpler, doesn’t it? What I like about this is that the transition code is all in one place, not spread across two methods. Sure, there’s no “visual” representation of the transition, but if the code is clean and clear, it should be easy to determine how one controller transitions to the next.

So should you ditch using segues? Not necessarily. They can definitely save time if you are transitioning between static views. But there may be cases where it’s cleaner and simpler to do a manual transition rather than use a segue.

Segues are a step in the right direction, the less code we have to write the better! Hopefully they’ll continue to be improved in future updates of iOS. But with the current implementation, you often have to write quite obscure code to work-around the limitations.