SwiftUI macOS Menu Bar app copy and paste into text field - swift

*EDIT: So I figured it out. It might not be the swiftUI way about doing it but it works for my app. I found this post that answered most my questions. I created a new file used the mainMenu view template and that was it. *
I'm creating a menu bar app with a text field that I want users to be able to paste their clipboards into. I think the issue is because it doesn't have a menu since copy and paste works on a black project in a text field in a normal window. I have read a few other post about using a swift package called hotkey but I don't think that will work since I want it to use the systems clipboards not a custom one to the app.
Can this be done in a menu bar only app?
Thank you for the help as I learn.
Here is the test code I'm using.
ContentView
import SwiftUI
struct ContentView: View {
#State var jobName = String()
var body: some View {
Text("Testing copy and paste")
.padding()
TextField(/*#START_MENU_TOKEN#*/"Placeholder"/*#END_MENU_TOKEN#*/, text: $jobName)
Button("print text") {
print(jobName)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
AppDelegate
import Cocoa
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
var popover: NSPopover!
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the popover
let popover = NSPopover()
popover.contentSize = NSSize(width: 400, height: 500)
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
self.popover = popover
// Create the status item
self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
if let button = self.statusBarItem.button {
button.image = NSImage(named: "Icon")
button.action = #selector(togglePopover(_:))
}
}
#objc func togglePopover(_ sender: AnyObject?) {
if let button = self.statusBarItem.button {
if self.popover.isShown {
self.popover.performClose(sender)
} else {
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
Main
import Foundation
import Cocoa
// 1
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
// 2
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

So I figured it out. It might not be the swiftUI way about doing it but it works for my app. I found this post that answered most my questions. I created a new file used the mainMenu view template and that was it.

Related

How can I update my Window title for macOS Storyboard Cocoa project via an action?

This is my project:
import Cocoa
import SwiftUI
var appName: String = "My App Name"
class ViewController: NSViewController {
override func viewWillAppear() {
let controller = NSHostingController(rootView: ContentView())
self.view = controller.view
self.view.window?.title = appName
}
}
struct ContentView: View {
var body: some View {
VStack {
Button("Change") {
appName += " updated!"
print(appName)
}
}
.frame(width: 400.0, height: 300.0)
}
}
My goal is be able to update my window title, I am be able to update the variable that holds my app name but since viewWillAppear function would not be triggered I am unable to update my window title there. I was thinking to use a notification there but in this case I am not sure if it is the right feet there, because it would need to post and receive notification, what is the approach for solving this problem?
You can introduce a ViewModel storing title as #Published and from the Button action update that property rather than a global appName. In your hosting controller, you can subscribe to changes of title, since it is #Published and update the window's title whenever the view model's title property is updated.
import Cocoa
import Combine
import SwiftUI
var appName: String = "My App Name"
final class ViewModel: ObservableObject {
#Published var title: String = appName
}
class ViewController: NSViewController {
private let viewModel = ViewModel()
private var titleObservation: AnyCancellable?
override func viewDidLoad() {
super.viewDidLoad()
// Observe the title of the view model and update the window's title whenever it changes
titleObservation = viewModel.$title.sink { [weak self] newTitle in
self?.view.window?.title = newTitle
}
}
override func viewWillAppear() {
let controller = NSHostingController(rootView: ContentView(viewModel: viewModel))
self.view = controller.view
self.view.window?.title = viewModel.title
}
}
struct ContentView: View {
#ObservedObject private var viewModel: ViewModel
init(viewModel: ViewModel) {
self.viewModel = viewModel
}
var body: some View {
VStack {
Button("Change") {
viewModel.title += " updated!"
}
}
.frame(width: 400.0, height: 300.0)
}
}
Alternatively, you can simply inject a closure for the Button action into your view from the hosting controller and update the window's title from the closure.
class ViewController: NSViewController {
override func viewWillAppear() {
let contentView = ContentView(
onButtonTap: { // this closure will be executed every time Button is tapped
appName += " updated!"
self.view.window?.title = appName
print(appName)
}
)
let controller = NSHostingController(rootView: contentView)
self.view = controller.view
self.view.window?.title = appName
}
}
struct ContentView: View {
// Closure to execute when `Button` is tapped
private let onButtonTap: () -> Void
init(onButtonTap: #escaping () -> Void) {
self.onButtonTap = onButtonTap
}
var body: some View {
VStack {
Button("Change", action: onButtonTap)
}
.frame(width: 400.0, height: 300.0)
}
}

How can I disable HideTabBar and ShowAllTabs in macOS in SwiftUI? [duplicate]

I have created a very simple app on MacOS created with SwiftUI. Default there is a menu item show tab bar. How do I remove this? I doesn't make sense to have tabs in this app.
I've found the following answering the same question, but for older versions of Swift, not for SwiftUI: How do I disable the Show Tab Bar menu option in Sierra apps?
The equivalent in SwiftUI is the same thing as the equivalent in Swift (which is missed in that post for some reason). To completely remove these items from any windows for your application, in your app delegate you can set the boolean value allowsAutomaticWindowTabbing to false
func applicationWillFinishLaunching(_ notification: Notification) {
NSWindow.allowsAutomaticWindowTabbing = false
}
Thanks to #JuJoDi
In case you are using SwiftUI life cycle (Scene), you could do the following:
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
}
}
}
}
I was looking for an answer for this as well and found out the following:
by default - as you already mentioned - the Show/Hide Tab is active:
There is a property on NSWindow called tabbingMode which allows us to take control by setting it to .disallowed. My problem though was: in a SwiftUI 2-lifecycle app, how can I get hold of the windows of the app?
Well, there's NSApplication.shared.windows, so my first (non working!!) attempt was to modify all the windows in my #main-App struct (as I already prevented new windows from being created, that should be suffice):
import SwiftUI
#main
struct DQ_SyslogApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
let _ = NSApplication.shared.windows.map { $0.tabbingMode = .disallowed }
}
}
.commands {
CommandGroup(replacing: .newItem) {} //remove "New Item"-menu entry
}
}
}
Unfortunately this did not work as NSApplication.shared.windows is empty in .onAppear.
My next step involved introducing an AppDelegate to my SwiftUI 2-lifecycle that implements applicationDidFinishLaunching(_:)...
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
print("Info from `applicationDidFinishLaunching(_:): Finished launchingā€¦")
let _ = NSApplication.shared.windows.map { $0.tabbingMode = .disallowed }
}
}
...and introducing this AppDelegate to the app:
import SwiftUI
#main
struct DQ_SyslogApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
CommandGroup(replacing: .newItem) {} //remove "New Item"-menu entry
}
}
}
This did the trick for me:
While I would prefer to have the menu entries removed entirely, this at least prevents the user from having tabs which is what the OP was asking for.
If anybody should happen to know a solution to hide those entries, please let us know. I couldn't find a CommandGroupPlacement that represents these menus...
I found how to remove the 'Edit' menu item after reading this article
https://steipete.com/posts/top-level-menu-visibility-in-swiftui/
func applicationDidFinishLaunching(_ notification: Notification) {
NSWindow.allowsAutomaticWindowTabbing = false
if let mainMenu = NSApp .mainMenu {
DispatchQueue.main.async {
if let edit = mainMenu.items.first(where: { $0.title == "Edit"}) {
mainMenu.removeItem(edit);
}
}
}
}
2021, swiftUI 2 and higher
Result in release build:
look at places with comments "HERE":
#main
struct MyFirstApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
init() {
// HERE
disallowTabbingMode()
}
#SceneBuilder
var body: some Scene {
WindowGroup {
AppMainView()
}
.commands {
//MenuLine_MyFirstApp_CustomAbout()
// HERE
MenuLine_File_NewWindow_Disable()
//MenuLine_Help_SupportEmail()
}
}
}
And method + bonuses for understanding menus in SwiftUI
// Set Custom About window instead of default
fileprivate func MenuLine_MyFirstApp_CustomAbout() -> CommandGroup<Button<Text>> {
CommandGroup(replacing: CommandGroupPlacement.appInfo) {
Button("About") { appDelegate.showAboutWnd() }
}
}
//Add some menu button into Help menu
fileprivate func MenuLine_Help_SupportEmail() -> CommandGroup<Button<Text>> {
CommandGroup(after: CommandGroupPlacement.help) {
Button("Support Email") { NSWorkspace.shared.open("mailto:support#myfirstapp.com") }
}
}
// Disable "File -> New window" menu item (make it absent in release build)
fileprivate func MenuLine_File_NewWindow_Disable() -> CommandGroup<EmptyView> {
CommandGroup(replacing: .newItem) { }
}
fileprivate func disallowTabbingMode() {
NSWindow.allowsAutomaticWindowTabbing = false
}

