如何隐藏 Dock 图标

我希望隐藏码头图标和显示一个 NSStatusItem的首选项。 我可以创建 StatusItem,但我不知道如何从 Dock. :-/删除图标

有什么想法吗?

43556 次浏览

我想您正在 Info.plist 中寻找 LSUIElement

LSUIElement (字符串)。如果此密钥设置为“1”,则 Launch Services 将该应用程序作为代理应用程序运行。代理程序不会出现在“停靠”或“强制退出”窗口中。虽然它们通常作为后台应用程序运行,但是如果需要,它们可以到前台来显示用户界面。

参见关于打开/关闭 给你的简短讨论

如果你想让它成为一个用户首选项,那么你就不能使用 UIElement。UIElement 驻留在捆绑中,你不应该编辑应用包中的任何文件,因为这会使 bundle 签名失效。

我找到的最好的解决方案是基于 这篇优秀的文章。我的解决方案是基于丹的评论。简而言之,没有办法做到这一点与可可,但它是可能的,与一个微小的碳代码位。

本文还建议制作一个帮助程序,专门处理停靠点图标。然后主应用程序根据用户的喜好启动并杀死这个应用程序。这种方法给我的印象是比使用 Carbon 代码更健壮,但我还没有尝试过。

要做到这一点,同时遵守苹果的指导方针,不修改应用程序包,并保证 Mac 应用程序商店的应用程序/(狮子应用程序?)将不会被 info.plist 修改破坏他们的签名你可以将 LSUIElement 默认设置为1,然后当应用程序启动时执行以下操作:

ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToForegroundApplication);

显示它的停靠图标,或绕过这如果用户选择不想要的图标。

只有一个副作用,应用程序的菜单不会显示,直到它丢失和重新获得焦点。

资料来源: 制作一个复选框来开关底座图标

就个人而言,我不喜欢设置任何 Info.plist 选项,而是根据用户设置使用 TransformProcessType(&psn, kProcessTransformToForegroundApplication)TransformProcessType(&psn, kProcessTransformToUIElementApplication)

在 Xcode 中,它显示为“ Application is agent (UIElement)”,它是布尔型的。

在 Info.plist 控件中,单击一个空白空间并从菜单中选择“ Add Row” 类型“ Application is agent (UIElement)” 设定为“是”。

为了使其可选,我在代码中添加了以下代码行(感谢 Valexa!)

 // hide/display dock icon
if (![[NSUserDefaults  standardUserDefaults] boolForKey:@"hideDockIcon"]) {
//hide icon on Dock
ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
}

您可以使用所谓的激活策略:

目标 C

// The application is an ordinary app that appears in the Dock and may
// have a user interface.
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];


// The application does not appear in the Dock and does not have a menu
// bar, but it may be activated programmatically or by clicking on one
// of its windows.
[NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory];


// The application does not appear in the Dock and may not create
// windows or be activated.
[NSApp setActivationPolicy: NSApplicationActivationPolicyProhibited];

Swift 4

// The application is an ordinary app that appears in the Dock and may
// have a user interface.
NSApp.setActivationPolicy(.regular)


// The application does not appear in the Dock and does not have a menu
// bar, but it may be activated programmatically or by clicking on one
// of its windows.
NSApp.setActivationPolicy(.accessory)


// The application does not appear in the Dock and may not create
// windows or be activated.
NSApp.setActivationPolicy(.prohibited)

这应该可以隐藏码头图标。

参见

更新为 Swift: (使用两种方式已经在上面介绍过,它们有相同的结果)

public class func toggleDockIcon_Way1(showIcon state: Bool) -> Bool {
// Get transform state.
var transformState: ProcessApplicationTransformState
if state {
transformState = ProcessApplicationTransformState(kProcessTransformToForegroundApplication)
}
else {
transformState = ProcessApplicationTransformState(kProcessTransformToUIElementApplication)
}


// Show / hide dock icon.
var psn = ProcessSerialNumber(highLongOfPSN: 0, lowLongOfPSN: UInt32(kCurrentProcess))
let transformStatus: OSStatus = TransformProcessType(&psn, transformState)
return transformStatus == 0
}


