簡體   English   中英

Swift macOS 彈出窗口檢測更改暗模式

[英]Swift macOS popover detect change dark mode

在此處輸入圖像描述

從圖像中可以看出,我有一個彈出窗口。

我必須確保當屏幕模式發生變化時,暗模式或亮模式,彈出窗口的顏色會發生變化。

顏色取自資產,如下所示:

NSColor(named: "backgroundTheme")?.withAlphaComponent(1)

在此處輸入圖像描述

正如您在 init function 中啟動彈出窗口時從代碼中看到的那樣,我相應地分配了顏色。

我怎樣才能攔截模式的變化?

你能幫我個忙嗎?

應用委托:

import Cocoa
import SwiftUI

@main
class AppDelegate: NSObject, NSApplicationDelegate {
    var popover = NSPopover.init()
    var statusBar: StatusBarController?
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let contentView = ContentView()
        popover.contentSize = NSSize(width: 560, height: 360)
        popover.contentViewController = NSHostingController(rootView: contentView)
        statusBar = StatusBarController.init(popover)
    }

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

狀態欄控制器:

import AppKit
import SwiftUI

extension NSPopover {
    
    private struct Keys {
        static var backgroundViewKey = "backgroundKey"
    }
    
    private var backgroundView: NSView {
        let bgView = objc_getAssociatedObject(self, &Keys.backgroundViewKey) as? NSView
        if let view = bgView {
            return view
        }
        
        let view = NSView()
        objc_setAssociatedObject(self, &Keys.backgroundViewKey, view, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        NotificationCenter.default.addObserver(self, selector: #selector(popoverWillOpen(_:)), name: NSPopover.willShowNotification, object: nil)
        return view
    }
    
    @objc private func popoverWillOpen(_ notification: Notification) {
        if backgroundView.superview == nil {
            if let contentView = contentViewController?.view, let frameView = contentView.superview {
                frameView.wantsLayer = true
                backgroundView.frame = NSInsetRect(frameView.frame, 1, 1)
                backgroundView.autoresizingMask = [.width, .height]
                frameView.addSubview(backgroundView, positioned: .below, relativeTo: contentView)
            }
        }
    }
    
    var backgroundColor: NSColor? {
        get {
            if let bgColor = backgroundView.layer?.backgroundColor {
                return NSColor(cgColor: bgColor)
            }
            return nil
        }
        set {
            backgroundView.wantsLayer = true
            backgroundView.layer?.backgroundColor = newValue?.cgColor
        }
    }
}

class StatusBarController {
    private var popover: NSPopover
    private var statusBar: NSStatusBar
    var statusItem: NSStatusItem

    
    init(_ popover: NSPopover) {
        self.popover = popover
        self.popover.backgroundColor = NSColor(named: "backgroundTheme")?.withAlphaComponent(1)
        statusBar = NSStatusBar.init()
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        
        if let statusBarButton = statusItem.button {
            statusBarButton.image = #imageLiteral(resourceName: "Fork")
            statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
            statusBarButton.image?.isTemplate = true
            statusBarButton.action = #selector(togglePopover(sender:))
            statusBarButton.target = self
            statusBarButton.imagePosition = NSControl.ImagePosition.imageLeft
        }
    }
    
    @objc func togglePopover(sender: AnyObject) {
        if(popover.isShown) {
            hidePopover(sender)
        }else {
            showPopover(sender)
        }
    }
    
    func showPopover(_ sender: AnyObject) {
        if let statusBarButton = statusItem.button {
            popover.show(relativeTo: statusBarButton.bounds, of: statusBarButton, preferredEdge: NSRectEdge.maxY)
        }
    }
    
    func hidePopover(_ sender: AnyObject) {
        popover.performClose(sender)
    }
    
}

您好,我會跳過在彈出窗口上設置顏色,而是在您的 ContentView.swift 中設置背景

然后將背景設置為包裝 UI 的 rest 的 VStack/HStack/ZStack。

var body: some View {
        VStack{
            Text("Hello, world!").padding()
            Button("Ok", action: {}).padding()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color("backgroundTheme").opacity(0.3))
        .padding(.top, -16)
    }

在此處輸入圖像描述 在此處輸入圖像描述

有一些事情要記住:

  • 將現有NSColor轉換為新顏色的.withAlphaComponent(_:)等方法不會返回動態顏色。

  • CGColor不支持動態。 使用.cgColorNSColor轉換為CGColor時,您正在從NSColor的“當前”顏色轉換。

所以你的 hacky 方式並不是你想要的好方法。


根據您所說的,如果我理解正確的話,您想要為彈出窗口的背景添加顏色疊加層,包括箭頭部分。

實際上,您可以在視圖 controller 中執行所有這些操作:

class PopoverViewController: NSViewController {
    /// for color overlay
    lazy var backgroundView: NSBox = {
        // 1. This extend the frame to cover arrow potion.
        let box = NSBox(frame: view.bounds.insetBy(dx: -13, dy: -13))
        box.autoresizingMask = [.width, .height]
        box.boxType = .custom
        box.titlePosition = .noTitle
        box.fillColor = NSColor(named: "backgroundTheme")
        return box
    }()
    
    /// for mounting SwiftUI views
    lazy var contentView: NSView = {
        let view = NSView(frame: view.bounds)
        view.autoresizingMask = [.width, .height]
        return view
    }()
    
    override func loadView() {
        view = NSView()
        
        // 2. This avoid clipping.
        view.wantsLayer = true
        view.layer?.masksToBounds = false

        view.addSubview(backgroundView)
        view.addSubview(contentView)
    }
}

注意12 ,這允許backgroundView繪制超出view的邊界,覆蓋箭頭部分。 backgroundView是一個NSBox object,它接受一個動態的NSColor object 來設置它的背景樣式。

請注意,如果您想更改顏色的不透明度,而不是.withAlphaComponent(_:) ,請更改資產的不透明度,就在 RGB 滑塊的正下方。

在此處輸入圖像描述

contentView在這里作為您的 SwiftUI 視圖的安裝點。 要從NSHostingController掛載內容,您可以執行以下操作:

let popoverViewController = PopoverViewController()
_ = popoverViewController.view // this trigger `loadView()`, you don't need this for auto layout

let hostingController = NSHostingController(rootView: ContentView())
hostingController.view.frame = popoverViewController.contentView.bounds
hostingController.view.autoresizingMask = [.width, .height]

popoverViewController.contentView.addSubview(hostingController.view)
popoverViewController.addChild(hostingController)

這會將hostingControllerview添加為popoverViewControllercontentView的子視圖。

就是這樣。

在此處輸入圖像描述

請注意,我使用autoresizingMask而不是自動布局,並將安裝部分從PopoverViewController中提取出來以簡化我的回答。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM