SwiftUI on Mac - How do I designate a button as being the primary?

前端 未结 3 456
暖寄归人
暖寄归人 2021-01-02 11:44

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

相关标签:
3条回答
  • 2021-01-02 12:22

    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()
        }
    }
    
    0 讨论(0)
  • 2021-01-02 12:23

    macOS 10.16 / iOS 14:

    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)
    
    0 讨论(0)
  • 2021-01-02 12:45

    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()
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题