1 duplicate symbol for architecture arm64

I am having this problem when building my macOS app and I don't know what's wrong.
duplicate symbol '_main' in:
/Users/gab/Library/Developer/Xcode/DerivedData/Flash-geftxscpjlmxndgzdhpuizdnidam/Build/Intermediates.noindex/Flash.build/Debug/Flash.build/Objects-normal/arm64/AppDelegate.o
/Users/gab/Library/Developer/Xcode/DerivedData/Flash-geftxscpjlmxndgzdhpuizdnidam/Build/Intermediates.noindex/Flash.build/Debug/Flash.build/Objects-normal/arm64/FlashApp.o
ld: 1 duplicate symbol for architecture arm64
I am trying to build a menu bar app for Big Sur.
I triple-checked the files AppDelegate.swift and FlashApp.swift and I can't see the problem.
AppDelegate.swift
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var popover = NSPopover.init()
var statusBar: StatusBarController?
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ContentView()
popover.contentViewController = MainViewController()
popover.contentSize = NSSize(width: 360, height: 360)
popover.contentViewController?.view = NSHostingView(rootView: contentView)
statusBar = StatusBarController.init(popover)
}
func applicationWillTerminate(_ aNotification: Notification) {
}
}
FlashApp.swift
import SwiftUI
#main
struct FlashApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Can anyone help me?

