title: CSS-like styling class for iOS
body: <p>I’m currently implementing a CSS-like styling engine for my iOS app’s native controls, in order to avoid reading a whole bunch of styling properties from a plist and applying every single one on each control.</p>
  
  <p>(Edit: no, <strong>I don’t want a UIWebView</strong>, I need to customize native controls. I don’t want to achieve pure CSS, just something that <strong>looks like CSS</strong> and works with the simplicity CSS.)</p>
  
  <p>Say I've got a plist structured like this:</p>
  
  <pre><code>closeButtonStyle = "background:transparent;font:Georgia/14;textColor:#faa"
  titleLabelStyle  = "background:transparent;font:Helvetica/12;textAlignment:left"
  </code></pre>
  
  <p>You can easily imagine what kind of attributes I’m stuffing in this.</p>
  
  <p>So far, everything works, I have a <code>UIStyle</code> class that parses such declarations and stores all found values in its ivars; I also have categories on <code>UIView</code>, <code>UILabel</code>, <code>UIButton</code>, ... which only declare a <code>-(void)setStyle:(UIStyle *)style</code> method. This method applies style variables only if they're defined.</p>
  
  <p>As I’ve said, everything works.</p>
  
  <p>My only question is regarding the parsing of the style string. I’ve chosen to use a NSScanner, but I’m not sure if it’s the best option and would like to have your opinion.</p>
  
  <p>For the record, here is how I’ve implemented my <code>UIStyle</code> :</p>
  
  <p>-- UIStyle.h</p>
  
  <pre><code>typedef struct {
      BOOL frame:1;
      BOOL font:1;
      BOOL textColor:1;
      BOOL backgroundColor:1;
      BOOL shadowColor:1;
      BOOL shadowOffset:1;
      BOOL textAlignment:1;
      BOOL titleEdgeInsets:1;
      BOOL numberOfLines:1;
      BOOL lineBreakMode:1;
  } UIStyleFlags;
  
  @interface UIStyle: NSObject {
      UIStyleFlags         _has;
      CGRect               _frame;
      UIFont              *_font;
      UIColor             *_textColor;
      UIColor             *_backgroundColor;
      UIColor             *_shadowColor;
      CGSize               _shadowOffset;
      UITextAlignment      _textAlignment;
      UIEdgeInsets         _titleEdgeInsets;
      NSInteger            _numberOfLines;
      UILineBreakMode      _lineBreakMode;
  }
  
  @property (readonly, nonatomic) UIStyleFlags         has;
  @property (readonly, nonatomic) CGRect               frame;
  @property (readonly, nonatomic) UIFont              *font;
  @property (readonly, nonatomic) UIColor             *textColor;
  @property (readonly, nonatomic) UIColor             *backgroundColor;
  @property (readonly, nonatomic) UIColor             *shadowColor;
  @property (readonly, nonatomic) CGSize               shadowOffset;
  @property (readonly, nonatomic) UITextAlignment      textAlignment;
  @property (readonly, nonatomic) UIEdgeInsets         titleEdgeInsets;
  @property (readonly, nonatomic) NSInteger            numberOfLines;
  @property (readonly, nonatomic) UILineBreakMode      lineBreakMode;
  
  - (id)initWithString:(NSString *)string;
  + (id)styleWithString:(NSString *)string;
  + (id)styleInDict:(NSDictionary *)dict key:(NSString *)key;
  
  @end
  
  @interface UIView (UIStyle)
  - (void)setStyle:(UIStyle *)style;
  @end
  
  @interface UILabel (UIStyle)
  - (void)setStyle:(UIStyle *)style;
  @end
  
  @interface UIButton (UIStyle)
  - (void)setStyle:(UIStyle *)style;
  @end
  </code></pre>
  
  <p>-- UIStyle.m</p>
  
  <pre><code>#import "UIStyle.h"
  
  @implementation UIStyle
  
  @synthesize has               = _has;
  @synthesize frame             = _frame;
  @synthesize font              = _font;
  @synthesize textColor         = _textColor;
  @synthesize backgroundColor   = _backgroundColor;
  @synthesize shadowColor       = _shadowColor;
  @synthesize shadowOffset      = _shadowOffset;
  @synthesize textAlignment     = _textAlignment;
  @synthesize titleEdgeInsets   = _titleEdgeInsets;
  @synthesize numberOfLines     = _numberOfLines;
  @synthesize lineBreakMode     = _lineBreakMode;
  
  - (id)initWithString:(NSString *)string {
      if ((self = [super init])) {
          _has.frame           = NO;
          _has.font            = NO;
          _has.textColor       = NO;
          _has.backgroundColor = NO;
          _has.shadowColor     = NO;
          _has.shadowOffset    = NO;
          _has.textAlignment   = NO;
          _has.titleEdgeInsets = NO;
          _has.numberOfLines   = NO;
          _has.lineBreakMode   = NO;
  
          _frame           = CGRectZero;
          _font            = nil;
          _textColor       = nil;
          _backgroundColor = nil;
          _shadowColor     = nil;
          _shadowOffset    = CGSizeZero;
          _textAlignment   = UITextAlignmentLeft;
          _titleEdgeInsets = UIEdgeInsetsZero;
          _numberOfLines   = 1;
          _lineBreakMode   = UILineBreakModeClip;
  
          NSScanner *scanner = [[NSScanner alloc] initWithString:string];
          NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
  
          NSCharacterSet *keyEndSet = [NSCharacterSet characterSetWithCharactersInString:@":"];
          NSCharacterSet *valueEndSet = [NSCharacterSet characterSetWithCharactersInString:@";"];
  
          while (![scanner isAtEnd]) {
              NSString *key;
              NSString *value;
  
              [scanner scanUpToCharactersFromSet:keyEndSet intoString:&amp;key];
              [scanner scanCharactersFromSet:keyEndSet intoString:NULL];
              [scanner scanUpToCharactersFromSet:valueEndSet intoString:&amp;value];
              [scanner scanCharactersFromSet:valueEndSet intoString:NULL];
  
              [dict setValue:value forKey:key];
          }
          [scanner release];
  
          for (NSString *key in dict) {
              NSString *value = (NSString *)[dict objectForKey:key];
  
              if ([key isEqualToString:@"frame"]) {
                  _frame = CGRectFromString(value);
                  _has.frame = YES;
              }
  
              else if ([key isEqualToString:@"font"]) {
                  NSArray *font = [value componentsSeparatedByString:@"/"];
                  NSString *fontName = (NSString *)[font objectAtIndex:0];
                  CGFloat fontSize = (CGFloat)[(NSString *)[font objectAtIndex:1] floatValue];
  
                  _font = [[UIFont fontWithName:fontName size:fontSize] retain];
                  _has.font = YES;
              }
  
              else if ([key isEqualToString:@"textColor"]) {
                  _textColor = [[UIColor colorWithString:value] retain];
                  _has.textColor = YES;
              }
  
              else if ([key isEqualToString:@"backgroundColor"]) {
                  _backgroundColor = [[UIColor colorWithString:value] retain];
              }
  
              else if ([key isEqualToString:@"shadow"]) {
                  NSArray *shadow = [value componentsSeparatedByString:@"/"];
                  _shadowColor = [[UIColor colorWithString:(NSString *)[shadow objectAtIndex:0]] retain];
                  _shadowOffset = CGSizeMake((CGFloat)[(NSString *)[shadow objectAtIndex:1] floatValue], (CGFloat)[(NSString *)[shadow objectAtIndex:2] floatValue]);
                  _has.shadowColor = YES;
                  _has.shadowOffset = YES;
              }
  
              else if ([key isEqualToString:@"textAlignment"]) {
                  if ([value isEqualToString:@"center"]) {
                      _textAlignment = UITextAlignmentCenter;
                  }
                  else if ([value isEqualToString:@"right"]) {
                      _textAlignment = UITextAlignmentRight;
                  }
                  else {
                      _textAlignment = UITextAlignmentLeft;
                  }
                  _has.textAlignment = YES;
              }
  
              else if ([key isEqualToString:@"titleEdgeInsets"]) {
                  _titleEdgeInsets = UIEdgeInsetsFromString(value);
                  _has.titleEdgeInsets = YES;
              }
  
              else if ([key isEqualToString:@"numberOfLines"]) {
                  _numberOfLines = (NSInteger)[value integerValue];
                  _has.numberOfLines = YES;
              }
  
              else if ([key isEqualToString:@"lineBreakMode"]) {
                  if ([value isEqualToString:@"character"]) {
                      _lineBreakMode = UILineBreakModeCharacterWrap;
                  }
                  else if ([value isEqualToString:@"clip"]) {
                      _lineBreakMode = UILineBreakModeClip;
                  }
                  else if ([value isEqualToString:@"head"]) {
                      _lineBreakMode = UILineBreakModeHeadTruncation;
                  }
                  else if ([value isEqualToString:@"tail"]) {
                      _lineBreakMode = UILineBreakModeTailTruncation;
                  }
                  else if ([value isEqualToString:@"middle"]) {
                      _lineBreakMode = UILineBreakModeMiddleTruncation;
                  }
                  else {
                      _lineBreakMode = UILineBreakModeWordWrap;
                  }
                  _has.lineBreakMode = YES;
              }
          }
  
          [dict release];
      }
      return self;
  }
  
  - (void)dealloc {
      [_font            release];
      [_textColor       release];
      [_backgroundColor release];
      [_shadowColor     release];
      [super dealloc];
  }
  
  + (id)styleWithString:(NSString *)string {
      return [[[UIStyle alloc] initWithString:string] autorelease];
  }
  
  + (id)styleInDict:(NSDictionary *)dict key:(NSString *)key {
      return [[[UIStyle alloc] initWithString:(NSString *)[dict objectForKey:key]] autorelease];
  }
  
  @end
  
  
  @implementation UIView (UIStyle)
  - (void)setStyle:(UIStyle *)style {
      if (style.has.frame) {
          [self setFrame:style.frame];
      }
  
      if (style.has.backgroundColor) {
          [self setBackgroundColor:style.backgroundColor];
      }
  }
  @end
  
  @implementation UILabel (UIStyle)
  - (void)setStyle:(UIStyle *)style {
      [super setStyle:style];
  
      if (style.has.font)
          [self setFont:style.font];
  
      if (style.has.textColor)
          [self setTextColor:style.textColor];
  
      if (style.has.shadowColor)
          [self setShadowColor:style.shadowColor];
  
      if (style.has.shadowOffset)
          [self setShadowOffset:style.shadowOffset];
  
      if (style.has.textAlignment)
          [self setTextAlignment:style.textAlignment];
  
      if (style.has.numberOfLines)
          [self setNumberOfLines:style.numberOfLines];
  
      if (style.has.lineBreakMode)
          [self setLineBreakMode:style.lineBreakMode];
  }
  @end
  
  @implementation UIButton (UIStyle)
  - (void)setStyle:(UIStyle *)style {
      [super setStyle:style];
  
      if (style.has.titleEdgeInsets)
          [self setTitleEdgeInsets:style.titleEdgeInsets];
  }
  @end
  </code></pre>
  
  <p>Is this the best way to go? Particularly, I would like your opinion on the scanning part of the code (the <code>while (![scanner isAtEnd])</code> loop).</p>