public class func toggleDockIcon_Way2(showIcon state: Bool) -> Bool {
var result: Bool
if state {
result = NSApp.setActivationPolicy(NSApplicationActivationPolicy.Regular)
}
else {
result = NSApp.setActivationPolicy(NSApplicationActivationPolicy.Accessory)
}
return result
}

在尝试了不同的变体之后,我仍然有以下问题:

  1. 应用程序菜单 无法点击后,应用程序图标在码头是 启用(后设置 NSApplication.ActivationPolicy.regular)。你首先需要切换到其他应用程序(比如 Finder.App) ,然后再切换回你的应用程序,使应用程序菜单正常工作。
  2. 应用程序窗口 退后[隐藏]了后,应用程序图标在 Dock 是 残疾人(后设置 NSApplication.ActivationPolicy.accessory)。你需要启动“任务控制”来显示应用程序窗口。

为了解决上述问题,我做了一个扩展:

import AppKit


extension NSApplication {
public enum Dock {
}
}


extension NSApplication.Dock {


public enum MenuBarVisibiityRefreshMenthod: Int {
case viaMenuVisibilityToggle, viaSystemAppActivation
}


public static func refreshMenuBarVisibiity(method: MenuBarVisibiityRefreshMenthod) {
switch method {
case .viaMenuVisibilityToggle:
DispatchQueue.main.async { // Async call not reaaly needed. But intuition tells to leave it.
// See: cocoa - Hiding the dock icon without hiding the menu bar - Stack Overflow: https://stackoverflow.com/questions/23313571/hiding-the-dock-icon-without-hiding-the-menu-bar
NSMenu.setMenuBarVisible(false)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // Without delay windows were not always been brought to front.
NSMenu.setMenuBarVisible(true)
NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps])
}
}
case .viaSystemAppActivation:
DispatchQueue.main.async { // Async call not reaaly needed. But intuition tells to leave it.
if let dockApp = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.dock").first {
dockApp.activate(options: [])
} else if let finderApp = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.finder").first {
finderApp.activate(options: [])
} else {
assertionFailure("Neither Dock.app not Finder.app is found in system.")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // Without delay windows were not always been brought to front.
NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps])
}
}
}
}


public enum AppIconDockVisibilityUpdateMethod: Int {
case carbon, appKit
}


@discardableResult
public static func setAppIconVisibleInDock(_ shouldShow: Bool, method: AppIconDockVisibilityUpdateMethod = .appKit) -> Bool {
switch method {
case .appKit:
return toggleDockIconViaAppKit(shouldShow: shouldShow)
case .carbon:
return toggleDockIconViaCarbon(shouldShow: shouldShow)
}
}


private static func toggleDockIconViaCarbon(shouldShow state: Bool) -> Bool {
// Get transform state.
let transformState: ProcessApplicationTransformState
if state {
transformState = ProcessApplicationTransformState(kProcessTransformToForegroundApplication)
} else {
transformState = ProcessApplicationTransformState(kProcessTransformToUIElementApplication)
}


// Show / hide dock icon.
var psn = ProcessSerialNumber(highLongOfPSN: 0, lowLongOfPSN: UInt32(kCurrentProcess))
let transformStatus: OSStatus = TransformProcessType(&psn, transformState)
return transformStatus == 0
}


private static func toggleDockIconViaAppKit(shouldShow state: Bool) -> Bool {
let newPolicy: NSApplication.ActivationPolicy = state ? .regular : .accessory
let result = NSApplication.shared.setActivationPolicy(newPolicy)
return result
}
}

用法:

前提条件: Info.plist设置 LSUIElement不存在或设置为值 NO

   private func hideDock() {
log.debug("Will hide app from dock.")
let status = NSApplication.Dock.setAppIconVisibleInDock(false)
log.debug("Status is: \(status)")
NSApplication.Dock.refreshMenuBarVisibiity(method: .viaMenuVisibilityToggle)
}


private func showDock() {
log.debug("Will show app in dock.")
let status = NSApplication.Dock.setAppIconVisibleInDock(true)
log.debug("Status is: \(status)")
// The method `viaMenuVisibilityToggle` not working. Menu itens non-clickable
NSApplication.Dock.refreshMenuBarVisibiity(method: .viaSystemAppActivation)
}