自定义对象的UIAppearance代理

时间:2013-03-31 18:26:10

标签: ios objective-c uiappearance

我有一个自定义对象,它继承自NSObject。 这个对象做了“一些事情”,其中一个是用一些UIKit对象创建一个UIView(UILabel,UIButtons ecc ecc ...)。 这个对象有一些属性,如:textColor,font,backgroundColor ...,用于自定义包含的UIKit对象的外观。

我想为这个对象的所有创建实例自定义这个属性“一次性”,我看了UIAppearance协议。

标准UIKit对象已经符合UIAppearance协议,但我不想在所有UILabel或UIButtons上应用该样式。我想仅将样式应用于对象实例中包含的UILabel和UIButtons。 此外,我不能(我不想)使用appearanceWhenContainedIn:因为使用我的自定义对象的开发人员可能不知道它内部包含什么类型的对象。

所以,我正在研究如何使我的自定义对象符合UIAppearance协议。

AFAIK必须实施

+ (id)appearance

方法。此方法应返回一个代理对象,您可以在其中发送所有自定义项。 但是,看一下UIKit对象的外观方法,我发现返回了一个私有对象。 类_UIAppearance的对象。

所以,似乎Apple没有给我一个标准的代理对象来自定义我自己,我必须从头开始创建。 这是对的还是我在放松一些东西?

由于

4 个答案:

答案 0 :(得分:15)

经过一些研究,我“放弃”使用标准的Apple对象。它暂时不存在。我已经创建了自己的代理,它非常简单(仅适用于“外观:”)。

让我们解释一下。 我想在NSObject子类上设置“textColor”的外观,我们称之为“FLObject”。 使FLObject符合UIAppearance协议并覆盖外观方法。 在此方法中,您应该返回一个代理类(我创建的代理类):

+ (id)appearance
{
    return [FLAppearance appearanceForClass:[self class]];
}

它是如何工作的? FLAppearance为appearanceForClass:方法传递的每个类创建一个自身的单个实例。 如果对同一个类调用它两次,则返回相同的实例。

然后,你可以这样做:

[[FLObject appearance] setTextColor:[UIColor redColor]]; 

FLAppearance覆盖forwardInvocation:方法,因此它接受发送的所有方法。 然后,它将所有调用放在一个数组中。 初始化FLObject时,只需调用

[(FLAppearance *)[FLAppearance appearanceForClass:[self class]] startForwarding:self];

将开始发送调用并设置外观。 当然,这需要一些调整和错误检查,但我认为这是一个良好的开端。

@interface FLAppearance ()

@property (strong, nonatomic) Class mainClass;
@property (strong, nonatomic) NSMutableArray *invocations;

@end

static NSMutableDictionary *dictionaryOfClasses = nil;

@implementation FLAppearance

// this method return the same object instance for each different class
+ (id) appearanceForClass:(Class)thisClass
{
    // create the dictionary if not exists
    // use a dispatch to avoid problems in case of concurrent calls
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!dictionaryOfClasses)
            dictionaryOfClasses = [[NSMutableDictionary alloc]init];
    });



    if (![dictionaryOfClasses objectForKey:NSStringFromClass(thisClass)])
    {
        id thisAppearance = [[self alloc]initWithClass:thisClass];
        [dictionaryOfClasses setObject:thisAppearance forKey:NSStringFromClass(thisClass)];
        return thisAppearance;
    }
    else
        return [dictionaryOfClasses objectForKey:NSStringFromClass(thisClass)];
}

- (id)initWithClass:(Class)thisClass
{
    self = [self initPrivate];
    if (self) {
        self.mainClass = thisClass;
        self.invocations = [NSMutableArray array];
    }
    return self;
}

- (id)init
{
    [NSException exceptionWithName:@"InvalidOperation" reason:@"Cannot invoke init. Use appearanceForClass: method" userInfo:nil];
    return nil;
}

- (id)initPrivate
{
    if (self = [super init]) {

    }
    return self;
}

-(void)forwardInvocation:(NSInvocation *)anInvocation;
{
    // tell the invocation to retain arguments
    [anInvocation retainArguments];

    // add the invocation to the array
    [self.invocations addObject:anInvocation];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [self.mainClass instanceMethodSignatureForSelector:aSelector];
}

-(void)startForwarding:(id)sender
{
    for (NSInvocation *invocation in self.invocations) {
        [invocation setTarget:sender];
        [invocation invoke];
    }
}

答案 1 :(得分:4)

出于我自己的项目的目的,我将所有内容收集在一起并发布自定义UIApperance代理作为开源项目MZApperance

答案 2 :(得分:2)

很好的实现,我稍微修改了代码并将该类创建为NSProxy的子类。在项目中使用它我发现内存泄漏:

例如:使用代理设置全局设置/外观,该类的每个实例永远不会达到refCount 0,因此永远不会调用dealloc

泄漏代码:

-(void)forwardInvocation:(NSInvocation *)anInvocation;
{
    [...]

    // !! This will retain also the target

    [anInvocation retainArguments];

    [...]
}

修正:

-(void)forwardInvocation:(NSInvocation *)anInvocation
{
     [anInvocation setTarget:nil];
     [anInvocation retainArguments];

     // add the invocation to the array
     [self.invocations addObject:anInvocation];
}

-(void)startForwarding:(id)sender
{
     for (NSInvocation *invocation in self.invocations) {

         // Create a new copy of the stored invocation,
         // otherwise setting the new target, this will never be released
         // because the invocation in the array is still alive after the call

         NSInvocation *targetInvocation = [invocation copy];
         [targetInvocation setTarget:sender];
         [targetInvocation invoke];
         targetInvocation = nil;
     }
}

复制NSInvocation的类别

-(id)copy
{
     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignature]];
     NSUInteger numberOfArguments = [[self methodSignature] numberOfArguments];

     [invocation setTarget:self.target];
     [invocation setSelector:self.selector];

     if (numberOfArguments > 2) {
         for (int i = 0; i < (numberOfArguments - 2); i++) {
             char buffer[sizeof(intmax_t)];
             [self getArgument:(void *)&buffer atIndex:i + 2];
             [invocation setArgument:(void *)&buffer atIndex:i + 2];
         }
     }

     return invocation;
}

答案 3 :(得分:0)

查看http://logicalthought.co/blog/2012/10/8/uiappearance-and-custom-views

基本上,您只需要使用UI_APPEARANCE_SELECTOR标记属性,只要您的类是UIView的子类,它将处理私有_UIAppearance类的实际自动售货,一切正常。


修改

你最好只使用单例和一些类方法来编写自己的解决方案,而不是试图用运行时做一些可怕的事情。它看起来不像UIAppearance支持您的用例。

另一方面,您可以将您销售的每个对象都粘贴在私有UIView子类中,然后再销售该子类的实例。然后,您可以将发送到NSObject的外观消息转发到您销售的实例并使用appearanceWhenContainedIn:<your private subclass>。这可能会变得混乱,并且可能会让您的班级消费者感到困惑。