Cannot inject AppDelegate to SwiftUI macos app - swift

I'm new to swift development and stuck developing sattus bar application. Found some good toturials (first, second, third, fourth), and in eash of them at some point authors inject AppDelegate to : App class, kinda:
import SwiftUI
#main
struct CaptureOneAppApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
And in tutorials similar code works just fine, but me getting compile-time error pointing row #5:
Type annotation missing in pattern
As I understand, compiler asks me to add some suffix with type definition, but i have no idea how it should look like and actually not sure if this is a correct way to solve the problem. So, what I'm doing wrong?
UPD AppDelegate.swift code:
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var statusBar: StatusBarController?
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
//Initialising the status bar
statusBar = StatusBarController.init()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
it's just copypasted from first

Related

Xcode SwiftUI window not shown after successful build

I get the following warning, on main.storyboard
Unsupported Configuartion:
Window Controller requires a content view controller
This is the custom class linked to the Window Controller, which is also the StoryBoard Entry Point,
import Cocoa
import SwiftUI
class FirstWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
let contentView = ContentView(myListModel: MyListModel())
.environmentObject(UserData())
self.window?.contentView = NSHostingView(rootView: contentView)
}
}
This is inside the AppDelegate.swift which is annotated as #NSApplicationMain.
func applicationDidFinishLaunching(_ aNotification: Notification) {
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window”)
}
The main.storyboard, AppDelegate.swift, and FirstWindowController.swift are identical to those of a project which was launching and showing the application window with no problem. I just renamed it to something else, and removed the Core Data support from the code. But in this project, the window doesn’t show up after successful build.
I also have checked and compared all the configuration of the storyboard for both of these projects. They seem to be totally identical.
Any help for fixing this would be appreciated.
This might be due to a bug.
I removed all the other schemes but the one I was using in the project and the window is now loading.
Yet, the same warning is still there.

Draw shape around cursor on macOS using Swift [duplicate]

This question already has an answer here:
How do I change my cursor on the whole screen? (not only current view/window)
(1 answer)
Closed 1 year ago.
I'm pretty new to macOS and swift development and I have been watching a few tutorials to figure this out. I also watched a udemy course that worked on 18 macOS projects which got me nowhere
All I want to do is a macOS menu bar app that will add a cursor highlight that should look something like:
I could get the cursor changed to an image doing the following
import SpriteKit
class CursorView: SKView {
override func resetCursorRects() {
if let targetImage = NSImage(named: "cursor") {
let cursor = NSCursor(image: targetImage,
hotSpot: CGPoint(x: targetImage.size.width / 2,
y: targetImage.size.height / 2))
addCursorRect(frame, cursor: cursor)
}
}
}
Three things wrong with this:
SKView is a class from SpriteKit and I don't think I should use that for my use-case
This calls the addCursorRect that add the changes to a window frame (I need to all the time regardless of the frame)
I can't have 100's of images for each style I would set in the future for the highlight color, size, or opacity
So, I'm here trying to understand how I can do this for a menu bar app that should be available on all screens and achieve a highlight as should in the above picture
Not sure if this matters but I'm using storyboard and don't mind switching to SwiftUI
I could really use some help from the community on this. Thank you
You can annotate the system mouse by drawing something around it. This can be done by
adding an CGEvent tap to capture mouse events
drawing the annotation around the cursor using a custom window
Here is a simple example application
import SwiftUI
#main
class AppDelegate: NSObject, NSApplicationDelegate {
var mouseTap: CFMachPort?
private var window: NSWindow = {
// Create the SwiftUI view that provides the window contents.
let contentView = Circle()
.stroke(lineWidth: 2)
.foregroundColor(.blue)
.frame(width: 30, height: 30)
.padding(2)
// Create the window and set the content view.
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 34, height: 34),
styleMask: [.borderless],
backing: .buffered,
defer: false
)
window.contentView = NSHostingView(rootView: contentView)
window.backgroundColor = .clear
window.level = NSWindow.Level.statusBar
window.makeKeyAndOrderFront(nil)
return window
}()
func applicationDidFinishLaunching(_ aNotification: Notification) {
if let tap = createMouseTap() {
if CGEvent.tapIsEnabled(tap: tap) {
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, CFRunLoopMode.commonModes)
mouseTap = tap
} else {
print("tap not enabled")
mouseTap = nil
}
} else {
print("tap not enabled")
}
}
func createMouseTap() -> CFMachPort? {
withUnsafeMutableBytes(of: &window) { pointer in
CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: CGEventTapOptions.listenOnly,
eventsOfInterest: (1 << CGEventType.mouseMoved.rawValue | 1 << CGEventType.leftMouseDragged.rawValue),
callback: mouseMoved,
userInfo: pointer.baseAddress
)
}
}
}
func mouseMoved(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, context: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
let window = context!.assumingMemoryBound(to: NSWindow.self).pointee
// using CGPoint+SIMD extension from https://gist.github.com/Dev1an/7973cee9d960479b35b705f88b7f38c4
window.setFrameOrigin(event.unflippedLocation - 17)
return nil
}
Drawback
Note that this implementation does require the user to allow the keyboard input option in "Universal Access" in "System Preferences".

Scheduling IOHIDManager with runloop in a GUI App

