私は現在、Volume OSXウィンドウのようなウィンドウを作成しようとしています。
これを行うために、私は自分の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ははるかにスムーズです):
コーナーをスムーズにする方法について何かアイデアはありますか?ありがとう
以下の元の回答で説明したハックは、OS X ElCapitanではもう必要ありません。 NSVisualEffectView
のmaskImage
がNSWindow
に設定されている場合、contentView
のNSVisualEffectView
はそこで正しく機能するはずです(そうである場合は十分ではありません) contentView
のサブビュー)。
サンプルプロジェクトは次のとおりです: https://github.com/marcomasser/OverlayTest
プライベートNSWindowメソッド- (NSImage *)_cornerMask
をオーバーライドすることでこれを行う方法を見つけました。角丸長方形を含むNSBezierPathを描画して作成した画像を返すだけで、OSXのボリュームウィンドウに似た外観になります。
私のテストでは、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
}
}
}
_
そのコードを含むアプリをAppStoreに送信した場合、Appleはどうなるかわかりません。実際には、プライベートAPIを呼び出しているのではなく、発生するメソッドをオーバーライドしているだけです。 AppKitのプライベートメソッドと同じ名前にする必要があります。名前の競合があることをどのように知る必要がありますか?????
その上、これはあなたが何もしなくても優雅に失敗します。 Appleがこれが内部的に機能する方法を変更し、メソッドが呼び出されない場合、ウィンドウは素敵な丸みを帯びた角を取得しませんが、すべてが機能し、見えますほぼ同じ。
この方法について私がどのようにして見つけたかについて知りたい場合は、次のようにします。
OS Xの音量表示が自分のやりたいことをしていることはわかっていたので、狂人のように音量を変更すると、その音量表示を画面に表示するプロセスによってCPU使用率が目立つようになることを期待していました。そのため、CPU使用率でソートされたアクティビティモニターを開き、フィルターをアクティブにして「マイプロセス」のみを表示し、音量を上げたり下げたりしました。
coreaudiod
と_/System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/BezelUI/BezelUIServer
_のBezelUIServer
と呼ばれるものが何かをしたことが明らかになりました。後者のバンドルリソースを見ると、ボリューム表示を描画する責任があることが明らかでした。 (注:このプロセスは、何かが表示された後、短時間しか実行されません。)
次に、Xcodeを使用して、プロセスが起動したらすぐにそのプロセスに接続し([デバッグ]> [プロセスに接続]> [プロセス識別子(PID)または名前で…]、「BezelUIServer」と入力)、ボリュームを再度変更しました。デバッガーが接続された後、ビューデバッガーを使用して、ビュー階層を確認し、ウィンドウがBSUIRoundWindow
というNSWindowサブクラスのインスタンスであることがわかりました。
バイナリで _class-dump
_ を使用すると、このクラスはNSWindowの直接の子孫であり、3つのメソッドのみを実装するのに対し、1つは- (id)_cornerMask
であり、有望に聞こえます。
Xcodeに戻り、オブジェクトインスペクター(右側、3番目のタブ)を使用して、ウィンドウオブジェクトのアドレスを取得しました。そのポインターを使用して、lldbに説明を出力することにより、この__cornerMask
_が実際に何を返すかを確認しました。
_(lldb) po [0x108500110 _cornerMask]
<NSImage 0x608000070300 Size={37, 37} Reps=(
"NSCustomImageRep 0x608000082d50 Size={37, 37} ColorSpace=NSCalibratedRGBColorSpace BPS=0 Pixels=0x0 Alpha=NO"
)>
_
これは、戻り値が実際にはNSImageであることを示しています。これは、__cornerMask
_を実装するために必要な情報です。
その画像を見たい場合は、ファイルに書き込むことができます。
_(lldb) e (BOOL)[[[0x108500110 _cornerMask] TIFFRepresentation] writeToFile:(id)[@"~/Desktop/maskImage.tiff" stringByExpandingTildeInPath] atomically:YES]
_
もう少し深く掘り下げるには、 Hopper Disassembler を使用してBezelUIServer
とAppKit
を逆アセンブルし、疑似コードを生成して__cornerMask
_がどのように実装および使用されるかを確認できます。内部がどのように機能するかをより明確に把握するため。残念ながら、このメカニズムに関するすべてはプライベートAPIです。
CALayer
が登場するずっと前に、この種のことをしたことを覚えています。パスを作成するには、NSBezierPath
を使用します。
NSWindow
をサブクラス化する必要があるとは思わない。ウィンドウに関する重要な点は、ウィンドウをNSBorderlessWindowMask
で初期化し、次の設定を適用することです。
[window setAlphaValue:0.5]; // whatever your desired opacity is
[window setOpaque:NO];
[window setHasShadow:NO];
次に、ウィンドウのcontentView
をカスタムNSView
サブクラスに設定し、次のようにdrawRect:
メソッドをオーバーライドします。
// "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
として構築してレイヤーにパスを割り当てることはできません。 path
?
これらのソリューションはどれも、Mojaveではうまくいきませんでした。しかし、1時間の調査の結果、さまざまなウィンドウデザインを紹介する この驚くべきレポ が見つかりました。解決策の1つは、OPの希望する外観のように見えます。私はそれを試しましたが、アンチエイリアス処理された丸い角でうまく機能し、タイトルバーのアーティファクトは残っていませんでした。作業コードは次のとおりです。
_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 = visualEffect
_ではなくcontentView.addSubview(visualEffect)
に注意してください。これは、それを機能させるための鍵の1つです。
NSVisualEffectViewはエッジをアンチエイリアスしないという制限がありますが、これは、影のないフローティングタイトルのないサイズ変更不可能なウィンドウのこのアプリケーションで機能するはずの厄介な回避策です-下にエッジだけを引き出す子ウィンドウがあります。
私は私のように見えるようにすることができました:
次のようにします。
すべてを保持するコントローラーの場合:
- (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のdrawrectは次のようになります。
- (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];
}
繰り返しますが、これはハックであり、使用するベースカラーに応じてlineWidth/alpha値を試す必要がある場合があります-私の例では、非常に近くまたは明るい背景の下で見ると、境界線が少しわかりますが、私自身の使用では、アンチエイリアスを使用しない場合よりも不快感が少なくなります。
ブレンディングモードは、ボリュームコントロールのようなネイティブのosx yosemiteポップアップと同じではないことに注意してください。これらのポップアップは、より多くのカラーバーン効果を示す、文書化されていない別のウィンドウの背後の外観を使用しているように見えます。
あなたが言及している「滑らかな効果」は「アンチエイリアス」と呼ばれます。私は少しグーグルをしました、そしてあなたはNSVisualEffectViewの角を曲がろうとした最初の人かもしれないと思います。 CALayerに、角を丸める境界半径を設定するように指示しましたが、他のオプションは設定していません。私はこれを試してみます:
layer.shouldRasterize = YES;
layer.edgeAntialiasingMask = kCALayerLeftEdge | kCALayerRightEdge | kCALayerBottomEdge | kCALayerTopEdge;