Soru NSVisualEffectView ile düzgün, yuvarlak, hacimli OS X pencere nasıl yapılır?


Şu anda, Volume OS X penceresine benzeyen bir pencere oluşturmaya çalışıyorum:
enter image description here


Bunu yapmak için kendime sahibim NSWindow şeffaf / titlebar-less / shadow-less olan (özel bir alt sınıf kullanarak) NSVisualEffectView içeriğinin içinde. İçerik görünümünde yer almam için alt sınıfımın kodu şöyledir:

- (void)setContentView:(NSView *)aView {
   aView.wantsLayer            = YES;
   aView.layer.frame           = aView.frame;
   aView.layer.cornerRadius    = 14.0;
   aView.layer.masksToBounds   = YES;

   [super setContentView:aView];
}


Ve işte sonuç (gördüğünüz gibi, köşeler grenli, OS X'ler daha yumuşaktır):
enter image description here
Köşeleri nasıl daha yumuşak hale getirebileceğine dair bir fikrin var mı? Teşekkürler


21
2017-10-22 22:55


Menşei


İOS'ta, yuvarlanmış köşeler tarafından üretildi +[UIBezierPath bezierPathWithRoundedRect:cornerRadius:] Bir tabakanın köşelerini yuvarlamaktan daha yeni, daha yumuşak bir yuvarlama eğrisi kullanın. Aynısı doğruysa acaba NSBezierPath OS X'de mi? İşte, yeni yuvarlama işlerinde derin bir dalış, artı bir parçası olmadığını fark ederseniz yakalayabileceğiniz ve kullanabileceğiniz kod NSBezierPath: paintcodeapp.com/news/code-for-ios-7-rounded-rectangles - Zev Eisenberg
ne dersin layer.allowsEdgeAntialiasing = YES;? - Brad Allred
@BradAllred layer.allowsEdgeAntialiasing iOS'a özgüdür ve OSX'te bulunmamaktadır. OSX eşdeğeri layer.edgeAntialiasingMask ayarını deniyor, ancak denemeden NSVisualEffectView üzerinde çalışma görünmüyor ve layer.cornerRadius'un çalışmasında da işe yaramıyor. - Vivek Gani
FWIW, şimdiye kadar şanssız bir çok varyasyon denedim. @pedro bu konuda bir radarı isteyebilirsiniz (rdar: // 19589105 burada) - Vivek Gani


Cevaplar:


OS X El Capitan için güncelleme

Asıl cevabımda aşağıda açıkladığım hack, OS X El Capitan'da artık gerekli değildir. NSVisualEffectView‘ler maskImage Orada doğru çalışması gerekir, eğer NSWindow‘ler contentView olarak ayarlanmış NSVisualEffectView (altyazısıysa, bu yeterli değildir. contentView).

Örnek bir proje: https://github.com/marcomasser/OverlayTest


Orijinal Cevap - Sadece OS X Yosemite için İlgili

Özel bir NSWindow yöntemini geçersiz kılarak bunu yapmanın bir yolunu buldum: - (NSImage *)_cornerMask. OS X’in birim penceresine benzer bir görünüm elde etmek için bir NSBezierPath öğesinin yuvarlatılmış bir doğrultu çizerek oluşturduğu resmi döndürmeniz yeterlidir.


Testlerimde NSVisualEffectView için bir maske resmi kullanmanız gerektiğini gördüm ve NSWindow. Kodunuzda, görünümün katmanını kullanıyorsunuz cornerRadius yuvarlak köşeleri almak için özellik, ancak bir maske resmi kullanarak aynı şeyi elde edebilirsiniz. Benim kodumda, hem NSVisualEffectView hem de NSWindow tarafından kullanılan bir NSImage oluşturur:

func maskImage(#cornerRadius: CGFloat) -> NSImage {
    let edgeLength = 2.0 * cornerRadius + 1.0
    let maskImage = NSImage(size: NSSize(width: edgeLength, height: edgeLength), flipped: false) { rect in
        let bezierPath = NSBezierPath(roundedRect: rect, xRadius: cornerRadius, yRadius: cornerRadius)
        NSColor.blackColor().set()
        bezierPath.fill()
        return true
    }
    maskImage.capInsets = NSEdgeInsets(top: cornerRadius, left: cornerRadius, bottom: cornerRadius, right: cornerRadius)
    maskImage.resizingMode = .Stretch
    return maskImage
}

Daha sonra maske görüntüsü için bir ayarlayıcıya sahip bir NSWindow alt sınıfı oluşturdum:

class MaskedWindow : NSWindow {

    /// Just in case Apple decides to make `_cornerMask` public and remove the underscore prefix,
    /// we name the property `cornerMask`.
    @objc dynamic var cornerMask: NSImage?

    /// This private method is called by AppKit and should return a mask image that is used to 
    /// specify which parts of the window are transparent. This works much better than letting 
    /// the window figure it out by itself using the content view's shape because the latter
    /// method makes rounded corners appear jagged while using `_cornerMask` respects any
    /// anti-aliasing in the mask image.
    @objc dynamic func _cornerMask() -> NSImage? {
        return cornerMask
    }

}

Sonra, NSWindowController alt sınıfımda, görünüm ve pencere için maske görüntüsünü kurdum:

class OverlayWindowController : NSWindowController {

    @IBOutlet weak var visualEffectView: NSVisualEffectView!

    override func windowDidLoad() {
        super.windowDidLoad()

        let maskImage = maskImage(cornerRadius: 18.0)
        visualEffectView.maskImage = maskImage
        if let window = window as? MaskedWindow {
            window.cornerMask = maskImage
        }
    }
}

Bu kodu içeren bir uygulamayı App Store'a gönderirseniz Apple'ın ne yapacağını bilmiyorum. Aslında herhangi bir özel API'yi aramıyorsunuz, sadece AppKit'te özel bir yöntemle aynı adı taşıyan bir yöntemi geçersiz kılıyorsunuz. Adlandırma çakışması olduğunu nasıl bilmelisiniz?

Ayrıca, bu bir şey yapmak zorunda kalmadan incelikle başarısız olur. Apple bu şekilde çalışmayı değiştirirse ve yöntem henüz aranmazsa, pencereniz güzel yuvarlak köşeleri elde etmez, ancak her şey hala çalışır ve çalışır neredeyse aynısı.


Bu yöntem hakkında nasıl öğrendiğimi merak ediyorsanız:

OS X birim göstergesinin yapmak istediklerimi gerçekleştirdiğini biliyordum ve ses seviyesini bir deli gibi değiştirmenin, ekranda o hacim göstergesini belirleyen işlem tarafından fark edilir bir CPU kullanımıyla sonuçlandığını umuyordum. Bu nedenle, CPU kullanımı ile sıralı olarak Activity Monitor'ü açtım, filtreyi sadece “My Processes” (Benim İşlemlerim) gösterecek şekilde ayarladı ve ses yukarı / aşağı tuşlarını kırdım.

Belli oldu coreaudiod ve bir şey denir BezelUIServer içinde /System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/BezelUI/BezelUIServer bir şey yaptı. İkincisi için paket kaynaklarına bakmadan, hacim gösteriminin çizilmesinden sorumlu olduğu açıktı. (Not: bu işlem sadece bir şey görüntüledikten sonra kısa bir süre için çalışır.)

Daha sonra, bu işlemin başlatıldığı anda (Debug> İşlemciye Ekle> İşlem Tanımlayıcıya (PID) veya Adına Göre…, ardından “BezelUIServer” yazarak) eklenecek olan Xcode'u kullandım ve sesi yeniden değiştirdim. Hata ayıklayıcı eklendikten sonra, görüntü hata ayıklayıcısı görünüm hiyerarşisine bir bakmamı ve pencerenin adında bir NSWindow alt sınıfının örneği olduğunu görmeme izin verir. BSUIRoundWindow.

kullanma class-dump ikili üzerinde bu sınıfın NSWindow'un doğrudan bir torunu olduğunu ve sadece üç yöntemi uyguladıklarını; - (id)_cornerMask, bu umut verici geliyordu.

Xcode'da, pencere nesnesinin adresini almak için Nesne Denetçisini (sağ taraf, üçüncü sekme) kullandım. Bu işaretçiyi kullanarak ne olduğunu kontrol ettim _cornerMask lldb'deki açıklamasını yazdırarak gerçekten döndürür:

(lldb) po [0x108500110 _cornerMask]
<NSImage 0x608000070300 Size={37, 37} Reps=(
    "NSCustomImageRep 0x608000082d50 Size={37, 37} ColorSpace=NSCalibratedRGBColorSpace BPS=0 Pixels=0x0 Alpha=NO"
)>

Bu, dönüş değerinin aslında uygulamak için gereken bilgiler olan bir NSImage olduğunu gösterir. _cornerMask.

Bu resme bakmak isterseniz, bir dosyaya yazabilirsiniz:

(lldb) e (BOOL)[[[0x108500110 _cornerMask] TIFFRepresentation] writeToFile:(id)[@"~/Desktop/maskImage.tiff" stringByExpandingTildeInPath] atomically:YES]

Biraz daha derine inmek için Hopper Disassembler sökmek BezelUIServer ve AppKit ve nasıl olduğunu görmek için sözde kodu oluşturmak _cornerMask içsellerin nasıl çalıştığına dair daha net bir resim elde etmek için uygulanır ve kullanılır. Ne yazık ki, bu mekanizma ile ilgili her şey özel API'dir.


20
2018-04-01 09:13



Keşfetmek hakkında düşünceli ayrıntılar için teşekkürler. - diimdeep
Harika cevap! Teşekkür ederim. Bu yaklaşımın en iyi grafik sonuçları verdiğini düşünüyorum. Ayrıca - [NSWindow _cornerMask] API'sının çok anlamlı olduğunu düşünüyorum. Umarım gelecekteki bir sürümde kamuya açık bir API olarak görünür. - joerick
NSWindow alt sınıfına geçmek için yukarıdaki Objective-C uygulaması: gist.github.com/joerick/d52f188b926835b1f655 - joerick
Yapmanın yanı sıra yapılması gereken başka bir şey var mı contentView maskeli NSVisualEffectView El Capitan'da uygun davranışı elde etmek için? Benim bir NSPanel Birlikte NSBorderlessWindowMask ve bir clearColor arka plan ve kenarlar hala jaggy çıkıyor. - chockenberry
Micro Snitch'in kodunu yeni güncelledim (obdev.at/microsnitch) dün ve iyi çalıştı. Kullanıyorum NSWindow ile NSBorderlessWindowMaskve de contentView ayarlandı NSVisualEffectView Arayüz Oluşturucu. Ayrıca burada bir örnek proje oluşturdum: github.com/marcomasser/OverlayTest - Marco Masser


Uzun zamandır böyle bir şey yapmayı hatırlıyorum. CALayer etrafındaydı. Kullan NSBezierPath yolu yapmak.

Ben sana gerçekten inanmıyorum gerek alt sınıfa NSWindow. Pencerenin önemli kısmı pencereyi NSBorderlessWindowMask ve aşağıdaki ayarları uygulayın:

[window setAlphaValue:0.5]; // whatever your desired opacity is
[window setOpaque:NO];
[window setHasShadow:NO];

Sonra sen contentView pencerenizin bir kişiye özel NSView alt sınıf drawRect: yöntem buna benzer geçersiz kılındı:

// "erase" the window background
[[NSColor clearColor] set];
NSRectFill(self.frame);

// make a rounded rect and fill it with whatever color you like
NSBezierPath* clipPath = [NSBezierPath bezierPathWithRoundedRect:self.frame xRadius:14.0 yRadius:14.0];
[[NSColor blackColor] set]; // your bg color
[clipPath fill];

sonuç (kaydırıcıyı dikkate almayın):

enter image description here

Düzenle: Bu yöntem istenmeyen herhangi bir nedenden dolayı ise, sadece bir CAShapeLayer seninki contentView'ler layer ya sonra yukarıdaki dönüştürmek NSBezierPath için CGPath ya da sadece CGPath ve katmanlara yolu atayın path?


3
2017-10-25 04:21



Merhaba! Cevabınız için teşekkürler. Sorun şu ki: doğrudan siyah yuvarlak bir görünüm çiziyorsunuz, istediğim şey bir pencereye sahip olmak ve bu pencerenin içinde NSVisualEffectView Bu yuvarlak (sadece OS X'in ses penceresi gibi). "Doldurmak" için bir yolu var mı NSBezierPath onun sınırlarını tüm bu tahıl olmadan yuvarlamak için bu NSVisualEffectView ile? - Pedro Vieira
@PedroVieira Sorunu anlamıyorum NSVisualEffectView sadece bir NSView alt sınıf, hayır? sadece bu görüşün bir alt görünümü olarak yerleştiremezsiniz? sadece taklit et bu görünümü bir "pencere" (Ben henüz test edemedim Yosemite yükseltmedim) - Brad Allred
@PedroVieira kullanarak hakkında ne CAShapeLayer? Düzenlememi gör. - Brad Allred
@BradAllred Bu, NSVisualEffectView kullanmaz. Bir CAShapeLayer uygularken, orijinal soruda yukarıda gösterildiği gibi pürüzlü bir kenar elde edersiniz. - Luke


Sözünü ettiğin "pürüzsüz etki", "Antialiasing" olarak adlandırılır. Biraz googling yaptım ve bir NSVisualEffectView'ın köşelerini yuvarlamaya çalışan ilk kişi siz olabilirsiniz. CALayer'a köşeleri yuvarlayacak bir sınır yarıçapına sahip olduğunu söyledin, ama başka bir seçenek belirlemedin. Bunu deneyeceğim:

layer.shouldRasterize = YES;
layer.edgeAntialiasingMask = kCALayerLeftEdge | kCALayerRightEdge | kCALayerBottomEdge | kCALayerTopEdge;

CALayer'ın kenar yumuşatıcı köşegen kenarları

https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CALayer_class/index.html#//apple_ref/occ/instp/CALayer/edgeAntialiasingMask


2
2017-10-30 14:51



Cevabınız için teşekkür ederim. Söylediklerinizi denedim ve NSVisualEffect'im hala o grenli kenarlara sahip. Belki başka bir mülk ya da başka bir şey eksik. İşte kullanıyorum kod (_roundView benim NSVisualEffectView olduğunu): pastebin.com/utrMTicj - Pedro Vieira
Çok teşekkürler! .shouldRasterize hile yaptı! - rihurla


NSVisualEffectView antialiasing kenarlarının sınırlamalarına rağmen, şu anda, gölge içermeyen, kayan başlıksız daha az gösterilemeyen bir pencerenin bu uygulaması için çalışması gereken bir kludgey geçici çözümü - sadece kenarları çizen altta bir çocuk penceresi var.

Benim bu şekilde görünmesini sağlayabildim:

enter image description here

aşağıdakileri yaparak:

Her şeyi tutan bir kontrolörde:

- (void) create {

NSRect windowRect = NSMakeRect(100.0, 100.0, 200.0, 200.0);
NSRect behindWindowRect = NSMakeRect(99.0, 99.0, 202.0, 202.0);
NSRect behindViewRect = NSMakeRect(0.0, 0.0, 202.0, 202.0);

NSRect viewRect = NSMakeRect(0.0, 0.0, 200.0, 200.0);

window = [FloatingWindow createWindow:windowRect];

behindAntialiasWindow = [FloatingWindow createWindow:behindWindowRect];
roundedHollowView = [[RoundedHollowView alloc] initWithFrame:behindViewRect];

[behindAntialiasWindow setContentView:roundedHollowView];
[window addChildWindow:behindAntialiasWindow ordered:NSWindowBelow];

backingView = [[NSView alloc] initWithFrame:viewRect];

contentView = [[NSVisualEffectView alloc] initWithFrame:viewRect];
[contentView setWantsLayer:NO];
[contentView setState:NSVisualEffectStateActive];
[contentView setAppearance:
 [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
[contentView setMaskImage:[AppDelegate maskImageWithBounds:contentView.bounds]];

[backingView addSubview:contentView];

[window setContentView:backingView];
[window setLevel:NSFloatingWindowLevel];
[window orderFront:self];


}

+ (NSImage *) maskImageWithBounds: (NSRect) bounds
{
return [NSImage imageWithSize:bounds.size flipped:YES drawingHandler:^BOOL(NSRect dstRect) {

    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:bounds xRadius:20.0 yRadius:20.0];

    [path setLineJoinStyle:NSRoundLineJoinStyle];
    [path fill];

    return YES;
}];
}

RoundedHollowView'ın görünüşü şuna benziyor:

- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// "erase" the window background
[[NSColor clearColor] set];
NSRectFill(self.frame);

[[NSColor colorWithDeviceWhite:1.0 alpha:0.7] set];

NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:20.0 yRadius:20.0];
path.lineWidth = 2.0;
[path stroke];

}

Yine, bu bir korsanlıktır ve kullandığınız temel renge bağlı olarak lineWidth / alpha değerleriyle oynamanız gerekebilir. Benim örneğimde, gerçekten yakından bakarsanız veya daha hafif arka planlara sahipseniz, kenarlıktan biraz daha fazlasını yapabilirsiniz. Kendi kullanımım herhangi bir antialiasing olmamasından daha az sarsıcı hissediyor.

Karıştırma modunun, ses kontrolü gibi yerel osx yosemite pop-up'larıyla aynı olmayacağını unutmayın; bunlar, daha fazla renk yanması etkisi gösteren farklı bir belgesiz arka plan görüntüsü kullanırlar.


2
2018-01-31 20:15



orada en iyi çözüm gibi görünüyor. bir deneyecek. paylaşım için teşekkürler - Pedro Vieira


En düzgün çözüm için Marco Masser'a yapılan tüm kudoslar, iki yararlı nokta var:

  1. Düzgün yuvarlatılmış köşeler çalışmak için NSVisualEffectView Görünüm denetleyicideki kök görünümü olmalıdır.
  2. Karanlık malzemeyi kullanırken, koyu arka plan üzerinde çok net görünen, hafif, kırpılmış kenarlar vardır. Bunu önlemek için pencere arka planınızı şeffaf hale getirin, window.backgroundColor = NSColor.clearColor().

    enter image description here


2
2018-01-24 22:08