Create a windowless SwiftUI macOS application?

I'm creating a menu bar app for macOS using SwiftUI.I have figured out how to create a menu bar item, and how to hide the icon from the Dock. But there is one thing left I need to figure out to have a proper menu bar app, and that is how to not show a window on screen (see screenshot).
When using SwiftUI for the #main App class, it looks like I have to return a WindowGroup with some content. I've tried EmptyView() etc. but it has to be "some view that conforms to Scene".
What I have
Here is the code I have so far.
import SwiftUI
import Combine
class AppViewModel: ObservableObject {
#Published var showPopover = false
}
#main struct WeeksApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup { Text("I don't want to show this window. šŸ„²") }
.onChange(of: scenePhase) { phase in
switch phase {
case .background:
print("Background")
case .active:
print("Active")
case .inactive:
print("Inactive")
#unknown default:
fatalError()
}
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var popover = NSPopover.init()
var statusBarItem: NSStatusItem?
var viewModel = AppViewModel()
var cancellable: AnyCancellable?
override init() {
super.init()
cancellable = viewModel.objectWillChange.sink { [weak self] in
if (self?.viewModel.showPopover == false) {
self?.closePopover(self)
}
}
}
func applicationDidFinishLaunching(_ notification: Notification) {
popover.behavior = .transient
popover.animates = false
popover.contentViewController = NSViewController()
popover.contentViewController?.view = NSHostingView(rootView: ContentView(viewModel: viewModel))
statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusBarItem?.button?.title = "Week \(getCurrentWeekNumber())"
statusBarItem?.button?.action = #selector(AppDelegate.togglePopover(_:))
}
#objc func showPopover(_ sender: AnyObject?) {
if let button = statusBarItem?.button {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
#objc func closePopover(_ sender: AnyObject?) {
popover.performClose(sender)
}
#objc func togglePopover(_ sender: AnyObject?) {
if popover.isShown {
closePopover(sender)
} else {
showPopover(sender)
}
}
func getCurrentWeekNumber() -> Int {
let calendar = Calendar.current
let weekOfYear = calendar.component(.weekOfYear, from: Date())
return weekOfYear
}
}
So how can I launch the app without showing a window? I would prefer a SwiftUI solution. I know it can be done the "old" way.
I'm not sure if it's a hack and if there is a better way. I wrote an app as an agent, with the possibility to show a window later in the process. However, at startup, the app should not show a window.
I made an application delegate with the following code:
class Appdelegate: NSObject, NSApplicationDelegate {
#Environment(\.openURL) var openURL
var statusItem: NSStatusItem!
func applicationDidFinishLaunching(_ notification: Notification) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.button!.image = NSImage(named: "AppIcon")
statusItem.isVisible = true
statusItem.menu = NSMenu()
addConfigrationMenuItem()
BundleLocator().checkExistingLaunchAgent()
if let window = NSApplication.shared.windows.first {
window.close()
}
}
}
Notice at the end I close the window, which let my app start in as a menubar app only. However, I do have a content view that can be opened later. The app therefore looks like this:
#main
struct RestartAtDateTryoutApp: App {
#NSApplicationDelegateAdaptor(Appdelegate.self) var appDelegate
var scheduler = Scheduler()
var body: some Scene {
WindowGroup {
ContentView()
.handlesExternalEvents(preferring: Set(arrayLiteral: "ContentView"), allowing: Set(arrayLiteral: "*"))
.environmentObject(scheduler)
.frame(width: 650, height: 450)
}
.handlesExternalEvents(matching: Set(arrayLiteral: "ContentView"))
}
}
This gives me the menubar app with items of which one is a window with a user interface.
I hope this also works in your app.

