Тень моего NSWindow обрезается при переключении экранов?

Я хотел иметь NSWindow с размытым фоном, поэтому я создал оболочку для NSVisualEffectView, которая будет использоваться в моем ContentView() с некоторой помощью Как размыть фон в приложении SwiftUI для macOS?. Я также пытался сделать это только с NSWindow, используя https://github.com/lukakerr/NSWindowStyles#:%7E:text=true-,6.%20Vibrant%20background,A,-vibrant.

struct VisualEffectView: NSViewRepresentable
{
    let material: NSVisualEffectView.Material
    let blendingMode: NSVisualEffectView.BlendingMode
    
    func makeNSView(context: Context) -> NSVisualEffectView
    {
        let visualEffectView = NSVisualEffectView()
        visualEffectView.material = material
        visualEffectView.blendingMode = blendingMode
        visualEffectView.state = NSVisualEffectView.State.active
        return visualEffectView
    }

    func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context)
    {
        visualEffectView.material = material
        visualEffectView.blendingMode = blendingMode
    }
}

Это работает и выглядит великолепно, однако, когда я перемещаю окно на другой экран - и делаю паузу с окном между обоими экранами, а затем перемещаю его в следующее окно - оно обрезает часть тени NSWindow.

Вот как это выглядит при перемещении экранов ⤵︎

введите здесь описание изображения

Есть ли способ предотвратить эту теневую отбивку?

Интерфейс: SwiftUI
LifeCycle: Appkit AppDelegate


person tyirvine    schedule 29.05.2021    source источник


Ответы (1)


Догадаться! К счастью, без каких-либо хаков, лол

введите здесь описание изображения

Правила

Чтобы добиться такого вида без неприятных артефактов в вопросе, вам нужно сделать несколько вещей так, как того хочет macOS.

<сильный>1. Не устанавливайте NSWindow.backgroundColor = .clear!
В первую очередь это причина неприятных артефактов, описанных выше! Оставив цвет вашего окна без изменений, вы обеспечите правильную работу окна при смене экранов. NSVisualEffectView захватывает изображение за окном и использует его в качестве фона, поэтому нет необходимости делать что-либо прозрачным.

<сильный>2. Обязательно включите .titled в styleMask окна!
Если этого не сделать, окно будет отображаться без закругленных углов. Если вы попытаетесь добавить закругленные углы (как это сделал я) в представление SwiftUI, у вас все равно будет непрозрачный фон на самом NSWindow. Если вы затем установите цвет фона вашего окна на .clear (как я сделал снова), возникнут проблемы с отсечением теней! Однако это не означает, что строка заголовка будет мешать, она не будет, мы вернемся к этому чуть позже.

<сильный>3. Добавьте свой NSVisualEffectView в свое представление SwiftUI!
Я обнаружил, что это проще, чем добавлять визуальный эффект к NSWindow.contentView в виде подпредставления.

Решение

<сильный>1. Итак, начните с настройки NSWindow и AppDelegate! ⤵︎
Все, что вам нужно сделать, это убедиться, что строка заголовка присутствует, но скрыта.

import Cocoa
import SwiftUI

@main
class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow!

    func applicationDidFinishLaunching(_ aNotification: Notification) {

        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Create the window and set the content view.
        // Note: You can add any styleMasks you want, just don't remove the ones below.
        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 300, height: 200),
            styleMask: [.titled, .fullSizeContentView],
            backing: .buffered, defer: false)

        // Hide the titlebar
        window.titlebarAppearsTransparent = true
        window.titleVisibility = .hidden

        // Hide all Titlebar Controls
        window.standardWindowButton(.miniaturizeButton)?.isHidden = true
        window.standardWindowButton(.closeButton)?.isHidden = true
        window.standardWindowButton(.zoomButton)?.isHidden = true

        // Set the contentView to the SwiftUI ContentView()
        window.contentView = NSHostingView(rootView: contentView)

        // Make sure the window is movable when grabbing it anywhere
        window.isMovableByWindowBackground = true

        // Saves frame position between opening / closing
        window.setFrameAutosaveName("Main Window")

        // Display the window
        window.makeKeyAndOrderFront(nil)
        window.center()
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
}

На этом этапе ваше окно, вероятно, будет выглядеть примерно так (если вы начинаете с пустого проекта). Вы можете увидеть «Привет, мир!» не совсем по центру из-за строки заголовка. ⤵︎

введите здесь описание изображения


<сильный>2. После того, как ваш NSWindow настроен, пришло время сделать ContentView() ⤵︎
Здесь вы просто хотите создать оболочку для NSVisualEffectView и добавить ее в качестве фона. А ТОГДА убедитесь, что вы удалили безопасные области из поля зрения! Это гарантирует, что вы избавитесь от любого пространства, которое занимала строка заголовка в представлении.

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(VisualEffectView(material: .popover, blendingMode: .behindWindow))

            // Very important! (You could technically just ignore the top so you do you)
            .edgesIgnoringSafeArea(.all)
    }
}

/// Takes the image directly behind the window and uses that to create a blurred material. It can technically be added anywhere but most often it's used as a backing material for sidebars and full windows.
struct VisualEffectView: NSViewRepresentable {
    let material: NSVisualEffectView.Material
    let blendingMode: NSVisualEffectView.BlendingMode

    func makeNSView(context: Context) -> NSVisualEffectView {
        let visualEffectView = NSVisualEffectView()
        visualEffectView.material = material
        visualEffectView.blendingMode = blendingMode
        visualEffectView.state = NSVisualEffectView.State.active
        return visualEffectView
    }

    func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context) {
        visualEffectView.material = material
        visualEffectView.blendingMode = blendingMode
    }
}

На этом этапе ваш вид должен выглядеть так, как вы хотите, без каких-либо негативных последствий! Наслаждайтесь ‹3 (Если у вас возникли проблемы с этим решением, оставьте комментарий!)

введите здесь описание изображения

Ресурсы

Спасибо @eonil за этот умный способ сохранить закругленные углы. Не мог бы понять это без этого ответа ⤵︎
https://stackoverflow.com/a/27613308/13142325

Спасибо lukakerr за этот список стилей NSWindow! https://github.com/lukakerr/NSWindowStyles

person tyirvine    schedule 29.05.2021