适用于 iOS 的类似 CSS 的样式类
我目前正在为我的 iOS 应用程序的本机控件实现一个类似 CSS 的样式引擎,以避免从 plist 中读取一大堆样式属性并将每个样式属性应用到每个控件上。
(编辑:不,我不需要 UIWebView,我需要自定义原生控件。我不想实现纯 CSS,只是看起来像 CSS 的东西并使用简单的 CSS。)
假设我有一个如下结构的 plist:
closeButtonStyle = "background:transparent;font:Georgia/14;textColor:#faa"
titleLabelStyle = "background:transparent;font:Helvetica/12;textAlignment:left"
您可以轻松想象我在其中填充了什么样的属性。
到目前为止,一切正常,我有一个 UIStyle 类,它解析此类声明并将所有找到的值存储在其 ivars 中;我还在 UIView
、UILabel
、UIButton
上有类别,...仅声明 -(void)setStyle:(UIStyle *)style
方法。仅当定义了样式变量时,此方法才应用它们。
正如我所说,一切正常。
我唯一的问题是关于样式字符串的解析。我选择使用 NSScanner,但我不确定它是否是最佳选择,想听听您的意见。
作为记录,以下是我实现 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
这是最好的方法吗?特别是,我想了解您对代码扫描部分(while (![scanner isAtEnd])
循环)的意见。
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).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
由于这不是标记语言,我更喜欢使用 正则表达式就可以了。
As this isn't a markup-language, I'd prefer to use regular expressions on it.
你不能使用 UIWebView< /strong> &使用普通CSS?
这不是比重新发明轮子更简单吗?重新发明轮子可能很有趣,但你可能无法做(或支持)CSS 能够做的很多事情。使用这种方法,您可能也无法利用 iOS 功能...您可能只想重新考虑您的方法。
Can't you use UIWebView & use normal CSS?
Wouldn't that be simpler than reinventing the wheel which might be fun but you might not be able to do (or support) lots of stuff which CSS is capable of & with this approach you might not be able to leverage iOS features too... You might want to just rethink your approach.