Подтвердить что ты не робот

Как использовать 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...

4b9b3361

Ответ 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