In AppKit I would do this by assigning its key equivalent to be ↩ or making its cell the window\'s default. However, neither of these seems possible in SwiftUI, so h
Here is a shorter, but less generic solution to create a primary button with return key equivalent, and default button blue tinting.
struct PrimaryButtonView: NSViewRepresentable {
typealias NSViewType = PrimaryButton
let title: String
let action: () -> Void
init(_ title: String, action: @escaping () -> Void) {
self.title = title
self.action = action
}
func makeNSView(context: Context) -> PrimaryButton {
PrimaryButton(title, action: action)
}
func updateNSView(_ nsView: PrimaryButton, context: Context) {
return
}
}
class PrimaryButton: NSButton {
let buttonAction: () -> Void
init(_ title: String, action: @escaping () -> Void) {
self.buttonAction = action
super.init(frame: .zero)
self.title = title
self.action = #selector(clickButton(_:))
bezelStyle = .rounded //Only this style results in blue tint for button
isBordered = true
focusRingType = .none
keyEquivalent = "\r"
}
required init?(coder: NSCoder) {
fatalError()
}
@objc func clickButton(_ sender: PrimaryButton) {
buttonAction()
}
}
As of Xcode 12 beta, new methods are exposed on Button() allowing assignment of keyEquivalent (either by enum case or explicit key and modifiers).
Setting as default:
Button( ... )
.keyboardShortcut(.defaultAction)
Setting as cancel:
Button( ... )
.keyboardShortcut(.cancelAction)
It's currently not possible. I have reported it to Apple.
However, for now, you can wrap NSButton.
Usage:
@available(macOS 10.15, *)
struct ContentView: View {
var body: some View {
NativeButton("Submit", keyEquivalent: .return) {
// Some action
}
.padding()
}
}
Implementation:
// MARK: - Action closure for controls
private var controlActionClosureProtocolAssociatedObjectKey: UInt8 = 0
protocol ControlActionClosureProtocol: NSObjectProtocol {
var target: AnyObject? { get set }
var action: Selector? { get set }
}
private final class ActionTrampoline<T>: NSObject {
let action: (T) -> Void
init(action: @escaping (T) -> Void) {
self.action = action
}
@objc
func action(sender: AnyObject) {
action(sender as! T)
}
}
extension ControlActionClosureProtocol {
func onAction(_ action: @escaping (Self) -> Void) {
let trampoline = ActionTrampoline(action: action)
self.target = trampoline
self.action = #selector(ActionTrampoline<Self>.action(sender:))
objc_setAssociatedObject(self, &controlActionClosureProtocolAssociatedObjectKey, trampoline, .OBJC_ASSOCIATION_RETAIN)
}
}
extension NSControl: ControlActionClosureProtocol {}
// MARK: -
@available(macOS 10.15, *)
struct NativeButton: NSViewRepresentable {
enum KeyEquivalent: String {
case escape = "\u{1b}"
case `return` = "\r"
}
var title: String?
var attributedTitle: NSAttributedString?
var keyEquivalent: KeyEquivalent?
let action: () -> Void
init(
_ title: String,
keyEquivalent: KeyEquivalent? = nil,
action: @escaping () -> Void
) {
self.title = title
self.keyEquivalent = keyEquivalent
self.action = action
}
init(
_ attributedTitle: NSAttributedString,
keyEquivalent: KeyEquivalent? = nil,
action: @escaping () -> Void
) {
self.attributedTitle = attributedTitle
self.keyEquivalent = keyEquivalent
self.action = action
}
func makeNSView(context: NSViewRepresentableContext<Self>) -> NSButton {
let button = NSButton(title: "", target: nil, action: nil)
button.translatesAutoresizingMaskIntoConstraints = false
button.setContentHuggingPriority(.defaultHigh, for: .vertical)
button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return button
}
func updateNSView(_ nsView: NSButton, context: NSViewRepresentableContext<Self>) {
if attributedTitle == nil {
nsView.title = title ?? ""
}
if title == nil {
nsView.attributedTitle = attributedTitle ?? NSAttributedString(string: "")
}
nsView.keyEquivalent = keyEquivalent?.rawValue ?? ""
nsView.onAction { _ in
self.action()
}
}
}