import SwiftUI struct OffsetKey: PreferenceKey { static var defaultValue: CGFloat = .zero static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } } struct ContentView: View { @Namespace var scrollId var body: some View { ScrollView { TooltipReader { proxy in ZStack { VStack { ForEach(0..<10, id: \.self) { i in Text("No.\(i)").padding() } Button(action: { withAnimation { proxy.toggle() } }) { Text("Hello, world!") .padding() .compositingGroup() } .modifier(proxy.capturer) ForEach(0..<10, id: \.self) { i in Text("No.\(i)").padding() } } GeometryReader { geometry in Color.clear.preference(key: OffsetKey.self, value: geometry.frame(in: .named(scrollId)).minY) } .frame(height: 0) } .modifier(TooltipModifier { OverlayView() }) .onPreferenceChange(OffsetKey.self) { _ in withAnimation { proxy.hide() } } } } .coordinateSpace(name: scrollId) } } struct TooltipReader: View { let content: (TooltipProxy) -> Content @StateObject var proxy = TooltipProxy() init(@ViewBuilder content: @escaping (TooltipProxy) -> Content) { self.content = content } var body: some View { content(proxy) .coordinateSpace(name: proxy.contentId) .environmentObject(proxy) } } class TooltipProxy: ObservableObject { var isPresented = false { willSet { if isPresented != newValue { objectWillChange.send() } } } @Published var frame: CGRect = .zero var contentId = UUID() func focus(frame: CGRect) { self.frame = frame } func toggle() { isPresented.toggle() } func show() { isPresented = true } func hide() { isPresented = false } var capturer: some ViewModifier { FrameCaptureModifier(proxy: self) } struct RectKey: PreferenceKey { static var defaultValue: CGRect = .zero static func reduce(value: inout CGRect, nextValue: () -> CGRect) { value = nextValue() } } struct FrameCaptureModifier: ViewModifier { let proxy: TooltipProxy func body(content: Content) -> some View { content .background(GeometryReader { geometry in Color.clear .preference(key: RectKey.self, value: geometry.frame(in: .named(proxy.contentId))) .onPreferenceChange(RectKey.self) { proxy.focus(frame: $0) } }) } } } struct TooltipModifier: ViewModifier { let tooltip: Tooltip @EnvironmentObject var proxy: TooltipProxy init(@ViewBuilder tooltip: () -> Tooltip) { self.tooltip = tooltip() } var frame: CGRect { proxy.frame } func body(content: Content) -> some View { ZStack { content if proxy.isPresented { tooltip .position(x: frame.midX, y: frame.midY - frame.height - 16) } } } } struct OverlayView: View { var body: some View { Button(action: { print("Tap!!!") }) { Text("Can you tap this button?") .padding() } .background(Color(UIColor.systemBackground)) .clipShape(Capsule()) .contentShape(Capsule()) .overlay(Capsule().stroke(Color.accentColor, lineWidth: 2)) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }