【问题标题】:UIAppearance proxy for custom objects自定义对象的 UIAppearance 代理
【发布时间】:2013-03-31 18:26:09
【问题描述】:

我有一个自定义对象,它继承自 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 类的对象。

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

谢谢

【问题讨论】:

    标签: ios objective-c uiappearance


    【解决方案1】:

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

    【讨论】:

    • 工作得很好我扩展了它,所以它也会沿着类层次结构传播,所以它也可以设置父类的选项:gist.github.com/vkodocha/5500276(代码很长,要添加内联)。
    • 干得好!作为一项改进,您可以使用instancetype 而不是id 作为返回类型,这样您甚至可以跳过对FLAppearance 的显式转换。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      很好的实现,我稍微修改了代码并将该类创建为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;
      }
      

      【讨论】:

      • Answer startForwarding有小错误,应该是:NSInvocation *targetInvocation = [invocation copy]; [targetInvocation setTarget:sender]; [targetInvocation 调用]; targetInvocation = nil;
      【解决方案4】:

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

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


      编辑:

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

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

      【讨论】:

      • 谢谢,我知道如何在 UIView 子类中使用它。正如我指定的,我想在 NSObject 子类中实现 UIAppearance
      • 我选择创建自己的与 UIAppearance 协议兼容的代理,这比看起来要容易:-) 我已经在答案中添加了代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-06
      • 2019-05-02
      • 2012-01-09
      • 2012-12-05
      • 1970-01-01
      相关资源
      最近更新 更多