2013-03-31 20 views
16

Ho un oggetto personalizzato che eredita da NSObject. Questo oggetto fa "alcune cose", una delle quali sta creando un UIView con alcuni oggetti UIKit (UILabel, UIButtons ecc ecc ...). Questo oggetto ha alcune proprietà come: textColor, font, backgroundColor ... che vengono utilizzate per personalizzare l'aspetto degli oggetti UIKit contenuti.Proxy UIA per gli oggetti personalizzati

Desidero personalizzare queste proprietà "one shot" per tutte le istanze create di questo oggetto e ho esaminato il protocollo UIAppearance.

Gli oggetti UIKit standard sono già conformi al protocollo UIAppearance, ma non voglio applicare lo stile su TUTTI UILabels o UIButtons. Voglio applicare gli stili solo agli UILabels e agli UIButtons contenuti nelle istanze dell'oggetto. Inoltre, non posso (e non voglio) usare apparenzaWhenContainedIn: perché lo sviluppatore che utilizza il mio oggetto personalizzato potrebbe non sapere che tipo di oggetti sono "contenuti" al suo interno.

Quindi, stavo cercando come rendere il mio oggetto personalizzato conforme al protocollo UIAppearance.

AFAIK deve implementare il metodo

+ (id)appearance 

. Questo metodo dovrebbe restituire un oggetto proxy in cui è possibile inviare tutte le personalizzazioni. Ma, guardando il metodo di aspetto degli oggetti UIKit, vedo che viene restituito un oggetto privato. Un oggetto di classe _UIAppearance.

Quindi, sembra che Apple non mi dia un oggetto proxy standard per la personalizzazione del mio, e devo creare se da zero. È giusto o sto perdendo qualcosa?

Grazie

risposta

14

Dopo un po 'di ricerca "mi arrendo" sull'utilizzo di un oggetto Apple standard. Non esiste, per ora. Ho creato il mio proxy, è abbastanza semplice (funziona solo con "aspetto:" ormai).

Spieghiamo. Voglio impostare l'aspetto di "textColor" su una sottoclasse NSObject, chiamiamolo "FLObject". Fare in modo che FLObject sia conforme al protocollo di aspetto UIA e sovrascrivere il metodo dell'aspetto. In questo metodo, si dovrebbe restituire una classe proxy (quello che ho creato):

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

Come funziona? FLAppearance crea una singola istanza di se stessa per ogni classe passata dal metodo appearanceForClass :. Se lo chiami due volte per la stessa classe, viene restituita la stessa istanza.

Poi, si può fare qualcosa di simile:

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

FLAppearance sovrascrive il forwardInvocation: metodo, in modo che accetti tutti i metodi inviati. Quindi, inserisce tutte le invocazioni in una matrice. Quando FLObject viene inizializzato, una semplice chiamata al

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

inizierà a trasmettere invocazioni e impostare l'aspetto. Certo, questo richiede un po 'di ottimizzazione e controllo degli errori, ma penso che sia un buon inizio.

@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

Funziona bene L'ho esteso in modo che possa anche risalire la gerarchia delle classi in modo che possa anche impostare le opzioni della classe genitore: https://gist.github.com/vkodocha/5500276 (il codice è troppo lungo per aggiungere inline). – Chris

+0

bel lavoro! Come miglioramento, potresti usare 'instancetype' invece di' id' come tipo di ritorno, in modo da poter saltare il cast esplicito a 'FLAppearance'. –

+0

@GabrielePetronella grazie per il suggerimento ;-) – LombaX

0

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

Fondamentalmente è solo bisogno di codificare le vostre proprietà con UI_APPEARANCE_SELECTOR e tutto funziona a patto che la classe è una sottoclasse di UIView che si occuperà del vending reale della _UIAppearance classe privata .


Edit:

Siete probabilmente meglio solo posizionare il proprio soluzione con un Singleton e alcuni metodi della classe piuttosto che tentare di fare qualcosa di spaventoso con il runtime. Non sembra che UIAppearance supporti il ​​tuo caso d'uso.

D'altra parte, è possibile attaccare ciascun oggetto venduto in una sottoclasse privata UIView e quindi vender istanze di tale sottoclasse. Quindi è possibile inoltrare i messaggi di stato inviati al numero NSObject alle istanze vendute e utilizzare appearanceWhenContainedIn:<your private subclass>. Ciò potrebbe diventare disordinato e potrebbe essere fonte di confusione per i consumatori della tua classe.

+0

Grazie, so come usarlo nelle sottoclassi di UIView. Come ho specificato, vorrei implementare UIAppearance in una sottoclasse NSObject – LombaX

+0

Vedere le mie modifiche. Questo aiuta affatto? –

+0

Ho scelto di creare il mio proxy compatibile con il protocollo UIAppearance, È più facile di quello che sembrava :-) Ho aggiunto il codice in una risposta. – LombaX

2

Nizza implementazione, ho leggermente modificato il codice e ha creato la classe come una sottoclasse di NSProxy. Usandolo in un progetto ho trovato una perdita di memoria:

Ad esempio: utilizzando il proxy per impostare impostazioni/aspetto globali, ogni istanza di quella classe non raggiungerà mai refCount 0, quindi dealloc non verrà mai chiamato.

codice Leak:

-(void)forwardInvocation:(NSInvocation *)anInvocation; 
{ 
    [...] 

    // !! This will retain also the target 

    [anInvocation retainArguments]; 

    [...] 
} 

Fix:

-(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; 
    } 
} 

categoria Copy per 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

La risposta ha un piccolo errore su startForwarding, dovrebbe essere: NSInvocation * targetInvocation = [invocation copy]; [targetInvocation setTarget: sender]; [invocazione targetInvocation]; targetInvocation = nil; – mientus

4

Ai fini del mio progetto, ho raccogliere tutto insieme e rilasciato UIApperance personalizzato proxy come progetto open source MZApperance

Problemi correlati