How do I disable the Show Tab Bar menu option in SwiftUI

I have created a very simple app on MacOS created with SwiftUI. Default there is a menu item show tab bar. How do I remove this? I doesn't make sense to have tabs in this app.
I've found the following answering the same question, but for older versions of Swift, not for SwiftUI: How do I disable the Show Tab Bar menu option in Sierra apps?
The equivalent in SwiftUI is the same thing as the equivalent in Swift (which is missed in that post for some reason). To completely remove these items from any windows for your application, in your app delegate you can set the boolean value allowsAutomaticWindowTabbing to false
func applicationWillFinishLaunching(_ notification: Notification) {
NSWindow.allowsAutomaticWindowTabbing = false
}
Thanks to #JuJoDi
In case you are using SwiftUI life cycle (Scene), you could do the following:
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
}
}
}
}
I was looking for an answer for this as well and found out the following:
by default - as you already mentioned - the Show/Hide Tab is active:
There is a property on NSWindow called tabbingMode which allows us to take control by setting it to .disallowed. My problem though was: in a SwiftUI 2-lifecycle app, how can I get hold of the windows of the app?
Well, there's NSApplication.shared.windows, so my first (non working!!) attempt was to modify all the windows in my #main-App struct (as I already prevented new windows from being created, that should be suffice):
import SwiftUI
#main
struct DQ_SyslogApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
let _ = NSApplication.shared.windows.map { $0.tabbingMode = .disallowed }
}
}
.commands {
CommandGroup(replacing: .newItem) {} //remove "New Item"-menu entry
}
}
}
Unfortunately this did not work as NSApplication.shared.windows is empty in .onAppear.
My next step involved introducing an AppDelegate to my SwiftUI 2-lifecycle that implements applicationDidFinishLaunching(_:)...
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
print("Info from `applicationDidFinishLaunching(_:): Finished launchingā€¦")
let _ = NSApplication.shared.windows.map { $0.tabbingMode = .disallowed }
}
}
...and introducing this AppDelegate to the app:
import SwiftUI
#main
struct DQ_SyslogApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
CommandGroup(replacing: .newItem) {} //remove "New Item"-menu entry
}
}
}
This did the trick for me:
While I would prefer to have the menu entries removed entirely, this at least prevents the user from having tabs which is what the OP was asking for.
If anybody should happen to know a solution to hide those entries, please let us know. I couldn't find a CommandGroupPlacement that represents these menus...
I found how to remove the 'Edit' menu item after reading this article
https://steipete.com/posts/top-level-menu-visibility-in-swiftui/
func applicationDidFinishLaunching(_ notification: Notification) {
NSWindow.allowsAutomaticWindowTabbing = false
if let mainMenu = NSApp .mainMenu {
DispatchQueue.main.async {
if let edit = mainMenu.items.first(where: { $0.title == "Edit"}) {
mainMenu.removeItem(edit);
}
}
}
}
2021, swiftUI 2 and higher
Result in release build:
look at places with comments "HERE":
#main
struct MyFirstApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
init() {
// HERE
disallowTabbingMode()
}
#SceneBuilder
var body: some Scene {
WindowGroup {
AppMainView()
}
.commands {
//MenuLine_MyFirstApp_CustomAbout()
// HERE
MenuLine_File_NewWindow_Disable()
//MenuLine_Help_SupportEmail()
}
}
}
And method + bonuses for understanding menus in SwiftUI
// Set Custom About window instead of default
fileprivate func MenuLine_MyFirstApp_CustomAbout() -> CommandGroup<Button<Text>> {
CommandGroup(replacing: CommandGroupPlacement.appInfo) {
Button("About") { appDelegate.showAboutWnd() }
}
}
//Add some menu button into Help menu
fileprivate func MenuLine_Help_SupportEmail() -> CommandGroup<Button<Text>> {
CommandGroup(after: CommandGroupPlacement.help) {
Button("Support Email") { NSWorkspace.shared.open("mailto:support#myfirstapp.com") }
}
}
// Disable "File -> New window" menu item (make it absent in release build)
fileprivate func MenuLine_File_NewWindow_Disable() -> CommandGroup<EmptyView> {
CommandGroup(replacing: .newItem) { }
}
fileprivate func disallowTabbingMode() {
NSWindow.allowsAutomaticWindowTabbing = false
}