Skip to content

Instantly share code, notes, and snippets.

@nielsmouthaan
Last active May 13, 2023 01:56
Show Gist options
  • Save nielsmouthaan/1d30fb9ea7357c5ae8f6324747fc481e to your computer and use it in GitHub Desktop.
Save nielsmouthaan/1d30fb9ea7357c5ae8f6324747fc481e to your computer and use it in GitHub Desktop.

This gist shows how to add a tooltip including a button to show it in a header of a table created using SwiftUI. It is used in ASO Suite.

Clean-Shot-2023-05-12-at-08-39-08-2x.png

import Foundation
extension AnyHashable {
struct tooltip {
static let tag = "tooltip.tag"
static let point = "tooltip.point"
}
}
import Foundation
extension Notification.Name {
static let tooltipButtonClicked = Notification.Name("tooltipButtonClicked")
}
import SwiftUI
import Introspect // https://github.com/siteline/SwiftUI-Introspect
struct Item: Identifiable {
var id = UUID()
var name: String
}
struct TableWithTooltips: View {
@State var items: [Item] = [
Item(name: "Some item"),
Item(name: "Another item"),
Item(name: "Yet another item")
]
@State private var tooltipVisibleForColumn: Int?
@State private var tooltipAnchorPoint = NSZeroPoint
var body: some View {
Table(items) {
TableColumn("Name") {
Text($0.name)
}
}
.introspectTableView { tableView in
let column = 0
let title = tableView.tableColumns[column].title
tableView.tableColumns[column].headerCell = TooltipHeaderCell(textCell: title, tag:column)
}
.popover(isPresented: showingTooltip, attachmentAnchor: .rect(.rect(CGRect(x: tooltipAnchorPoint.x, y: tooltipAnchorPoint.y, width: 0, height: 0))), arrowEdge: .bottom, content: {
VStack {
Text("Hello!")
}
.padding()
.frame(width: 250)
})
.onReceive(NotificationCenter.default.publisher(for: .tooltipButtonClicked)) { notification in
if let userInfo = notification.userInfo {
if let tag = userInfo[.tooltip.tag] as? Int, let point = userInfo[.tooltip.point] as? NSPoint {
tooltipVisibleForColumn = tag
tooltipAnchorPoint = point
}
}
}
}
private var showingTooltip: Binding<Bool> {
return Binding(
get: { tooltipVisibleForColumn != nil },
set: { if !$0 { tooltipVisibleForColumn = nil } }
)
}
}
struct TestTable_Previews: PreviewProvider {
static var previews: some View {
TableWithTooltips()
}
}
import Foundation
import AppKit
class TooltipHeaderCell: NSTableHeaderCell {
init(textCell: String, tag: Int) {
super.init(textCell: textCell)
self.tag = tag
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView)
{
super.drawInterior(withFrame: cellFrame, in: controlView)
for subview in controlView.subviews {
if subview.tag == tag {
subview.removeFromSuperview()
}
}
if cellFrame.origin.x == 0 {
return
}
let attributes: [NSAttributedString.Key: Any] = (font != nil) ? [.font: font!] : [:]
let titleWidth = (title as NSString).size(withAttributes: attributes).width
let x = titleRect(forBounds: cellFrame).origin.x + titleWidth + 15
let frame = NSRect(x: x, y: titleRect(forBounds: cellFrame).origin.y, width: cellFrame.height, height: cellFrame.height)
let button = NSButton(frame: frame)
button.tag = tag
button.image = NSImage(systemSymbolName: "info.circle.fill", accessibilityDescription: nil)
button.imagePosition = .imageOnly
button.isBordered = false
button.target = self
button.action = #selector(buttonClicked(_:))
controlView.addSubview(button)
}
@objc func buttonClicked(_ sender: NSButton) {
let point = NSPoint(x: sender.frame.midX, y: sender.frame.maxY)
NotificationCenter.default.post(name: .tooltipButtonClicked, object: self, userInfo: [
.tooltip.tag: tag,
.tooltip.point: point
]) // Click handler is prefered, but that caused a crash so using a notification instead.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment