我在 swiftui 中创建了工具提示,但是当我将它与覆盖层一起使用时 - 框架被破坏。
import SwiftUI
struct HintBox: View {
@State private var showTooltip = true
var body: some View {
VStack {
Text("Here text")
TooltipView(text: "it's hint! it's hint! it's hint! it's hint! it's hint! it's hint!", isVisible: $showTooltip)
.frame(maxWidth: .infinity)
.onTapGesture {
showTooltip.toggle()
}
}
}
}
struct TooltipView: View {
var text: String
@Binding var isVisible: Bool
var body: some View {
ZStack(alignment: .top) {
if isVisible {
Text(text)
.padding()
.background(Color.gray)
.foregroundColor(.white)
.cornerRadius(8)
Triangle()
.fill(Color.gray)
.frame(width: 20, height: 10)
.offset(y: 0)
}
}
}
}
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.midX - 10, y: rect.minY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY - 10))
path.addLine(to: CGPoint(x: rect.midX + 10, y: rect.minY))
path.closeSubpath()
return path
}
}
我需要这样做,以便我可以准确确定三角形的位置(底部或顶部)、中间、左侧或右侧。而且,它必须高于其他观点。即用一个三角形指向我将附加它的视图。当您点击它时,它应该停止显示
实现此目的的一种方法是将提示显示为源视图上的覆盖层。覆盖层会自动采用底层视图的大小,并使用
GeometryReader
您可以通过这种方式找到源视图的大小。
您希望提示能够突破源足迹的界限。这是通过使用
.fixedSize()
来完成的。但是,这意味着叠加层不再居中,因此您必须应用偏移量才能使其回到中心。
然后,为了将提示对齐到源视图的上方、下方、之前或之后,您需要知道提示本身的大小。在这里,您可以再次使用相同的技术,在提示的隐藏版本上设置覆盖层,并使用另一个
GeometryReader
来查找其大小。
这里尝试展示它的工作原理:
struct HintBox: View {
@State private var showTooltip = true
var body: some View {
VStack {
Text("Text here")
.overlay {
TooltipView(
text: "it's hint! it's hint! it's hint! it's hint! it's hint!",
alignment: .top,
isVisible: $showTooltip
)
}
.onTapGesture {
showTooltip.toggle()
}
}
}
}
struct TooltipView: View {
var text: String
let alignment: Edge
@Binding var isVisible: Bool
let arrowOffset = CGFloat(8)
private var oppositeAlignment: Alignment {
let result: Alignment
switch alignment {
case .top: result = .bottom
case .bottom: result = .top
case .leading: result = .trailing
case .trailing: result = .leading
}
return result
}
private var theHint: some View {
Text(text)
.padding()
.background(Color.gray)
.foregroundColor(.white)
.cornerRadius(8)
.overlay(alignment: oppositeAlignment) {
// The arrow is a square that is rotated by 45 degrees
Rectangle()
.fill(Color.gray)
.frame(width: 22, height: 22)
.rotationEffect(.degrees(45))
.offset(x: alignment == .leading ? arrowOffset : 0)
.offset(x: alignment == .trailing ? -arrowOffset : 0)
.offset(y: alignment == .top ? arrowOffset : 0)
.offset(y: alignment == .bottom ? -arrowOffset : 0)
}
.padding()
.fixedSize()
}
var body: some View {
if isVisible {
GeometryReader { proxy1 in
// Use a hidden version of the hint to form the footprint
theHint
.hidden()
.overlay {
GeometryReader { proxy2 in
// The visible version of the hint
theHint
.drawingGroup()
.shadow(radius: 4)
// Center the hint over the source view
.offset(
x: -(proxy2.size.width / 2) + (proxy1.size.width / 2),
y: -(proxy2.size.height / 2) + (proxy1.size.height / 2)
)
// Move the hint to the required edge
.offset(x: alignment == .leading ? (-proxy2.size.width / 2) - (proxy1.size.width / 2) : 0)
.offset(x: alignment == .trailing ? (proxy2.size.width / 2) + (proxy1.size.width / 2) : 0)
.offset(y: alignment == .top ? (-proxy2.size.height / 2) - (proxy1.size.height / 2) : 0)
.offset(y: alignment == .bottom ? (proxy2.size.height / 2) + (proxy1.size.height / 2) : 0)
}
}
}
.onTapGesture {
isVisible.toggle()
}
}
}
}