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.
(Edit: no, I don’t want a UIWebView, I need to customize native controls. I don’t want to achieve pure CSS, just something that looks like CSS and works with the simplicity CSS.)
Say I've got a plist structured like this:
closeButtonStyle = "background:transparent;font:Georgia/14;textColor:#faa"
titleLabelStyle = "background:transparent;font:Helvetica/12;textAlignment:left"
You can easily imagine what kind of attributes I’m stuffing in this.
So far, everything works, I have a UIStyle
class that parses such declarations and stores all found values in its ivars; I also have categories on UIView
, UILabel
, UIButton
, ... which only declare a -(void)setStyle:(UIStyle *)style
method. This method applies style variables only if they're defined.
As I’ve said, everything works.
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.
For the record, here is how I’ve implemented 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? Particularly, I would like your opinion on the scanning part of the code (the while (![scanner isAtEnd])
loop).
As this isn't a markup-language, I'd prefer to use regular expressions on it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With