SwiftUI:如何在悬停时显示工具提示/提示?

21
如何在某个视图上显示工具提示 / 提示信息?例如,在按钮上。 在这里输入图片描述

方法与我在回答你关于acceptsFirstMouse的问题时所描述的相同。 - Asperi
@Asperi 我相信我看到过这个本地修改器。但我不记得在哪里看到它,而且谷歌也没有告诉我。 - Andrew_STOP_RU_WAR_IN_UA
@Asperi 这个问题涉及到 UI 平台的一个独立部分,因此将其作为一个单独的问题是很好的。如果/当 Apple 支持这两个独立的关注点之一,它们可能不会采用相同的解决方案,并且它们可能不会同时到达,因此将其分成两个问题有助于整个社区,特别是随着新答案的出现。 - Ky -
6个回答

36

SwiftUI 2.0

就像它的名字一样简单

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

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

也许在 SwiftUI 2.0 的发布版本中它无法工作:https://stackoverflow.com/questions/64148126/value-of-type-has-no-member-help-after-update-to-latest-stable-xcode-r - Andrew_STOP_RU_WAR_IN_UA
这可能是最好的解决方案,但我刚试过了,它没有任何效果。我本来期望鼠标悬停在按钮上时会显示文本,但实际上并没有。 - RawMean
3
我已经成功地在macOS应用程序上运行了部署目标为11.0和Xcode 12.3。 - Nick N
可以确认,正如@NickN所提到的,这在macOS上似乎运行良好(目前在Big Sur 11.5.2上使用Xcode 13 Beta 5)。 - shufflingb

21

2020 | SwiftUI 1和2都适用

在SwiftUI 2中:

Toggle("...", isOn: $isOn)
    .help("this is tooltip")

在SwiftUI 1中,实际上没有本地方法来创建工具提示。但是这里也有一个解决方案:

import SwiftUI

@available(OSX 10.15, *)
public extension View {
    func tooltip(_ toolTip: String) -> some View {
        self.overlay(TooltipView(toolTip))
    }
}

@available(OSX 10.15, *)
private struct TooltipView: NSViewRepresentable {
    let toolTip: String
    
    init(_ toolTip: String) {
        self.toolTip = toolTip
    }
    
    func makeNSView(context: NSViewRepresentableContext<TooltipView>) -> NSView {
        NSView()
    }
    
    func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<TooltipView>) {
        nsView.toolTip = self.toolTip
    }
}

使用方法:

Text("some text with tooltip")
    .tooltip("some tooltip")

这在自定义ButtonStyle(在此处描述)中不起作用。https://dev59.com/b1MH5IYBdhLWcg3wwiSH - Mike Lee
这个解决方案在 TooltipView 中初始化 toolTip 为可选类型,有什么原因吗? - soundflix
另外2点:@available(OSX 11.0, *)不是必需的,因为它也适用于10.15,在SwiftUI之前也无法使用,所以我们可以完全省略它。此外,主要好处是它确实在10.15中起作用,否则我们可以使用.help(..)import Foundation也不是必需的,因为它将随SwiftUI一起导入。 - soundflix

7
感谢AndrewSorin提供的解决方向。虽然这些解决方案大部分都有效,但当我使用它们时,它们完全搞乱了布局。原来,工具提示有自己的大小、框架等,这些并不会自动匹配内容。
理论上,我可以通过使用固定的框架等来解决这些问题,但这似乎不是正确的方向。
我想出了以下(稍微复杂)但易于使用的解决方案,它没有这些缺点。
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
    }
}

我已将GeometryReader添加到工具提示的内容中,然后将工具提示的大小限制为与内容的大小相匹配。
使用方法如下:
Toggle("...", isOn: $isOn)
   .tooltip("This is my tip")

2023年1月的新增内容

最新版本的SwiftUI在tooltip方法上需要进行一些小的更改。我注意到存在快速重绘问题。通过添加ZStack,可以解决这个问题:

extension View {
    func tooltip(_ tip: String) -> some View {
        ZStack {
            background(GeometryReader { childGeometry in
                TooltipView(tip, geometry: childGeometry) {
                    self
                }
            })
            // self
        }
    }
}

2023年2月:我移除了第二个自我。有了多余的自我,父母会发出光芒。


2

当覆盖层不够好用时,例如您想要在接受鼠标事件的控件(而覆盖层将不允许通过点击),如Toggle上使用工具提示,则解决方案可能是使用一个包含NSHostingView本身的主机视图 - 它支持AppKit视图作为工具提示 - 最终在内部加载更多SwiftUI内容:

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
    }
}

使用这个方法时,如果与某些SwiftUI内容一起使用,则需要注意大小问题(然后您可以使用fixedSize()或frame(width:height:)使其按照您的需求工作),但除此之外它非常易于使用:

Tooltip("A description") { 
    Toggle("...", isOn: $isOn) 
}

1
struct TooltipText: View {
    @State private var isActive = false
    
    let text: String
    let helpText: String
    var body: some View {
        Text(isActive ? helpText : text)
            .padding( isActive ? 6 : 0)
            .background(Color.white)
            .overlay(
                RoundedRectangle(cornerRadius: 3)
                    .stroke(Color.blue, lineWidth: isActive  ? 1 : 0)
            )
            .animation(.easeOut(duration: 0.2) )
            .gesture(DragGesture(minimumDistance: 0)
                        .onChanged( { _ in isActive = true } )
                        .onEnded( { _ in isActive = false } )
            )
    }
}

使用:

TooltipText(text: "sum of squares:", helpText: "sum of data (with mean subtracted) squared")

1
谢谢并欢迎 @user3585825,您能否包含一些解释。 - Useme Alehosaini

0

你也可以在界面构建器(在Xcode中以图形方式布局您的视图)中完成此操作。当您添加按钮时,“Identity Inspector”选项卡 - 位于屏幕右侧面板上默认的“Attributes Inspector”选项卡之前...还有一个“Tooltip”字段。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接