I am currently implementing a CSS-style styling mechanism for my own iOS app controls, so as not to read a bunch of style properties from plist and apply each of them to each control.
(Edit: no, I do not want UIWebView , I need to configure my own controls. I do not want to get pure CSS, just something that looks like CSS and works with CSS simplicity.)
Say that I have a structure like:
closeButtonStyle = "background:transparent;font:Georgia/14;textColor:#faa" titleLabelStyle = "background:transparent;font:Helvetica/12;textAlignment:left"
You can easily imagine what attributes I impose on this.
While everything works, I have a UIStyle class that parses such declarations and stores all the values ββfound in its ivars; I also have categories on UIView , UILabel , UIButton , ... which only declare a method -(void)setStyle:(UIStyle *)style . This method applies style variables only if they are defined.
As Ive said, everything works.
My only question is about parsing a style string. Ive chosen to use NSScanner, but Im not sure if its the best option and would like your opinion.
For the record, this is how Ive executed my UIStyle :
- UIStyle.h
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
- UIStyle.m
#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:&key]; [scanner scanCharactersFromSet:keyEndSet intoString:NULL]; [scanner scanUpToCharactersFromSet:valueEndSet intoString:&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
Is this the best way to go? In particular, I would like to get your opinion on the code scanning part ( while (![scanner isAtEnd]) ).