I need to make a SwiftUI tooltip component that supports being applied to any SwiftUI view, ideally via a ViewModifier. What I want to accomplish is pretty much exactly what AirBnb currently has, to be more precise, this: https://imgur.com/a/Y6uW4YX
The View should have the entire screen width minus 20pts horizontal margins on each side (as you can see on the AirBnb example).
My current implementation sorta works but layout breaks if the View is not exactly centered in the middle of the screen.
Current code:
struct TestView: View {
var body: some View {
HStack {
Rectangle()
.frame(width: 100, height: 50)
.foregroundStyle(Color.green)
.tooltip()
}
}
}
extension View {
func tooltip() -> some View {
modifier(TooltipModifier())
}
}
struct TooltipModifier: ViewModifier {
func body(content: Content) -> some View {
content
.overlay(alignment: .bottom) {
Tooltip()
.fixedSize()
.alignmentGuide(.bottom, computeValue: { dimension in
dimension[.top] - 20
})
}
}
}
struct Tooltip: View {
var body: some View {
ZStack(alignment: .top) {
HStack {
Text("This is some text that guides the user")
Spacer()
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 16, height: 16)
}
.padding(8)
// This only works if the view is exactly in the middle of the screen, if not then alignment breaks.
.frame(width: UIScreen.main.bounds.width - 40)
.background(Color.red.opacity(0.5))
.cornerRadius(8)
Triangle()
.fill(Color.red.opacity(0.5))
.frame(width: 20, height: 10)
.offset(y: -10)
}
}
}
struct Triangle: Shape {
public func path(in rect: CGRect) -> Path {
var path = Path()
let topMiddle = CGPoint(x: rect.midX, y: rect.minY)
let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY)
let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY)
path.move(to: bottomLeft)
path.addLine(to: bottomRight)
path.addArc(
center: CGPoint(x: topMiddle.x, y: topMiddle.y),
radius: 0,
startAngle: .degrees(0),
endAngle: .degrees(180),
clockwise: true
)
path.addLine(to: bottomLeft)
return path
}
}
If the view is exactly centered in the middle of the screen such as what TestView currently has, the result is what I need:
But if we add a second rectangle to the main HStack so it is no longer in the middle of the screen, then layout breaks:
Any idea how can I make it so it takes the full width and supports views on any part of the screen? Tried using GeometryReaders but as usual, they seem to completely destroy my layout.
Thanks!


