17

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

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

标准 UIKit 对象已经符合 UIAppearance 协议,但我不想在所有 UILabel 或 UIButtons 上应用样式。我只想将样式应用于我的对象实例中包含的 UILabels 和 UIButtons。此外,我不能(也不想)使用 appearanceWhenContainedIn: 因为使用我的自定义对象的开发人员可能不知道其中“包含”了哪些类型的对象。

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

AFAIK 它必须实施

+ (id)appearance

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

所以,苹果似乎没有给我一个标准的代理对象来定制我自己的,如果我必须从头开始创建。是对的还是我失去了什么?

谢谢

4

4 回答 4

16

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

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

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

这个怎么运作?FLAppearance 为外观ForClass: 方法传递的每个类创建一个自身实例。如果您为同一个类调用两次,则返回相同的实例。

然后,您可以执行以下操作:

[[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];
    }
}
于 2013-03-31T20:36:57.107 回答
4

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

于 2013-08-17T18:16:21.027 回答
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;
}
于 2013-07-15T07:28:25.557 回答
0

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

基本上你只需要标记你的属性,UI_APPEARANCE_SELECTOR只要你的类是一个子类,UIView它就会处理私有_UIAppearance类的实际售卖。


编辑:

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

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

于 2013-03-31T18:35:22.647 回答