Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

macos

swiftui

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 how do I make a button the default window button?

A Cancel button in the normal grey look, and a Save button in the system accent color. The text "Like this" is inserted with an arrow pointing to the Save button.

like image 439
Ky. Avatar asked Jul 31 '19 05:07

Ky.


People also ask

How do I code a button in SwiftUI?

SwiftUI's button is similar to UIButton , except it's more flexible in terms of what content it shows and it uses a closure for its action rather than the old target/action system. To create a button with a string title you would start with code like this: Button("Button title") { print("Button tapped!") }

How do I programmatically click a button in SwiftUI?

For example: you can run actionOfButton() from the place you want programmatically tap the Button, it would work the same.

How do I disable a button in SwiftUI?

SwiftUI lets us disable any part of its forms or even the whole form, all by using the disabled() modifier. This takes a single Boolean that defines whether the element should be disabled or not. The form element's style automatically gets updated to reflect its status – buttons and toggles get grayed out, for example.

How do I make a circle button in SwiftUI?

We are going to create two @State var's called circleTapped and circlePressed. Then we are going to create a ZStack. Here we are going to be creating an Image for our button this image is going to be used from the SF Symbols app. Now that is done, we are now going to give the button a background.


2 Answers

macOS 11.0 / 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)
like image 179
stef Avatar answered Sep 25 '22 19:09

stef


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()
        }
    }
}
like image 27
Sindre Sorhus Avatar answered Sep 22 '22 19:09

Sindre Sorhus