UIAppearance proxy for custom objects

前端 未结 4 559
小鲜肉
小鲜肉 2020-12-24 14:48

I have a custom object, it inherits from NSObject. This object does \"some things\", one of it is creating an UIView with some UIKit objects (UILabel, UIButtons ecc ecc...)

4条回答
  •  悲哀的现实
    2020-12-24 15:02

    After some reserach I "give up" about using a standard Apple object. It doesn't exists, for now. I've created my own proxy, it's quite simple (works only with "appearance:" by now).

    Let's explain it. I want to set the appearance of "textColor" on a NSObject subclass, let's call it "FLObject". Make FLObject conforms to UIAppearance protocol and override the appearance method. In this method, you should return a proxy class (the one I created):

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

    How it works? FLAppearance creates a single instance of itself for each class passed by the appearanceForClass: method. If you call it two times for the same class, the same instance is returned.

    Then, you can do something like this:

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

    FLAppearance overrides the forwardInvocation: method, so it accepts all methods sent. Then, it puts all invocations in an array. When FLObject is initialized, a simple call to

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

    will start to send invocations and set the appearance. Sure, this needs some tuning and error checking, but I think it's a good start.

    @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];
        }
    }
    

提交回复
热议问题