SwiftUI and macOS Option-click a Button - swift

Hi how can I detect when a user option-clicks a Button?
Button("Example") {
if optionKeyDown {
// A
} else {
// B
}
}
I am using the SwiftUI App Lifecycle.

I don't know of a pure SwiftUI way of doing it, but SwiftUI for macOS apps still needs work to really get the UI experience Mac users expect.
However, you can use CoreGraphics.CGEventSource to achieve what you want:
import CoreGraphics
extension CGKeyCode
{
static let kVK_Option : CGKeyCode = 0x3A
static let kVK_RightOption: CGKeyCode = 0x3D
var isPressed: Bool {
CGEventSource.keyState(.combinedSessionState, key: self)
}
static var optionKeyPressed: Bool {
return Self.kVK_Option.isPressed || Self.kVK_RightOption.isPressed
}
}
The specific key code names in the extension aren't particularly Swifty, so rename them if you like. Those particular names go back to the Classic MacOS Toolbox, and were used in the early days of OS X in the Carbon Event Manager. I tend to keep them for historical reasons. I've created a github gist with of all the old key codes.
With that extension in place, using it is straight-forward. Your code snippet becomes
Button("Example") {
if CGKeyCode.optionKeyPressed {
// A
} else {
// B
}
}

Related

A Swift input method switcher works only after changing focus to another window

I am trying to write a MacOS app which switch input methods by previously assigned shortcut(command+space in here).
Switching input methods preoperly works so that the language icon at the status bar(top right) immediately changes as I put the shortcut.
The problem I got in here is that the actual input method does not change.
For example, if I run my app when the selected input method is Korean, then although the status bar is showing the selected input method is Japanese after command+space, what I can only type is Korean characters.
However, after I change focus to another text app(e.g. from sublime text to xcode), only then the selected input method is reflected well.
I am using MacOS Monterey 12.6 and Xcode 13.1.
My project contains two source files.
The code in the file AppDelegate.swift is as follows:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var switcher = Switcher()
}
And the code in the file Switcher.swift is as follows:
import Cocoa
import MASShortcut
class Switcher{
var lang: Int = 0
var kr: TISInputSource?
var jp: TISInputSource?
var en: TISInputSource?
init(){
let inputSourceNSArray = TISCreateInputSourceList(nil, false).takeRetainedValue() as NSArray
let inputSourceList = inputSourceNSArray as! [TISInputSource]
for inputSource in inputSourceList {
if inputSource.id == "com.apple.inputmethod.Korean.2SetKorean" {
self.kr = inputSource
}
if inputSource.id == "com.apple.inputmethod.Kotoeri.RomajiTyping.Japanese" {
self.jp = inputSource
}
if inputSource.id == "com.apple.keylayout.ABC" {
self.en = inputSource
}
}
self.register()
}
func switchLang(){
self.lang = (self.lang + 1) % 3
switch lang {
case 0:
TISSelectInputSource(self.kr)
case 1:
TISSelectInputSource(self.jp)
case 2:
TISSelectInputSource(self.en)
default:
print("error")
}
}
func register() {
let langShortcut = MASShortcut(keyCode: kVK_Space, modifierFlags: [.command])
MASShortcutMonitor.shared()?.register(langShortcut, withAction: {
self.switchLang()
})
}
}
I wrote these codes by referring KAWA, but KAWA does not make this issue.
I have analyzed all codes of KAWA several times, I couldn't find out why the same problem does not occur in KAWA.
I am quite new to Swift, and I have no idea to approach.
Could you help me....? Thank you.

Cannot find "UIScreen" in scope [duplicate]

This question already has an answer here:
Adjust screen brightness in Mac OS X app
(1 answer)
Closed last year.
I've just approached Swift, for building a quick utility app for my MacBook.
I have written the following code, but it gives me the error "Cannot find 'UIScreen' in scope".
class AppDelegate: NSObject,NSApplicationDelegate{
//...
func applicationDidFinishLaunching(_ notification: Notification) {
Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
UIScreen.mainScreen().brightness = CGFloat(0.0) //<- error here
}
}
//...
}
Initially I thought I've missed an import, but I cannot find what to import (UIKit, is only for iOS).
Thank you for any help
Anything beginning with "UI" is an iOS class. Those classes are not available from a macOS app.
The equivalent class for macOS is probably NSScreen, although I don't think it has an exposed brightness property like the one you are trying to set.
Edit:
It looks like it is possible to set the screen brightness in MacOS, but you have to use much lower-level code. Check out this link.
The relevant code from that link is the following, which uses IOKit:
func setBrightnessLevel(level: Float) {
var iterator: io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"), &iterator) == kIOReturnSuccess {
var service: io_object_t = 1
while service != 0 {
service = IOIteratorNext(iterator)
IODisplaySetFloatParameter(service, 0, kIODisplayBrightnessKey, level)
IOObjectRelease(service)
}
}
}

