Skip to content

Instantly share code, notes, and snippets.

@niaeashes
Last active March 4, 2022 11:54
Show Gist options
  • Save niaeashes/d8f7f17c56f2ccd444032b0145b62283 to your computer and use it in GitHub Desktop.
Save niaeashes/d8f7f17c56f2ccd444032b0145b62283 to your computer and use it in GitHub Desktop.
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<Content: View>: 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<Tooltip: View>: 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()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment