![](/img/trans.png)
[英]How to detect switch between macOS default & dark mode using Swift 3
[英]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
不支持動態。 使用.cgColor
將NSColor
轉換為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)
}
}
注意1
和2
,這允許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)
這會將hostingController
的view
添加為popoverViewController
的contentView
的子視圖。
就是這樣。
請注意,我使用autoresizingMask
而不是自動布局,並將安裝部分從PopoverViewController
中提取出來以簡化我的回答。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.