我记得在_cornerMask
出现之前很早就做了这种事情。您使用CALayer
创建路径。
我目前正在尝试制作一个类似于Volume OS X窗口的窗口:
为此,我有自己的NSWindow
(使用自定义子类),它是透明/无标题栏/无阴影的,其contentView中有一个NSVisualEffectView
。这是我的子类的代码,可以使内容视图变圆:
- (void)setContentView:(NSView *)aView {
aView.wantsLayer = YES;
aView.layer.frame = aView.frame;
aView.layer.cornerRadius = 14.0;
aView.layer.masksToBounds = YES;
[super setContentView:aView];
}
这是结果(如您所见,角落是粒状的,OS X更加平滑):关于如何使转角更平滑的任何想法?谢谢
我在下面的原始答案中描述的hack在OS X El Capitan上不再需要。如果将NSVisualEffectView
的maskImage
设置为NSWindow
,则contentView
的NSVisualEffectView
应该可以在此处正常工作(如果它只是contentView
的子视图,则还不够)。
这里是一个示例项目:https://github.com/marcomasser/OverlayTest
我找到了一种方法来通过重写私有NSWindow方法:- (NSImage *)_cornerMask
。只需返回通过绘制带有圆角矩形的NSBezierPath创建的图像,即可获得类似于OS X的体积窗口的外观。
在我的测试中,我发现您需要为NSVisualEffectView 和 NSWindow使用蒙版图像。在代码中,您使用的是视图图层的cornerRadius
属性来获取圆角,但是可以通过使用蒙版图像来实现相同的目的。在我的代码中,我生成了NSVisualEffectView和NSWindow都使用的NSImage:
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
}
然后我创建了一个NSWindow子类,该子类具有用于蒙版图像的设置器:
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
}
}
然后,在我的NSWindowController子类中,为视图和窗口设置蒙版图像:
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
}
}
}
我不知道如果您将带有该代码的应用提交到App Store,Apple会怎么做。您实际上并没有调用任何私有API,只是覆盖了恰好与AppKit中的私有方法同名的方法。您怎么知道存在命名冲突? 😉
此外,您无需执行任何操作即可优雅地失败。如果Apple改变了其内部工作方式,而该方法仅被调用,则您的窗口将无法获得漂亮的圆角,但是一切仍然有效,并且看起来almost相同。
如果您对我对这种方法的了解感到好奇:
我知道OS X音量指示可以完成我想做的事情,我希望像疯子一样更改音量,这会导致在屏幕上显示该音量指示的过程导致CPU占用率显着。因此,我打开了活动监视器,并按CPU使用率进行了排序,激活了过滤器以仅显示“我的进程”,并敲定了我的音量上/下键。
[很明显coreaudiod
和在BezelUIServer
中称为/System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/BezelUI/BezelUIServer
的东西做了什么。通过查看后者的捆绑包资源,很明显,它负责绘制音量指示。 (注意:该过程在显示内容后仅运行一小段时间。)
然后,我使用Xcode在启动后立即将其附加到该进程(调试>附加到进程>按进程标识符(PID)或名称…,然后输入“ BezelUIServer”),然后再次更改音量。附加调试器后,视图调试器使我了解了视图层次结构,并发现该窗口是名为BSUIRoundWindow
的NSWindow子类的实例。
在二进制文件上使用class-dump
表明该类是NSWindow的直接后代,仅实现三种方法,而其中一个是class-dump
,听起来很有希望。
在Xcode中,我使用对象检查器(右侧,第三个选项卡)获取窗口对象的地址。使用该指针,我通过在lldb中打印其描述来检查此- (id)_cornerMask
实际返回的内容:
_cornerMask
这表明返回值实际上是一个NSImage,这是我实现
(lldb) po [0x108500110 _cornerMask] <NSImage 0x608000070300 Size={37, 37} Reps=( "NSCustomImageRep 0x608000082d50 Size={37, 37} ColorSpace=NSCalibratedRGBColorSpace BPS=0 Pixels=0x0 Alpha=NO" )>
所需的信息。如果要查看该图像,可以将其写入文件:
_cornerMask
要更深入一点,您可以使用
(lldb) e (BOOL)[[[0x108500110 _cornerMask] TIFFRepresentation] writeToFile:(id)[@"~/Desktop/maskImage.tiff" stringByExpandingTildeInPath] atomically:YES]
分解Hopper Disassembler和BezelUIServer
并生成伪代码以查看AppKit
的实现方式,并用于更清晰地了解内部原理。不幸的是,与该机制有关的一切都是私有API。
我记得在_cornerMask
出现之前很早就做了这种事情。您使用CALayer
创建路径。
我不相信您实际上需要
NSBezierPath
的子类。关于窗口的重要一点是用NSWindow
初始化窗口并应用以下设置:NSBorderlessWindowMask
然后您将窗口的
[window setAlphaValue:0.5]; // whatever your desired opacity is [window setOpaque:NO]; [window setHasShadow:NO];
设置为自定义contentView
子类,并使用NSView
方法覆盖,类似于以下内容:drawRect:
结果(忽略滑块):
<< img src =“ https://image.soinside.com/eyJ1cmwiOiAiaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS9yS0Q2Ty5wbmcifQ==” alt =“在此处输入图像描述”>
Edit
:如果由于某种原因不希望使用此方法,是否不能简单地将// "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];
分配为CAShapeLayer
的contentView
,然后将上述layer
转换为NSBezierPath
还是仅构造为CGPath
并将路径分配给图层CGPath
? 您指的“平滑效果”称为“抗锯齿”。我做了一些谷歌搜索,我想您可能是第一个尝试绕过NSVisualEffectView的人。您告诉CALayer有一个边界半径,它将使拐角变圆,但是您没有设置任何其他选项。我会尝试的:
path
layer.shouldRasterize = YES;
layer.edgeAntialiasingMask = kCALayerLeftEdge | kCALayerRightEdge | kCALayerBottomEdge | kCALayerTopEdge;
Anti-alias diagonal edges of CALayer
尽管NSVisualEffectView的局限性不是对边缘进行抗锯齿,但是现在这是一个笨拙的解决方法,适用于此浮动无标题,不可调整大小且没有阴影的窗口的应用程序-在子窗口下方仅画出边缘。
我能够使我看起来像这样:
<< img src =“ https://image.soinside.com/eyJ1cmwiOiAiaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS9neTdVei5wbmcifQ==” alt =“在此处输入图像描述”>“ >>
通过执行以下操作:
在拥有所有内容的控制器中:
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CALayer_class/index.html#//apple_ref/occ/instp/CALayer/edgeAntialiasingMaskRoundedHollowView的drawrect看起来像这样:
- (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; }]; }
同样,这是一种技巧,您可能需要根据所使用的基色来使用lineWidth / alpha值-在我的示例中,如果您看上去很近或在较浅的背景下,会稍微划一下边框,但就我自己的使用而言,与没有任何抗锯齿功能相比,它感觉不那么刺耳。
请记住,混合模式将与音量控制之类的osx优胜美地本机弹出窗口不同-那些弹出窗口似乎使用了不同的,未记录的幕后外观,显示出更多的色彩燃烧效果。
对于Marco Masser的最简洁的解决方案,所有的荣誉,有两点有用:
- (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];
}
必须是视图控制器内的根视图。使用深色材料时,仍有有趣的浅色裁切边缘,这些边缘在深色背景上非常明显。为避免这种情况,请使窗口背景透明,NSVisualEffectView
。
window.backgroundColor = NSColor.clearColor()
这些解决方案都无法在Mojave上为我工作。但是,经过一个小时的研究,我发现展示了不同的窗户设计。解决方案之一看起来像是OP所需的外观。我尝试了一下,它的抗锯齿圆角效果很好,没有标题栏伪像。这是工作代码:
this amazing repo
[最后请注意let visualEffect = NSVisualEffectView()
visualEffect.translatesAutoresizingMaskIntoConstraints = false
visualEffect.material = .dark
visualEffect.state = .active
visualEffect.wantsLayer = true
visualEffect.layer?.cornerRadius = 16.0
window?.titleVisibility = .hidden
window?.styleMask.remove(.titled)
window?.backgroundColor = .clear
window?.isMovableByWindowBackground = true
window?.contentView?.addSubview(visualEffect)
,而不是contentView.addSubview(visualEffect)
。这是使其工作的关键之一。
我记得在_cornerMask
出现之前很早就做了这种事情。您使用CALayer
创建路径。
您指的“平滑效果”称为“抗锯齿”。我做了一些谷歌搜索,我想您可能是第一个尝试绕过NSVisualEffectView的人。您告诉CALayer有一个边界半径,它将使拐角变圆,但是您没有设置任何其他选项。我会尝试的:
尽管NSVisualEffectView的局限性不是对边缘进行抗锯齿,但是现在这是一个笨拙的解决方法,适用于此浮动无标题,不可调整大小且没有阴影的窗口的应用程序-在子窗口下方仅画出边缘。
对于Marco Masser的最简洁的解决方案,所有的荣誉,有两点有用:
- (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];
}
必须是视图控制器内的根视图。这些解决方案都无法在Mojave上为我工作。但是,经过一个小时的研究,我发现展示了不同的窗户设计。解决方案之一看起来像是OP所需的外观。我尝试了一下,它的抗锯齿圆角效果很好,没有标题栏伪像。这是工作代码:
this amazing repo