Swift macOS 弹出窗口检测更改暗模式

2023-11-30

enter image description here

从图像中可以看出,我有一个弹出窗口。

我必须确保当屏幕模式发生变化(深色模式或浅色模式)时,弹出窗口的颜色会发生变化。

颜色取自资源,如下所示:

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

enter image description here

正如您从代码中看到的,在 init 函数中启动弹出窗口时,我相应地分配了颜色。

如何拦截模式的变化?

你能帮我个忙吗?

应用程序委托:

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)
    }
    
}

有一些事情需要记住:

  • 方法如.withAlphaComponent(_:)改变现有的NSColor更改为新颜色不会返回动态颜色。

  • CGColor不具有动态能力。当转换一个NSColor to a CGColor using .cgColor,您正在从 a 的“当前”颜色进行转换NSColor.

所以你的hacky方式并不是真正实现你想要的东西的好方法。


根据你所说的,如果我理解正确的话,你想在弹出窗口的背景上添加颜色叠加,包括箭头部分。

您实际上可以在视图控制器中完成所有这些操作:

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 and 2,这允许backgroundView超越view的边界,覆盖箭头部分。backgroundView is a NSBox接受动态的对象NSColor对象来设置其背景的样式。

请注意,如果您想更改颜色的不透明度,而不是.withAlphaComponent(_:),更改资源的不透明度,位于 RGB 滑块的正下方。

enter image description here

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's view作为子视图popoverViewController's contentView.

就是这样。

enter image description here

请注意我使用autoresizingMask而不是自动布局并将安装部件从PopoverViewController简单地回答我的问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Swift macOS 弹出窗口检测更改暗模式 的相关文章

随机推荐

  • 清除 ASP.NET 中的文本框历史记录

    如何清除文本框的历史记录 我已设置 autocomplet off 并将 AutoCompleteType 设置为禁用 当我在 Chrome Firefox 中双击文本框时 会出现历史记录 文本 因此当我单击它时 文本将被插入到文本框中 有
  • 何时使用 OpenCL API 标量数据类型?

    我一直无法理解何时使用 OpenCL API 数据类型 如 cl float cl uchar 等 可以在此处找到 http www khronos org registry cl sdk 1 0 docs man xhtml scalar
  • Java:如何使用 switch 语句[重复]

    这个问题在这里已经有答案了 可能的重复 如何在 Java 中比较字符串 我无法理解如何使用 Java switch 语句 在执行其中一个 case 语句中的方法后 它仍然会转到默认语句并运行该语句 这是代码 Scanner scanner
  • Sympy:求解具有初始条件误差的微分方程

    SinceSympy版本 1 2 PythonSympy已经实现了在给定一些初始条件的情况下求解简单微分方程中的常数的能力 我试图测试此功能 但不断出现我不知道如何解决的错误 The 文档表示初始条件的格式如下 我尝试遵循实现该功能的实际拉
  • Java 和 SSH:维护连接

    目标 通过一个连接和多个会话或一个持久会话远程控制 ssh 服务器 问题 1 我目前使用 sshj 通过 SSH 进行一些远程控制 它运行良好 但我似乎无法让它正确处理提示 主机不提供真正的root权限 只提供sudo i 所以我需要先登录
  • 在android上模糊图像

    我想要模糊图像 我用过 public Bitmap mohu Bitmap bmpOriginal int hRadius int vRadius int width height r g b c a gry c1 a1 r1 g1 b1
  • 如何在 -(void)touchesMoved 期间仅调用一次方法?

    我在用 void touchesMoved每当我做某事enter特定的框架 在本例中是按钮的区域 我的问题是 我只希望它在我进入框架时做一些事情 not当我在框架内移动手指时 有谁知道我如何在框架内时仅调用一次方法 并且如果我在同一个 to
  • Delphi 使用可变参数列表调用 JNI 方法

    使用 Embarcadero 的 Jni api 单元 如何向需要它的 JNI 方法提供变量参数列表 例如 JNINativeInterface 的 CallStaticObjectMethodV 方法 清单 1 有一个 va list 类
  • 通过修改插件调用JEdtiable提交按钮

    解决方案 1 不使用 TinyMCE 如果您没有将 TinyMCE 与 JEditable 一起使用 请查看下面 Arman P 的帖子 解决方案 2 使用 TinyMCE 如果您使用 TinyMCE 那么不幸的是 Arman P s 方法
  • 获取自定义适配器中的项目列表

    那么 我有一个扩展 ArrayAdapter 的自定义适配器 我想从适配器内获取整个列表 可以这样做吗 我知道在 ArrayAdapter 构造函数中 您传入对象列表并将其存储为 mObjects 但该列表是私有的 我想在构造函数中的自定义
  • Python 2.7 中具有多个函数参数的多重处理

    我正在尝试实现多处理来加速复制循环 但无法让它在 Python27 中工作 这是我的程序的一个非常简化的版本 基于 SO 的文档和其他答案 例如用于多个参数的 Python multiprocessing pool map 我意识到关于多处
  • 使用 JQuery 获取图像的真实尺寸

    给定图像路径列表 如何迭代它们并找到实际的图像尺寸 我假设我必须将它们插入没有宽度或高度属性的 DOM 中 并对它们执行 width 和 height var paths path image png somewhere page jpg
  • 内存映射文件 java NIO

    我了解如何创建内存映射文件 但我的问题是在下面一行中说 FileChannel roChannel new RandomAccessFile file r getChannel ByteBuffer roBuf roChannel map
  • include_next 预处理器指令导致 MSVC 中出现问题

    我知道 inlcude next 恰好是 C 预处理器的 GNU 扩展 我在使用 MSVC 编译 Gnu 库代码时遇到问题 例如 有一个 stdio h 头文件或者更确切地说GNU like
  • Java / NetBeans - 如何进入 jar 文件?

    我在 Netbeans 中有一个 Java 项目 其属性的库部分列出了几个 jar 文件 我在其中一个 jar 文件中发现了一个错误 我想在调试时介入它 这是我到目前为止所做的 我已经找到了源代码 jar 文件 我将该源代码作为一个项目打开
  • 本地运行SAM时如何连接RDS实例?

    我正在使用 SAM 无服务器应用程序模型 在本地测试连接到云中的 Aurora RDS 实例的 Lambda 函数 使用以下命令 sam local invoke lambda function name event event json
  • 如何使用webp图片并支持safari

    由于压缩效果更好 我尝试在整个网站中使用 webp 图像 不过我知道 safari 不支持 webp 图像是使用加载的background image url img img webp 然后我应用其他background特性 我明白了
  • 在c中使用逗号运算符

    我读过逗号运算符用于分配表达式 并将正确的表达式提供给左值 但是为什么这个程序在不使用括号的情况下将左表达式分配给左值呢 我正在使用 Turbo C 编译器 int b 2 int a a b 2 b 5 prints 10 as expe
  • lua代码反混淆

    我最近下载了一些 lua 代码 我发现里面有一些混淆的字符串 这让我想知道那里使用了什么样的混淆技术 有人有想法吗 local code 27 76 117 97 81 0 1 4 4 4 8 0 64 0 0 0 64 67 58 92
  • Swift macOS 弹出窗口检测更改暗模式

    从图像中可以看出 我有一个弹出窗口 我必须确保当屏幕模式发生变化 深色模式或浅色模式 时 弹出窗口的颜色会发生变化 颜色取自资源 如下所示 NSColor named backgroundTheme withAlphaComponent 1