2014-09-18 16 views
13

Il prossimo OSX 10.10 ("Yosemite") offre un nuovo tipo di visualizzazione, NSVisualEffectView, che supporta la traslucenza attraverso la finestra o all'interno della finestra. Sono principalmente interessato alla traslucenza attraverso la finestra, quindi mi concentrerò su questo in questa domanda, ma si applica anche alla traslucenza all'interno della finestra.Come utilizzare NSVisualEffectView retrocompatibile con OSX <10.10?

L'utilizzo della traslucenza attraverso la finestra in 10.10 è banale. Devi solo inserire un NSVisualEffectView da qualche parte nella tua gerarchia di visualizzazione e impostarlo su blendingMode su NSVisualEffectBlendingModeBehindWindow. Questo è tutto ciò che serve.

Sotto 10.10 è possibile definire NSVisualEffectView s in IB, impostare la proprietà della modalità di fusione e si è in esecuzione.

Tuttavia, se si desidera essere retrocompatibili con versioni OSX precedenti, non è possibile farlo. Se si tenta di includere un NSVisualEffectView nel proprio XIB, si verificherà un arresto anomalo non appena si tenta di caricare XIB.

Voglio una soluzione "imposta e dimentica" che offrirà traslucenza quando viene eseguito in 10,10 e semplicemente si riduce a una vista opaca quando si esegue su versioni precedenti del sistema operativo.

Quello che ho fatto finora è di rendere la vista in questione un normale NSView in XIB, e quindi aggiungere il codice (chiamato da awakeFromNib) che controlla per [NSVisualEffectView class] != nil, e quando è definita la classe, creo un istanza di NSVisualEffectView, sposta tutte le sottoview della mia vista corrente nella nuova vista e la installa in posizione. Funziona, ma è un codice personalizzato che devo scrivere ogni volta che voglio una vista traslucida.

Sto pensando che questo potrebbe essere possibile utilizzando un oggetto NSProxy. Ecco cosa sto pensando:

Definire una sottoclasse personalizzata di NSView (chiamiamola MyTranslucentView). In tutti i metodi init (initWithFrame e initWithCoder) butterei via l'oggetto appena creato e invece creo una sottoclasse di NSProxy che ha una variabile di istanza privata (myActualView). All'inizio, deciderebbe di creare l'oggetto myActualView come NSVisualEffectView se OS> = 10.10 e un normale NSView in OS < 10.10.

Il proxy inoltrerà i messaggi ALL a myActualView.

Questa sarebbe una buona quantità di codice pignolo e di basso livello, ma penso che dovrebbe funzionare.

Qualcuno ha fatto qualcosa del genere? In tal caso, puoi indicarmi la direzione giusta o darmi qualche suggerimento?

Apple è MOLTO più aperto con il contratto Beta con Yosemite a rispetto ai precedenti Betas. Non penso di violare la mia Beta NDA parlando di questo in termini generali, ma il codice effettivo utilizzando NSVisualEffectView probabilmente dovrebbe essere condiviso in NDA ...

+0

Un'altra soluzione potrebbe essere quella di utilizzare una categoria su 'NSView', qualcosa come '- (id) initVisualEffectView' che produce l'effetto, restituendo un' NSView' o un 'NSVisualEffectView' poiché immagino che erediti da' NSView'. O se stai usando pennini, forse '- (void) applyVisualEffectView' (a self). – Larme

+0

Che cosa farebbe il metodo category per le viste basate su pennini? Immagino che potrebbe creare una nuova vista che è l'intera dimensione della vista, spostare tutti i contenuti della vista su quella vista e renderla l'unica sottoview della vista. Potrebbe funzionare ... –

+0

@Larme, avrei dovuto taggarti nel mio commento di risposta in modo da ricevere una notifica ... –

risposta

11

c'è una soluzione molto semplice, ma un po 'hacky: basta creare dinamicamente una classe denominata NSVisualEffectView quando la vostra applicazione si avvia. Quindi puoi caricare i pennini che contengono la classe, con un comodo fallback su OS X 10.9 e versioni precedenti.

Ecco un estratto della mia app delegato per illustrare l'idea:

AppDelegate.m

#import "AppDelegate.h" 
#import <objc/runtime.h> 

@implementation PGEApplicationDelegate 
-(void)applicationWillFinishLaunching:(NSNotification *)notification { 
    if (![NSVisualEffectView class]) { 
     Class NSVisualEffectViewClass = objc_allocateClassPair([NSView class], "NSVisualEffectView", 0); 
     objc_registerClassPair(NSVisualEffectViewClass); 
    } 
} 
@end 

Devi compilare questo contro il OS X 10.10 SDK.

Come funziona?

Quando l'app è in esecuzione su 10.9 e precedenti, [NSVisualEffectView class] sarà NULL. In tal caso, le seguenti due righe creano una sottoclasse di NSView senza metodi e senza ivars, con il nome NSVisualEffectView.

