Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS-like styling class for iOS

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).

like image 742
Cyrille Avatar asked Aug 09 '11 10:08

Cyrille


1 Answers

As this isn't a markup-language, I'd prefer to use regular expressions on it.

like image 138
vikingosegundo Avatar answered Nov 15 '22 08:11

vikingosegundo