how to display tooltip / hint on some view? As example, on the button.
In SwiftUI on macOS 11, you can use the . help("Tooltip text") view modifier to add a tooltip. See the "What's new in SwiftUI" session for WWDC 2020.
Alternatively known as a balloon, help balloon, flyover help, or ScreenTip, a Tooltip is a text description near an object. The tooltip is displayed when the user hovers the mouse cursor over the object.
Many graphical web browsers display the title attribute of an HTML element as a tooltip when a user hovers the pointer over that element; in such a browser, when hovering over Wikipedia images and hyperlinks a tooltip will appear.
HTML: Use a container element (like <div>) and add the "tooltip" class to it. When the user mouse over this <div>, it will show the tooltip text. The tooltip text is placed inside an inline element (like <span>) with class="tooltiptext" .
In swiftUI 2:
Toggle("...", isOn: $isOn)
.help("this is tooltip")
In swiftUI 1 there is really no native way to create a tooltip. But here is a solution also for this:
import Foundation
import SwiftUI
public extension View {
/// Overlays this view with a view that provides a Help Tag.
func toolTip(_ toolTip: String) -> some View {
self.overlay(TooltipView(toolTip).allowsHitTesting(false))
}
}
private struct TooltipView: NSViewRepresentable {
let toolTip: String
init(_ toolTip: String?) {
if let toolTip = toolTip {
self.toolTip = toolTip
}
else
{
self.toolTip = ""
}
}
func makeNSView(context: NSViewRepresentableContext<TooltipView>) -> NSView {
NSView()
}
func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<TooltipView>) {
nsView.toolTip = self.toolTip
}
}
SwiftUI 2.0
As simple as
Button("Action") { }
.help("Just do something")
Button("Action") { }
.help(Text("Just do something"))
Thanks to both Andrew and Sorin for the solution direction. The presented solutions mostly worked but when I used them they totally messed up the layout. It turns out that the Tooltip has its own size, frame etc. which isn't automatically matching the content.
In theory I could address those problems by using fixed frames etc. but that did not seem the right direction to me.
I have come up with the following (slightly more complex) but easy to use solution which doesn't have these drawbacks.
extension View {
func tooltip(_ tip: String) -> some View {
background(GeometryReader { childGeometry in
TooltipView(tip, geometry: childGeometry) {
self
}
})
}
}
private struct TooltipView<Content>: View where Content: View {
let content: () -> Content
let tip: String
let geometry: GeometryProxy
init(_ tip: String, geometry: GeometryProxy, @ViewBuilder content: @escaping () -> Content) {
self.content = content
self.tip = tip
self.geometry = geometry
}
var body: some View {
Tooltip(tip, content: content)
.frame(width: geometry.size.width, height: geometry.size.height)
}
}
private struct Tooltip<Content: View>: NSViewRepresentable {
typealias NSViewType = NSHostingView<Content>
init(_ text: String?, @ViewBuilder content: () -> Content) {
self.text = text
self.content = content()
}
let text: String?
let content: Content
func makeNSView(context _: Context) -> NSHostingView<Content> {
NSViewType(rootView: content)
}
func updateNSView(_ nsView: NSHostingView<Content>, context _: Context) {
nsView.rootView = content
nsView.toolTip = text
}
}
I have added a GeometryReader
to the content of the tooltip and then constrain the size of the Tooltip to the match the size of the content.
To use it:
Toggle("...", isOn: $isOn)
.tooltip("This is my tip")
When the overlay isn't good enough, e.g. you want the tooltip on a control that accepts mouse events (and an overlay would not allow clicks through), such as Toggle, a solution may be to use a Host view that internally includes a NSHostingView itself - that supports a tooltip being an AppKit view - eventually loading further SwiftUI content inside:
struct Tooltip<Content: View>: NSViewRepresentable {
typealias NSViewType = NSHostingView<Content>
init(_ text: String?, @ViewBuilder content: () -> Content) {
self.text = text
self.content = content()
}
let text: String?
let content: Content
func makeNSView(context: NSViewRepresentableContext<Tooltip<Content>>) -> NSViewType {
NSViewType(rootView: content)
}
func updateNSView(_ nsView: NSViewType, context: NSViewRepresentableContext<Tooltip<Content>>) {
nsView.rootView = content
nsView.toolTip = text
}
}
This does have some caveats regarding sizing when used with certain SwiftUI content (and then you may hopefully use fixedSize() or a frame(width:height:) to get it working as you need), but it's otherwise easy to use:
Tooltip("A description") {
Toggle("...", isOn: $isOn)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With