Così quando AppKit ora annulla un NSVisualEffectView da un file di pennino, utilizzerà la classe appena creata. Quella sottoclasse si comporterà in modo identico a un NSView.

Ma perché non tutto va in fiamme?

Quando la vista non è archiviata dal file pennino, utilizza NSKeyedArchiver. La cosa bella è che semplicemente ignora le chiavi aggiuntive che corrispondono alle proprietà/ivars di NSVisualEffectView.

C'è altro che devo fare attenzione?

  1. Prima di accedere a qualsiasi proprietà di NSVisualEffectView nel codice (ad esempio material), assicurarsi che la classe risponde al selettore ([view respondsToSelector:@selector(setMaterial:)])
  2. [[NSVisualEffectView alloc] initWithFrame:] lavoro continuerà a non perché il nome della classe viene risolto al momento della compilazione. Utilizzare [[NSClassFromString(@"NSVisualEffectView") alloc] initWithFrame:] oppure allocare uno NSView se [NSVisualEffectView class] è NULL.
+0

Fresco. Mi piace questo approccio. (votato) Quello che ho finito col fare nel progetto del mio cliente era di creare una sottoclasse personalizzata di UIView. Lo uso invece di una vista di effetti visivi. In fase di runtime, la vista verifica se NSVisualEffectView è definito. Se è così, ne crea uno, con i suoi limiti come frame, e si sposta su tutte le sue sottoview in questa nuova vista figlia, quindi installa NSVisualEffectView come se fosse una (solo) sottoview. Mi piace molto il tuo approccio, tuttavia, dato che il mio approccio modifica la gerarchia delle viste in fase di runtime, aggiungendo una sottoview extra. –

+3

La tua soluzione è abbastanza intelligente, ma ha un altro svantaggio, che non è stato ancora menzionato. Quando si imposta NSVisualEffectView in un XIB, che dispone dell'opzione "Builds for" (situata nella prima scheda del pannello Inspector) impostata su target di deployment inferiore a 10.10, si otterrà un errore di generazione che indica "NSVisualEffectView non disponibile prima di OS X 10.10" . –

3

Io uso questa categoria solo al mio livello superiore vista.

Se la vista NSVisualEffects è disponibile, quindi inserisce una vista vivacità sul retro e tutto funziona.

L'unica cosa a cui prestare attenzione è che si dispone di una sottoview in più, quindi se si stanno cambiando le visualizzazioni in seguito, è necessario tenerne conto.

@implementation NSView (HS) 

-(instancetype)insertVibrancyViewBlendingMode:(NSVisualEffectBlendingMode)mode 
{ 
    Class vibrantClass=NSClassFromString(@"NSVisualEffectView"); 
    if (vibrantClass) 
    { 
     NSVisualEffectView *vibrant=[[vibrantClass alloc] initWithFrame:self.bounds]; 
     [vibrant setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; 
     [vibrant setBlendingMode:mode]; 
     [self addSubview:vibrant positioned:NSWindowBelow relativeTo:nil]; 

     return vibrant; 
    } 

    return nil; 
} 

@end 
+2

Non sono sicuro che sia una buona idea, perché tutti i pulsanti e le etichette finiscono * sopra * il 'NSVisualEffectView', invece che * dentro *. Ciò si traduce in alcuni problemi di visualizzazione sottile. –

+0

puoi elaborare (o pubblicare uno screenshot?).La mia aspettativa è che i pulsanti non saranno traslucidi e che le etichette lo faranno (che è quello che vedo qui). Non ho una chiara idea di cosa sia 'corretto' però ... –

+0

Ad esempio, i campi di testo regolano il colore dell'etichetta in base all'aspetto della vista dell'effetto visivo. Oppure le immagini dei template sono rese vibranti. Ma la vista deve essere una sottoview di NSVisualEffectView. Ulteriori dettagli nella sessione del WWDC 2014 220 "Adozione di funzionalità avanzate della nuova interfaccia utente di OS X Yosemite" (Trascrizione: http://asciiwwdc.com/2014/sessions/220) –

0

Finii con una variazione di @Confused Vorlon di, ma spostando il punto di vista del bambino alla vista effetto visivo, in questo modo:

@implementation NSView (Vibrancy) 


- (instancetype) insertVibrancyView 
{ 
    Class vibrantClass = NSClassFromString(@"NSVisualEffectView"); 
    if(vibrantClass) { 
     NSVisualEffectView* vibrant = [[vibrantClass alloc] initWithFrame:self.bounds]; 
     [vibrant setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 

     NSArray* mySubviews = [self.subviews copy]; 
     for(NSView* aView in mySubviews) { 
      [aView removeFromSuperview]; 
      [vibrant addSubview:aView]; 
     } 

     [self addSubview:vibrant]; 

     return vibrant; 
    } 

    return nil; 
} 

@end