I am writing an app that is monitoring input from a gamepad in swift.
I managed to build a command-line application with the intended behavior :
import Foundation
import IOKit.hid
var valueCallback : IOHIDValueCallback = {
(context, result, sender, value) in
let element = IOHIDValueGetElement(value)
print(IOHIDElementGetUsage(element), IOHIDValueGetIntegerValue(value))
}
var attachCallback : IOHIDDeviceCallback = {
(context, result, sender, device) in
IOHIDDeviceOpen(device, IOOptionBits(kIOHIDOptionsTypeSeizeDevice))
IOHIDDeviceRegisterInputValueCallback(device, valueCallback, context)
print("Controller attached")
}
var detachCallback : IOHIDDeviceCallback = {
(context, result, sender, device) in
print("Controller detached")
}
class HID {
let manager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
let devices = [kIOHIDTransportKey: "Bluetooth"] as CFDictionary
init() {
IOHIDManagerSetDeviceMatching(self.manager, self.devices)
IOHIDManagerRegisterDeviceMatchingCallback(self.manager, attachCallback, nil)
IOHIDManagerRegisterDeviceRemovalCallback(self.manager, detachCallback, nil)
IOHIDManagerScheduleWithRunLoop(self.manager, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue)
IOHIDManagerOpen(self.manager, IOOptionBits(kIOHIDOptionsTypeNone))
}
}
var hidTest = HID()
CFRunLoopRun()
I then want to use this inside an actual app, but it then doesn't work as intended. I used the same code for the class and callbacks, and tried this in the AppDelegate :
import Cocoa
import SwiftUI
import AppKit
import IOKit.hid
import Foundation
#main
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ContentView()
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
let hidTest = HID()
CFRunLoopRun()
}
}
Everything compiles fine, but I am not able to get any value from the gamepad. I think the problem is there : IOHIDManagerScheduleWithRunLoop(self.manager, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue) because the manager is probably not attached to the app's RunLoop.
Is there something specific I need to do to make this work ?
Thanks in advance !
I'm not a Swift expert, but I do know about IOKit and runloops, so I'll attempt an answer.
I think it's likely there is a problem with the call to CFRunLoopRun(). Cocoa apps already run in a runloop which is implicitly created and run by NSApplication. Don't explicitly run the runloop, just allow applicationDidFinishLaunching to return.
When you do this, you should probably make your HID object a member of your application object or similar, or it'll be destroyed. I'm not sure if the Swift bindings for IOKit perform any automatic resource destruction; if so, this would presumably destroy the HID manager object, which you probably want to stay alive for the duration of you application's lifetime.

NSWorkspace didMountNotification / didUnmountNotification not triggered

I am in the process of creating a new macOS application that is going to perform some action whenever a USB device (or other storage device) has been attached to the computer. It also needs to run silently in the background.
For the USB part, I have added the following class to the project:
import Foundation
import AppKit
class UsbDetector {
init() {
print("Hello from UsbDetector")
let notificationCenter = NSWorkspace.shared.notificationCenter
notificationCenter.addObserver(self, selector: #selector(didMount(_:)), name: NSWorkspace.didMountNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(didUnmount(_:)), name: NSWorkspace.didUnmountNotification, object: nil)
}
#objc func didMount(_ notification: NSNotification) {
print("Hello from mounted")
if let devicePath = notification.userInfo!["NSDevicePath"] as? String {
print("Mounted: \(devicePath)")
}
}
#objc func didUnmount(_ notification: NSNotification) {
print("Hello from unmounted")
if let devicePath = notification.userInfo!["NSDevicePath"] as? String {
print("Unmounted: \(devicePath)")
}
}
}
I instantiate the UsbDetector from AppDelegate.swift as follows:
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
print("Hello from AppDelegate")
// Start UsbDetector
_ = UsbDetector()
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
To make it run in the background, I have added the following key to the Info.plist file of the project:
<key>LSBackgroundOnly</key>
<true/>
When the app is started, it is visible in the Activity Monitor but nowhere else, which is fine. But when I attach / detach a USB stick, the NSWorkspace event is not triggered. All that gets printed to the console is:
Hello from AppDelegate
Hello from UsbDetector
I am using Xcode 11.5 and macOS Catalina 10.15.5.
Can someone tell me what I am doing wrong here? Thanks in advance!

How do I get a titlebar to show programmatically in NSWindow?

Im trying to develop an OS X cocoa application programmatically, and I'm trying to display a window with a title bar which displays the usual traffic light (close, minimise, fullscreen) options at the top.
However, when the window displays on the screen, there is just an empty window.
Here is the code I am using:
class AppDelegate: NSObject, NSApplicationDelegate {
let window = NSWindow(contentRect: NSMakeRect(10, 10, 200, 200),
styleMask: NSWindowStyleMask.closable,
backing: NSBackingStoreType.buffered, defer: true)
func applicationDidFinishLaunching(_ aNotification: Notification) {
self.titleVisibility = .visible;
self.titlebarAppearsTransparent = false;
self.isMovableByWindowBackground = true;
let controller = NSWindowController(window: window)
controller.showWindow(self);
}
I have tried different NSWindowStyleMask when constructing the NSWindow but to no success.
This is what I see:
I am using Xcode 8.3 on 10.12
So you need 4 style masks.
NSWindowStyleMask.closable
NSWindowStyleMask.miniaturizable
NSWindowStyleMask.resizable
NSWindowStyleMask.titled
To put them all into one, you can use array
[NSWindowStyleMask.closable, NSWindowStyleMask.miniaturizable, NSWindowStyleMask.resizable, NSWindowStyleMask.titled]
But try to write in swift style
[.closable, .miniaturizable, .resizable, .titled]