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?
Related
I have an NSButton in my ViewController class that is not triggering its click function (closeButtonPressed) when clicked.
I would test with a Button but UIKit isn't available for my project, as it's a non-Catalyst app for Mac.
As this has been a challenging and time-consuming issue, I am grateful for your time and expertise in helping me resolve it.
Minimal reproducible example
import SwiftUI
#main
struct MinimalReproducibleExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import Cocoa
import SwiftUI
struct ViewControllerWrapper: NSViewRepresentable {
func makeNSView(context: Context) -> NSView {
return ViewController().view
}
func updateNSView(_ nsView: NSView, context: Context) {
// You can add any logic you need here to update the view as needed.
}
}
class ViewController: NSViewController {
let closeButton = NSButton(title: "X", target: nil, action: nil)
override func loadView() {
self.view = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 300))
self.view.wantsLayer = true
self.view.layer?.backgroundColor = NSColor.black.cgColor
}
override func viewDidLoad() {
super.viewDidLoad()
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.cyan.cgColor
closeButton.target = self
closeButton.action = #selector(closeButtonPressed)
closeButton.frame = CGRect(x: 100, y: 100, width: 50, height: 30)
closeButton.bezelColor = NSColor.gray
view.addSubview(closeButton)
}
#objc func closeButtonPressed() {
print("Close button pressed")
}
}
struct ContentView: View {
private var viewControllerWrapper: ViewControllerWrapper
init(){
self.viewControllerWrapper = ViewControllerWrapper()
}
var body: some View {
VStack {
viewControllerWrapper
}
}
}
(MacOS 12.6.3, Xcode 14.2)
As you are using a NSViewController you should use NSViewControllerRepresentable instead of using NSViewRepresentable.
NSViewControllerRepresentable is explicitly for use with NSViewController while NSViewRepresentable is for use with NView.
If you can change your ViewControllerWrapper to the following:
struct ViewControllerWrapper: NSViewControllerRepresentable {
func makeNSViewController(context: Context) -> ViewController {
ViewController()
}
func updateNSViewController(_ nsViewController: ViewController, context: Context) {
}
}
This should allow the button to work.
*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.
My MacOS app doesn't have any text editing possibilities. How can I hide the Edit menu which is added to my app automatically? I'd prefer to do this in SwiftUI.
I would expect the code below should work, but it doesn't.
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}.commands {
CommandGroup(replacing: .textEditing) {}
}
}
}
To my knowledge you cannot hide the whole menu, you can just hide element groups inside of it:
.commands {
CommandGroup(replacing: .pasteboard) { }
CommandGroup(replacing: .undoRedo) { }
}
For native (Cocoa) apps
It is possible to remove application menus using an NSApplicationDelegate. This approach may break in future macOS versions (e.g. if the position of the Edit menu were to change), but does currently work:
class MyAppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
let indexOfEditMenu = 2
func applicationDidFinishLaunching(_ : Notification) {
NSApplication.shared.mainMenu?.removeItem(at: indexOfEditMenu)
}
}
#main
struct MyApp: App {
#NSApplicationDelegateAdaptor private var appDelegate: MyAppDelegate
var body: some Scene {
WindowGroup {
ContentView()
}.commands {
// ...
}
}
}
For Catalyst (UIKit) apps
For Catalyst-based macOS apps, the approach is similar to that above, except that a UIApplicationDelegate deriving from UIResponder is used:
class MyAppDelegate: UIResponder, UIApplicationDelegate, ObservableObject {
override func buildMenu(with builder: UIMenuBuilder) {
/// Only operate on the main menu bar.
if builder.system == .main {
builder.remove(menu: .edit)
}
}
}
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor private var appDelegate: MyAppDelegate
var body: some Scene {
WindowGroup {
ContentView()
}.commands {
// ...
}
}
}
The current suggestions failed for me when SwiftUI updated the body of a window.
Solution:
Use KVO and watch the NSApp for changes on \.mainMenu. You can remove whatever you want after SwiftUI has its turn.
#objc
class AppDelegate: NSObject, NSApplicationDelegate {
var token: NSKeyValueObservation?
func applicationDidFinishLaunching(_ notification: Notification) {
// Remove a single menu
if let m = NSApp.mainMenu?.item(withTitle: "Edit") {
NSApp.mainMenu?.removeItem(m)
}
// Remove Multiple Menus
["Edit", "View", "Help", "Window"].forEach { name in
NSApp.mainMenu?.item(withTitle: name).map { NSApp.mainMenu?.removeItem($0) }
}
// Must remove after every time SwiftUI re adds
token = NSApp.observe(\.mainMenu, options: .new) { (app, change) in
["Edit", "View", "Help", "Window"].forEach { name in
NSApp.mainMenu?.item(withTitle: name).map { NSApp.mainMenu?.removeItem($0) }
}
// Remove a single menu
guard let menu = app.mainMenu?.item(withTitle: "Edit") else { return }
app.mainMenu?.removeItem(menu)
}
}
}
struct MarblesApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some View {
//...
}
}
Thoughts:
SwiftUI either has a bug or they really don't want you to remove the top level menus in NSApp.mainMenu. SwiftUI seems to reset the whole menu with no way to override or customize most details currently (Xcode 13.4.1). The CommandGroup(replacing: .textEditing) { }-esque commands don't let you remove or clear a whole menu. Assigning a new NSApp.mainMenu just gets clobbered when SwiftUI wants even if you specify no commands.
This seems like a very fragile solution. There should be a way to tell SwiftUI to not touch the NSApp.mainMenu or enable more customization. Or it seems SwiftUI should check that it owned the previous menu (the menu items are SwiftUI.AppKitMainMenuItem). Or I'm missing some tool they've provided. Hopefully this is fixed in the WWDC beta?
(In Xcode 13.4.1 with Swift 5 targeting macOS 12.3 without Catalyst.)
For those of you that are looking for any updates on this - have a look at this question that I asked (and answered myself):
SwiftUI Update the mainMenu [SOLVED] kludgey
The way I got around it was to put it in a DispatchQueue.main.async closure in the AppDelegate applicationWillUpdate function
import Foundation
import AppKit
public class AppDelegate: NSObject, NSApplicationDelegate {
public func applicationWillUpdate(_ notification: Notification) {
DispatchQueue.main.async {
let currentMainMenu = NSApplication.shared.mainMenu
let editMenu: NSMenuItem? = currentMainMenu?.item(withTitle: "Edit")
if nil != editMenu {
NSApp.mainMenu?.removeItem(editMenu!)
}
}
}
}
It took me a good 4 days of searching and attempting things :) - typical that it came down to a 2 line code change
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.
I installed the Mapbox SDK into my project, but I don't understand how to integrate this code snippet with SwiftUI.
I created a SwiftUI View named MapView, where I import the Mapbox Framework.
I try to use the UIViewRepresentable protocol, as in Apple's tutorial, but without success.
import Mapbox
class MapView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let styleURL = URL(string: "mapbox://styles/mapbox/outdoors-v9")
let mapView = MGLMapView(frame: view.bounds,
styleURL: styleURL)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.setCenter(CLLocationCoordinate2D(latitude: 45.52954,
longitude: -122.72317),
zoomLevel: 14,
animated: false)
view.addSubview(mapView)
}
}
I am new to iOS development, so any help would be much appreciated.
This is a working sample on how you can integrate the MGLMapView with SwiftUI.
When using UIViewRepresentable you have to implement two delegates: makeUIView that will return your view (in this case MGLMapView) and updateUIView which will receive the same view type as the one returned in makeUIView (again MGLMapView).
You can use this to experiment.
Also I recommend you get familiar with the React architecture to understand better the SwiftUI approach.
You can improve this sample by making ContentView receive a styleURL, and trying different styles in the preview.
import Mapbox
import SwiftUI
struct ContentView : View {
var body: some View {
MapboxViewRepresentation(MGLStyle.streetsStyleURL)
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
struct MapboxViewRepresentation : UIViewRepresentable {
let styleURL: URL
init(_ styleURL: URL) {
self.styleURL = styleURL
}
func makeUIView(context: UIViewRepresentableContext<MapboxViewRepresentation>) -> MGLMapView {
let mapView = MGLMapView(frame: .zero, styleURL: styleURL)
return mapView
}
func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<MapboxViewRepresentation>) {
}
}
UPDATE: Here is the official guide on how to integrate Mapbox with SwiftUI https://docs.mapbox.com/help/tutorials/ios-swiftui/