Skip to content

Instantly share code, notes, and snippets.

@palmin
Last active December 7, 2023 23:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save palmin/c2110ec689747a20406afaab523c7673 to your computer and use it in GitHub Desktop.
Save palmin/c2110ec689747a20406afaab523c7673 to your computer and use it in GitHub Desktop.
import SwiftUI
#if os(iOS)
import UIKit
struct BranchPickerButton: UIViewRepresentable {
public var title: String = ""
public let producer: () async throws -> [(String, String)]
@Binding public var value: String
func makeUIView(context: Context) -> UIButton {
var config = UIButton.Configuration.plain()
config.title = value.isEmpty ? NSLocalizedString("Default", comment: "") : value
config.image = UIImage(systemName: "chevron.up.chevron.down")
config.imagePlacement = .trailing
config.imagePadding = 5
config.preferredSymbolConfigurationForImage = .init(pointSize: 12, weight: .regular)
config.titleAlignment = .trailing
config.contentInsets = .zero
let button = UIButton(configuration: config, primaryAction: nil)
button.contentHorizontalAlignment = .trailing
let defererred = UIDeferredMenuElement { [weak button] callback in
Task {
let items: [(String, String)]
do {
items = try await producer()
} catch {
let warning = UIAction(title: error.localizedDescription,
image: UIImage(systemName: "exclamationmark.triangle"), handler: { _ in })
warning.attributes = .disabled
callback([warning])
return
}
var elements = [UIMenuElement]()
for (title, value) in items {
let element = UIAction(title: title, handler: { _ in
button?.setTitle(title, for: .normal)
self.value = value
})
if(self.value == value) {
element.state = .on
}
elements.append(element)
}
callback(elements)
}
}
button.showsMenuAsPrimaryAction = true
button.menu = UIMenu(title: self.title, children: [defererred])
return button
}
func updateUIView(_ button: UIButton, context: Context) {
button.configuration?.title = value.isEmpty ? NSLocalizedString("Default", comment: "") : value
}
}
#endif
#if os(macOS)
import AppKit
fileprivate class BranchPickerPopupButton : NSPopUpButton {
var callback: (String) -> Void = { _ in }
var producer: () async throws -> [(String, String)] = { [] }
var value = ""
@objc func picked(_ sender: NSMenuItem) {
if let string = sender.representedObject as? String {
value = string
callback(string)
}
}
@objc func refreshMenu() {
guard let menu = menu else { return }
// Populate the menu asynchronously
Task {
do {
let items = try await producer()
menu.removeAllItems()
var selected: NSMenuItem?
for (title, itemValue) in items {
let menuItem = NSMenuItem(title: title,
action: #selector(BranchPickerPopupButton.picked),
keyEquivalent: "")
menuItem.target = self
menuItem.representedObject = itemValue
if value == itemValue {
selected = menuItem
}
menu.addItem(menuItem)
}
select(selected)
} catch {
menu.removeAllItems()
let warningItem = NSMenuItem(title: error.localizedDescription, action: nil, keyEquivalent: "")
warningItem.image = NSImage(systemSymbolName: "exclamationmark.triangle", accessibilityDescription: nil)
warningItem.isEnabled = false
menu.addItem(warningItem)
}
}
}
}
struct BranchPickerButton: NSViewRepresentable {
var title: String = ""
let producer: () async throws -> [(String, String)]
@Binding var value: String
func makeNSView(context: Context) -> NSPopUpButton {
let popUpButton = BranchPickerPopupButton()
popUpButton.callback = { value = $0 }
popUpButton.producer = producer
NotificationCenter.default.addObserver(popUpButton, selector: #selector(BranchPickerPopupButton.refreshMenu),
name: NSPopUpButton.willPopUpNotification,
object: popUpButton)
let deferredMenuElement = NSMenuItem()
deferredMenuElement.title = title
deferredMenuElement.submenu = NSMenu(title: title)
popUpButton.menu = deferredMenuElement.submenu
let title = value.isEmpty ? NSLocalizedString("Default", comment: "") : value
let menuItem = NSMenuItem(title: title, action: nil, keyEquivalent: "")
popUpButton.menu?.addItem(menuItem)
return popUpButton
}
func updateNSView(_ nsView: NSPopUpButton, context: Context) {
let button = nsView as? BranchPickerPopupButton
button?.value = value
let title = value.isEmpty ? NSLocalizedString("Default", comment: "") : value
let menuItem = NSMenuItem(title: title, action: nil, keyEquivalent: "")
button?.menu?.removeAllItems()
button?.menu?.addItem(menuItem)
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment