Mac 上的 SwiftUI - 如何将按钮指定为主按钮?
SwiftUI on Mac - How do I designate a button as being the primary?
在 AppKit 中,我会通过 assigning its key equivalent to be ↩ or making its cell the window's default 来完成此操作。但是,这在 SwiftUI 中似乎都不可能,所以 如何将按钮设置为默认 window 按钮?
目前不可以。我有 reported it to Apple.
不过,目前您可以包装 NSButton。
用法:
@available(macOS 10.15, *)
struct ContentView: View {
var body: some View {
NativeButton("Submit", keyEquivalent: .return) {
// Some action
}
.padding()
}
}
实施:
// 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()
}
}
}
这是一个更短但不太通用的解决方案,用于创建具有 return 等效键和默认按钮蓝色着色的主按钮。
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()
}
}
macOS 11.0/iOS14:
从 Xcode 12 beta 开始,Button() 公开了新方法,允许分配 keyEquivalent(通过枚举大小写或显式键和修饰符)。
默认设置:
Button( ... )
.keyboardShortcut(.defaultAction)
设置为取消:
Button( ... )
.keyboardShortcut(.cancelAction)
在 AppKit 中,我会通过 assigning its key equivalent to be ↩ or making its cell the window's default 来完成此操作。但是,这在 SwiftUI 中似乎都不可能,所以 如何将按钮设置为默认 window 按钮?
目前不可以。我有 reported it to Apple.
不过,目前您可以包装 NSButton。
用法:
@available(macOS 10.15, *)
struct ContentView: View {
var body: some View {
NativeButton("Submit", keyEquivalent: .return) {
// Some action
}
.padding()
}
}
实施:
// 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()
}
}
}
这是一个更短但不太通用的解决方案,用于创建具有 return 等效键和默认按钮蓝色着色的主按钮。
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()
}
}
macOS 11.0/iOS14:
从 Xcode 12 beta 开始,Button() 公开了新方法,允许分配 keyEquivalent(通过枚举大小写或显式键和修饰符)。
默认设置:
Button( ... )
.keyboardShortcut(.defaultAction)
设置为取消:
Button( ... )
.keyboardShortcut(.cancelAction)