Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI : How do you display a tooltip / hint on hover?

how to display tooltip / hint on some view? As example, on the button.

enter image description here

like image 593
Andrew Avatar asked Dec 01 '19 19:12

Andrew


People also ask

How do I add a tooltip in SwiftUI?

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.

What is a hover tooltip?

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.

Which element is used to display a tooltip for a hyperlink?

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.

How do you put a tooltip in a paragraph?

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" .


Video Answer


4 Answers

2020 | SwiftUI 1 and 2 both

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
    }
}
like image 22
Andrew Avatar answered Nov 16 '22 00:11

Andrew


SwiftUI 2.0

As simple as

   Button("Action") { }
     .help("Just do something")

   Button("Action") { }
     .help(Text("Just do something"))
like image 140
Asperi Avatar answered Nov 15 '22 23:11

Asperi


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")
like image 21
Fred Appelman Avatar answered Nov 16 '22 00:11

Fred Appelman


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) 
}
like image 42
Sorin Dolha Avatar answered Nov 15 '22 23:11

Sorin Dolha