OS X El Capitan のアップデート
以下の元の回答で説明したハックは、OS X El Capitan では不要になりました。NSVisualEffectView
がに設定されている場合maskImage
、 はそこで正しく機能するはずです(のサブビューである場合は十分ではありません)。NSWindow
contentView
NSVisualEffectView
contentView
サンプル プロジェクトは次のとおりです: https://github.com/marcomasser/OverlayTest
元の回答 – OS X Yosemite にのみ関連
プライベート 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 が内部の動作方法を変更し、メソッドが呼び出されない場合、ウィンドウの角が丸くなりませんが、すべてが機能し、ほとんど同じように見えます。
私がこの方法をどのように知ったかに興味がある場合は、次のようにします。
OS X の音量表示が自分のやりたいことをやってくれることはわかっていたので、狂ったように音量を変更すると、その音量表示を画面に表示するプロセスによって CPU 使用率が顕著になることを願っていました。そのため、アクティビティ モニターを開き、CPU 使用率で並べ替え、フィルターをアクティブにして「マイ プロセス」のみを表示し、ボリュームの上下キーを叩きました。
coreaudiod
呼び出さBezelUIServer
れた何かが何かをしたことが明らかになりまし/System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/BezelUI/BezelUIServer
た。後者のバンドル リソースを見ると、ボリューム表示の描画を担当していることは明らかでした。(注: このプロセスは、何かが表示された後、短時間だけ実行されます。)
次に、Xcode を使用して、そのプロセスが起動したらすぐにそのプロセスにアタッチし ([デバッグ] > [プロセスにアタッチ] > [プロセス識別子 (PID) または名前で…] を選択し、「BezelUIServer」と入力)、ボリュームを再度変更しました。デバッガーをアタッチした後、ビュー デバッガーを使用してビュー階層を調べ、ウィンドウが NSWindow サブクラスのインスタンスであることがわかりましたBSUIRoundWindow
。
バイナリで使用class-dump
すると、このクラスは NSWindow の直接の子孫であり、3 つのメソッドのみを実装していることが示されましたが、そのうちの 1 つは- (id)_cornerMask
有望に思えました。
Xcode に戻り、オブジェクト インスペクター (右側、3 番目のタブ) を使用してウィンドウ オブジェクトのアドレスを取得しました。そのポインターを使用して_cornerMask
、lldb にその説明を出力して、これが実際に何を返すかを確認しました。
(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 逆アセンブラーを使用して逆アセンブルしBezelUIServer
、AppKit
疑似コードを生成して、 がどのように_cornerMask
実装され、使用されているかを確認し、内部がどのように機能するかをより明確に理解することができます。残念ながら、このメカニズムに関するすべてがプライベート API です。