草庐IT

objective-c - 适用于 iOS 的类 CSS 样式类

coder 2023-09-22 原文

我目前正在为我的 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 中;我在 UIViewUILabelUIButton 上也有类别,...它们只声明了一个 -(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]) 循环)发表意见。

最佳答案

因为这不是标记语言,所以我更愿意使用 regular expressions在上面。

关于objective-c - 适用于 iOS 的类 CSS 样式类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6994999/

有关objective-c - 适用于 iOS 的类 CSS 样式类的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  3. ruby - capybara field.has_css?匹配器 - 2

    我在MiniTest::Spec和Capybara中使用以下规范:find_field('Email').must_have_css('[autofocus]')检查名为“电子邮件”的字段是否具有autofocus属性。doc说如下:has_css?(path,options={})ChecksifagivenCSSselectorisonthepageorcurrentnode.据我了解,字段“Email”是一个节点,因此调用must_have_css绝对有效!我做错了什么? 最佳答案 通过JonasNicklas得到了答案:No

  4. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

  5. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  6. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  7. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  8. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  9. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  10. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

随机推荐