自定义对象的UIAppearance代理

问题描述:

我有一个自定义对象,它从NSObject继承。 该对象执行“一些事情”,其中一个是创建一个UIView与一些UIKit对象(UILabel,UIButtons ecc ecc ...)。 该对象具有一些属性,如:textColor,font,backgroundColor ...,用于自定义包含的UIKit对象的外观。自定义对象的UIAppearance代理

我想为这个对象的所有创建的实例定制这个属性“一杆”,并且我看了UIAppearance协议。

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

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

据我所知它必须实现

+ (id)appearance 

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

所以,看起来苹果并没有给我一个标准的代理对象来定制我自己的,我必须从头开始创建。 这是对的还是我正在失去什么?

感谢

经过一番研究后,我“放弃了”使用标准的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]; 
    } 
} 
+0

工作正常我对它进行了扩展,所以它也会向上传递类hirarchy,因此它还可以设置父类的选项:https://gist.github.com/vkodocha/5500276(代码为long以添加内联)。 – Chris

+0

不错的工作!作为一种改进,你可以使用'instancetype'而不是'id'作为返回类型,这样你甚至可以跳过显式转换为'FLAppearance'。 –

+0

@GabrielePetronella谢谢你的提示;-) – LombaX

退房http://logicalthought.co/blog/2012/10/8/uiappearance-and-custom-views

基本上你只需要UI_APPEARANCE_SELECTOR标记您的特性,并且只要你的类是UIView子类,将处理私人_UIAppearance类的实际售货一切正常。


编辑:

你可能会更好过只是滚动使用单和自己的解决方案的一些类的方法,而不是试图做一些可怕的与运行。它看起来不像UIAppearance支持你的用例。

另一方面,您可以将每个物品放在私人UIView子类中,然后代之以销售该子类的实例。然后,您可以将发送到您的NSObject的外观消息转发到您销售的实例并使用appearanceWhenContainedIn:<your private subclass>。这可能会变得混乱,但可能会让你的班级消费者感到困惑。

+0

谢谢,我知道如何在UIView子类中使用它。正如我指定的,我想在NSObject子类中实现UIAppearance – LombaX

+0

请参阅我的编辑。这些帮助有用? –

+0

我选择创建与UIAppearance协议兼容的自己的代理,它比看起来更容易:-)我已将代码添加到答案中。 – LombaX

尼斯实施,我稍微修改了代码,并创建一个类来作为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; 
} 
+1

答案在startForwarding上有一个小错误,它应该是:NSInvocation * targetInvocation = [invocation copy]; [targetInvocation setTarget:sender]; [targetInvocation invoke]; targetInvocation = nil; – mientus

对于我自己的项目而言,我收集一切融合在一起并发布定制UIApperance作为开源项目的代理MZApperance