自定义对象的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];
}
}
退房http://logicalthought.co/blog/2012/10/8/uiappearance-and-custom-views
基本上你只需要UI_APPEARANCE_SELECTOR
标记您的特性,并且只要你的类是UIView
子类,将处理私人_UIAppearance
类的实际售货一切正常。
编辑:
你可能会更好过只是滚动使用单和自己的解决方案的一些类的方法,而不是试图做一些可怕的与运行。它看起来不像UIAppearance
支持你的用例。
另一方面,您可以将每个物品放在私人UIView
子类中,然后代之以销售该子类的实例。然后,您可以将发送到您的NSObject
的外观消息转发到您销售的实例并使用appearanceWhenContainedIn:<your private subclass>
。这可能会变得混乱,但可能会让你的班级消费者感到困惑。
尼斯实施,我稍微修改了代码,并创建一个类来作为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;
}
答案在startForwarding上有一个小错误,它应该是:NSInvocation * targetInvocation = [invocation copy]; [targetInvocation setTarget:sender]; [targetInvocation invoke]; targetInvocation = nil; – mientus
工作正常我对它进行了扩展,所以它也会向上传递类hirarchy,因此它还可以设置父类的选项:https://gist.github.com/vkodocha/5500276(代码为long以添加内联)。 – Chris
不错的工作!作为一种改进,你可以使用'instancetype'而不是'id'作为返回类型,这样你甚至可以跳过显式转换为'FLAppearance'。 –
@GabrielePetronella谢谢你的提示;-) – LombaX