Обновление для OS X El Capitan
Взлом, который я описал в своем первоначальном ответе ниже, больше не нужен в OS X El Capitan. maskImage
NSVisualEffectView
должно там работать корректно, если contentView
NSWindow
установлено как NSVisualEffectView
(недостаточно, если это подвид contentView
).
Вот пример проекта: https://github.com/marcomasser/OverlayTest.
Оригинальный ответ — актуально только для OS X Yosemite
Я нашел способ сделать это, переопределив частный метод NSWindow: - (NSImage *)_cornerMask
. Просто верните изображение, созданное путем рисования NSBezierPath со скругленным прямоугольником, чтобы получить вид, похожий на окно тома OS X.
При тестировании я обнаружил, что вам нужно использовать изображение маски для NSVisualEffectView и NSWindow. В своем коде вы используете свойство cornerRadius
слоя представления, чтобы получить закругленные углы, но вы можете добиться того же, используя изображение маски. В моем коде я генерирую NSImage, который используется как NSVisualEffectView, так и NSWindow:
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
}
}
}
Я не знаю, что сделает Apple, если вы отправите приложение с этим кодом в App Store. На самом деле вы не вызываете какой-либо частный API, вы просто переопределяете метод, который имеет то же имя, что и частный метод в AppKit. Как узнать о конфликте имен? ????
Кроме того, это изящно терпит неудачу, и вам не нужно ничего делать. Если Apple изменит то, как это работает внутри, и метод просто не будет вызываться, ваше окно не получит красивых закругленных углов, но все по-прежнему будет работать и выглядеть почти так же.
Если вам интересно, как я узнал об этом методе:
Я знал, что индикация громкости OS X делает то, что я хочу, и я надеялся, что изменение громкости как сумасшедшее приведет к заметному использованию ЦП процессом, который выводит эту индикацию громкости на экран. Поэтому я открыл монитор активности, отсортированный по использованию ЦП, активировал фильтр, чтобы показывать только «Мои процессы», и нажал клавиши увеличения/уменьшения громкости.
Стало ясно, что coreaudiod
и что-то под названием BezelUIServer
в /System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/BezelUI/BezelUIServer
что-то сделали. При просмотре ресурсов пакета для последнего стало очевидно, что он отвечает за отрисовку индикации объема. (Примечание: этот процесс выполняется только в течение короткого времени после того, как он что-то отобразит.)
Затем я использовал Xcode для подключения к этому процессу, как только он запустился («Отладка» > «Присоединить к процессу» > «По идентификатору процесса (PID) или имени…», затем введите «BezelUIServer») и снова изменил том. После того, как отладчик был подключен, отладчик представления позволил мне взглянуть на иерархию представлений и увидеть, что окно было экземпляром подкласса NSWindow с именем BSUIRoundWindow
.
Использование class-dump
в двоичном файле показало, что этот класс является прямым потомком NSWindow и реализует только три метода: в то время как один - (id)_cornerMask
, который звучал многообещающе.
Вернувшись в Xcode, я использовал инспектор объектов (правая сторона, третья вкладка), чтобы получить адрес объекта окна. С помощью этого указателя я проверил, что на самом деле возвращает этот _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 Disassembler, чтобы разобрать BezelUIServer
и AppKit
и сгенерировать псевдокод, чтобы увидеть, как _cornerMask
реализован и используется, чтобы получить более четкое представление о том, как работает внутреннее устройство. К сожалению, все, что касается этого механизма, является частным API.
person
Marco Masser
schedule
01.04.2015
+[UIBezierPath bezierPathWithRoundedRect:cornerRadius:]
, используют новую, более мягкую кривую скругления, чем скругление углов слоя. Интересно, верно ли то же самое дляNSBezierPath
в OS X? Вот подробное описание новых возможностей округления, а также код, который вы можете взять и использовать, если обнаружите, что он не является частьюNSBezierPath
: paintcodeapp.com/news/code-for-ios-7-скругленные-прямоугольники - person Zev Eisenberg   schedule 23.10.2014layer.allowsEdgeAntialiasing = YES;
? - person Brad Allred   schedule 06.11.2014