Mac 上的 SwiftUI - 如何将按钮指定为主按钮?

Posted

技术标签:

【中文标题】Mac 上的 SwiftUI - 如何将按钮指定为主按钮?【英文标题】:SwiftUI on Mac - How do I designate a button as being the primary? 【发布时间】:2019-07-31 05:40:45 【问题描述】:

在 AppKit 中,我会通过 assigning its key equivalent to be 或 making its cell the window's default 执行此操作。但是,这些在 SwiftUI 中似乎都不可能,所以如何将按钮设为默认窗口按钮?

【问题讨论】:

【参考方案1】:

macOS 11.0 / ios 14:

从 Xcode 12 测试版开始,Button() 上公开了新方法,允许分配 keyEquivalent(通过枚举大小写或显式键和修饰符)。

默认设置:

Button( ... )
    .keyboardShortcut(.defaultAction)

设置为取消:

Button( ... )
    .keyboardShortcut(.cancelAction)

【讨论】:

但这仅适用于菜单,不适用于窗口或工作表内部,因此,即使对于 SwiftUI 2,这也不是解决方案。 现在可以在 Xcode 版本 12.2 beta 3 (12B5035g) 和 Big Sur bêta 11.0 (20A5395g) 中使用 更改按钮样式时不起作用。【参考方案2】:

目前不可能。我有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 = "\u1b"
        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()
        
    

【讨论】:

在纯 SwiftUI 中仍然不可能。希望我们能在今年 6 月的 WWDC 上看到它。 我真的希望如此,但缺少许多功能,例如 SearchBar、这些按钮等等。您的解决方案是否也为按钮着色?稍后会尝试一下。感谢您的回答 如果您KeyEquivalent.return,该按钮将以用户的强调色(通常为蓝色)突出显示。 这是迄今为止适用于 Mac 的最佳解决方案!干得好 从 Xcode 12 beta 开始,原生 SwiftUI 已经有一段时间了。详情见我的回答。【参考方案3】:

这是一个较短但不太通用的解决方案,用于创建具有等效返回键和默认按钮蓝色的主按钮。

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()
    

【讨论】:

非常好的解决方案。 Button 占用所有可用空间。我怎样才能改变它,只占用标题的空间? 我知道是否有办法使按钮大小适合文本。我在父视图中明确设置了 PrimaryButtonView 的框架。

以上是关于Mac 上的 SwiftUI - 如何将按钮指定为主按钮?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:检测 Mac 触控板上的手指位置

您如何在 Swift Playgrounds 中的 iPad 上的 SwiftUI 中渲染 Text("Content") 与 Mac 上的 Xcode?

ForEach onDelete Mac

AppKit 上的 SwiftUI - 在弹出窗口中将焦点设置为 TextField

Mac 上的 SwiftUI:帮助文本即使在不透明度为零的视图中也始终可见

SwiftUI - 如何检测按钮上的长按?