Как использовать NSVisualEffectView с обратной совместимостью с OSX <10.10?
Предстоящая OSX 10.10 ( "Yosemite" ) предлагает новый тип представления NSVisualEffectView, который поддерживает сквозную прозрачность или внутри окна. Меня в основном интересует прозрачность прозрачного окна, поэтому я собираюсь сосредоточиться на этом в этом вопросе, но это относится и к полупрозрачности внутри окна.
Использование прозрачной прозрачности в 10.10 тривиально. Вы просто разместите NSVisualEffectView
где-нибудь в своей иерархии представлений и установите ее blendingMode
на NSVisualEffectBlendingModeBehindWindow
. Это все, что нужно.
В разделе 10.10 вы можете определить NSVisualEffectView
в IB, установить свой режим режима смешивания, и вы выключены и запущены.
Однако, если вы хотите быть обратно совместимым с более ранними версиями OSX, вы не можете этого сделать. Если вы попытаетесь включить NSVisualEffectView
в свой XIB, вы потерпите крах, как только вы попытаетесь загрузить XIB.
Я хочу, чтобы оно "установило его и забудет", которое предложит прозрачность при запуске в 10.10 и просто ухудшится до непрозрачного представления при запуске в более ранних версиях ОС.
То, что я сделал до сих пор, заключается в том, чтобы сделать рассматриваемое представление обычным NSView в XIB, а затем добавить код (вызванный awakeFromNib), который проверяет на [NSVisualEffectView class] != nil
, и когда он определяется классом, я создаю экземпляр NSVisualEffectView, переместите все мои текущие представления в представлении на новое представление и установите его на место. Это работает, но это настраиваемый код, который я должен писать каждый раз, когда хочу полупрозрачный вид.
Я думаю, что это возможно, используя объект NSProxy. Вот что я думаю:
Определите пользовательский подкласс NSView (позвоните ему в MyTranslucentView). Во всех методах init (initWithFrame и initWithCoder) я удалял вновь созданный объект и вместо этого создавал подкласс NSProxy с частной переменной экземпляра (myActualView). Во время init он решил создать объект myActualView как NSVisualEffectView, если OS >= 10.10 и обычный NSView под OS < 10.10.
Прокси-сервер пересылал бы ВСЕ сообщения myActualView.
Это будет довольно суетливый, низкоуровневый код, но я думаю, что он должен работать.
Кто-нибудь сделал что-то подобное? Если да, можете ли вы указать мне в правильном направлении или дать мне какие-либо указатели?
Apple является MUCH более открытой с бета-соглашением с Yosemite a, чем с предыдущими бета-версиями. Я не думаю, что нарушаю свою бета-версию NDA, говоря об этом в общих чертах, но фактический код с использованием NSVisualEffectView
, вероятно, должен быть совместно использован в NDA...
Ответы
Ответ 1
Существует действительно простое, но несколько хакерское решение: просто динамически создайте класс с именем NSVisualEffectView
, когда начнется ваше приложение. Затем вы можете загружать nibs, содержащие класс, с изящным отступлением от OS X 10.9 и более ранних версий.
Здесь выдержка моего делегата приложения, чтобы проиллюстрировать идею:
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
Вы должны скомпилировать это с OS X 10.10 SDK.
Как это работает?
Когда ваше приложение работает с 10.9 и ранее, [NSVisualEffectView class]
будет NULL. В этом случае следующие две строки создают подкласс NSView
без методов и без ivars с именем NSVisualEffectView
.
Итак, когда AppKit теперь отбрасывает NSVisualEffectView
из файла nib, он будет использовать ваш вновь созданный класс. Этот подкласс будет вести себя одинаково с NSView.
Но почему все не плачет?
Когда представление распаковано из файла nib, оно использует NSKeyedArchiver
. Самое приятное в том, что он просто игнорирует дополнительные ключи, соответствующие свойствам /ivars NSVisualEffectView
.
Что-нибудь еще, о чем мне нужно быть осторожным?
- Перед доступом к любым свойствам
NSVisualEffectView
в коде (например, material
) убедитесь, что класс отвечает на селектор ([view respondsToSelector:@selector(setMaterial:)]
)
-
[[NSVisualEffectView alloc] initWithFrame:]
все еще не работает, потому что имя класса разрешено во время компиляции. Либо используйте [[NSClassFromString(@"NSVisualEffectView") alloc] initWithFrame:]
, либо просто выделите NSView
, если [NSVisualEffectView class]
равно NULL.
Ответ 2
Я просто использую эту категорию в своем представлении верхнего уровня.
Если доступно представление NSVisualEffects, он вставляет вид оживления сзади и все просто работает.
Единственное, на что нужно обратить внимание, это то, что у вас есть дополнительное подзапрос, поэтому, если вы меняете взгляды позже, вам придется принять это во внимание.
@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
Ответ 3
Я закончил с вариацией @Confused Vorlon's, но переместил дочерние представления в визуальный эффект, например:
@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