How do I check if the shift key is currently being pressed down in Swift?

I'm trying to check whether or not shift is being pressed when a function is run. Something like this (but obviously not this, this is just an example):
func doThisThing() {
if Keyboard.shared.keyBeingPressed(.shift) { // < What I'm trying to figure out
print("Doing this thing.")
} else {
print("You're not holding shift.")
}
}
I tried looking, but all I could find was keyDown/keyUp events, which isn't practical in this case.
import AppKit
public class KeyboardHelper
{
public static var optionKeyIsDown: Bool
{
let flags = NSEvent.modifierFlags
return flags.contains(.option)
}
public static var shiftKeyIsDown: Bool
{
let flags = NSEvent.modifierFlags
return flags.contains(.shift)
}
}
It's been a long time since I've done this sort of thing, but I seem to remember that you would need to set up an NSResponder and watch for keyDown/keyUp events. Then you'd parse the event to look for the shift key being pressed or released.
It looks like there is also a function flagsChanged that you can implement to be notified when one of the modifier keys changes state. See this SO thread:
How can I detect that the Shift key has been pressed?

NotificationCenter.Publisher VS PassThroughSubject

I have an object which I want to send throughout multiple listeners/subscribers, so I was checking out Combine and I saw 2 different kind of publishers, namely NotificationCenter.Publisher and PassThroughSubject. I am confused why anyone would use a NotificationCenter.Publisher over PassThroughSubject.
I came up with the code below, demonstrating both ways. To summarize:
NotificationCenter.Publisher needs to have a Notification.Name static property
Isn't really that typesafe (since I can post a different kind of object for the same Notification.Name/different publisher for the same Notification.Name)
Posting a new value needs to be done on NotificationCenter.default (not the publisher itself)
An explicit downcast to the used type in the map closure
In what scenarios someone will use NotificationCenter.Publisher over PassThroughSubject?
import UIKit
import Combine
let passThroughSubjectPublisher = PassthroughSubject<String, Never>()
let notificationCenterPublisher = NotificationCenter.default.publisher(for: .name).map { $0.object as! String }
extension Notification.Name {
static let name = Notification.Name(rawValue: "someName")
}
class PassThroughSubjectPublisherSubscriber {
init() {
passThroughSubjectPublisher.sink { (_) in
// Process
}
}
}
class NotificationCenterPublisherSubscriber {
init() {
notificationCenterPublisher.sink { (_) in
// Process
}
}
}
class PassThroughSubjectPublisherSinker {
init() {
passThroughSubjectPublisher.send("Henlo!")
}
}
class NotificationCenterPublisherSinker {
init() {
NotificationCenter.default.post(name: .name, object: "Henlo!")
}
}
If you have to use a 3rd party framework that uses NotificationCenter.
NotificationCenter can be thought of as a first generation message passing system, while Combine is second generation. It has runtime overhead and requires casting the objects you can store in Notifications. Personally I would never use NotificationCenter when building an iOS 13 framework, but you do need to use it to access a lot of iOS notifications that are only published there. Basically in my personal projects I’m going to treat it as read only unless absolutely necessary.

Can you create polyfills in swift using availability checking?

Swift 2 lets you check the current platform and version to make sure that newer methods/properties are available and allow you to write fallback code if they are not, by writing a conditional block with #available.
So for example, in OSX10.10, NSTextField has a property called placeholderString, but in older versions, you had to access this info via an intermediate NSTextFieldCell object. You could safely use the new system with a fallback for the old one like this:
var placeholder : String
if #available(OSX 10.10, *) {
placeholder = inputBox.placeholderString!
} else {
placeholder = (inputBox.cell as! NSTextFieldCell).placeholderString!
}
Swift also allows you to extend existing classes with extension
For example, adding the placeholder property to NSTextField
extension NSTextField {
var placeholderString: String? {
return (self.cell as! NSTextFieldCell).placeholderString
}
}
So... Is it possible to combine these 2 ideas and create something like a polyfill, where we extend a class conditionally on the current platform, in order to retrospectively add features of the new API to older platforms?
So in my example, I would want to write something like this:
if !#available(OSX 10.10, *) {
extension NSTextField {
var placeholderString: String? {
return (self.cell as! NSTextFieldCell).placeholderString
}
}
}
The above doesn't compile. But it illustrates the kind of thing I'm looking for. If a particular thing is available on the current platform, then use it, otherwise supply an implementation for it.
Is there any way in Swift to achieve this?
Subsequently you would be able to safely use textField.placeholderString without needing to worry about the platform version.
if #available() is checked a runtime, not at compile or link time.
Therefore I assume that the only possible way is to define a wrapper
method:
extension NSTextField {
var myPlaceholderString: String? {
if #available(OSX 10.10, *) {
return self.placeholderString
} else {
return (self.cell as! NSTextFieldCell).placeholderString
}
}
}
(and I understand that this is not exactly what you are looking for.)
Perhaps someone else has a better solution!